MTLGfx.mm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. // Copyright 2022 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "VideoBackends/Metal/MTLGfx.h"
  4. #include "VideoBackends/Metal/MTLBoundingBox.h"
  5. #include "VideoBackends/Metal/MTLObjectCache.h"
  6. #include "VideoBackends/Metal/MTLPipeline.h"
  7. #include "VideoBackends/Metal/MTLStateTracker.h"
  8. #include "VideoBackends/Metal/MTLTexture.h"
  9. #include "VideoBackends/Metal/MTLUtil.h"
  10. #include "VideoBackends/Metal/MTLVertexFormat.h"
  11. #include "VideoBackends/Metal/MTLVertexManager.h"
  12. #include "VideoCommon/FramebufferManager.h"
  13. #include "VideoCommon/Present.h"
  14. #include "VideoCommon/VideoBackendBase.h"
  15. #include <fstream>
  16. Metal::Gfx::Gfx(MRCOwned<CAMetalLayer*> layer) : m_layer(std::move(layer))
  17. {
  18. UpdateActiveConfig();
  19. [m_layer setDisplaySyncEnabled:g_ActiveConfig.bVSyncActive];
  20. SetupSurface();
  21. g_state_tracker->FlushEncoders();
  22. }
  23. Metal::Gfx::~Gfx() = default;
  24. bool Metal::Gfx::IsHeadless() const
  25. {
  26. return m_layer == nullptr;
  27. }
  28. // MARK: Texture Creation
  29. static MTLTextureType FromAbstract(AbstractTextureType type, bool multisample)
  30. {
  31. switch (type)
  32. {
  33. case AbstractTextureType::Texture_2D:
  34. return multisample ? MTLTextureType2DMultisample : MTLTextureType2D;
  35. case AbstractTextureType::Texture_2DArray:
  36. return multisample ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray;
  37. case AbstractTextureType::Texture_CubeMap:
  38. return MTLTextureTypeCube;
  39. }
  40. ASSERT(false);
  41. return MTLTextureType2DArray;
  42. }
  43. std::unique_ptr<AbstractTexture> Metal::Gfx::CreateTexture(const TextureConfig& config,
  44. std::string_view name)
  45. {
  46. @autoreleasepool
  47. {
  48. MRCOwned<MTLTextureDescriptor*> desc = MRCTransfer([MTLTextureDescriptor new]);
  49. [desc setTextureType:FromAbstract(config.type, config.samples > 1)];
  50. [desc setPixelFormat:Util::FromAbstract(config.format)];
  51. [desc setWidth:config.width];
  52. [desc setHeight:config.height];
  53. [desc setMipmapLevelCount:config.levels];
  54. [desc setArrayLength:config.layers];
  55. [desc setSampleCount:config.samples];
  56. [desc setStorageMode:MTLStorageModePrivate];
  57. MTLTextureUsage usage = MTLTextureUsageShaderRead;
  58. if (config.IsRenderTarget())
  59. usage |= MTLTextureUsageRenderTarget;
  60. if (config.IsComputeImage())
  61. usage |= MTLTextureUsageShaderWrite;
  62. [desc setUsage:usage];
  63. id<MTLTexture> texture = [g_device newTextureWithDescriptor:desc];
  64. if (!texture)
  65. return nullptr;
  66. if (name.empty())
  67. [texture setLabel:[NSString stringWithFormat:@"Texture %d", m_texture_counter++]];
  68. else
  69. [texture setLabel:MRCTransfer([[NSString alloc] initWithBytes:name.data()
  70. length:name.size()
  71. encoding:NSUTF8StringEncoding])];
  72. return std::make_unique<Texture>(MRCTransfer(texture), config);
  73. }
  74. }
  75. std::unique_ptr<AbstractStagingTexture>
  76. Metal::Gfx::CreateStagingTexture(StagingTextureType type, const TextureConfig& config)
  77. {
  78. @autoreleasepool
  79. {
  80. const size_t stride = config.GetStride();
  81. const size_t buffer_size = stride * static_cast<size_t>(config.height);
  82. MTLResourceOptions options = MTLStorageModeShared;
  83. if (type == StagingTextureType::Upload)
  84. options |= MTLResourceCPUCacheModeWriteCombined;
  85. id<MTLBuffer> buffer = [g_device newBufferWithLength:buffer_size options:options];
  86. if (!buffer)
  87. return nullptr;
  88. [buffer
  89. setLabel:[NSString stringWithFormat:@"Staging Texture %d", m_staging_texture_counter++]];
  90. return std::make_unique<StagingTexture>(MRCTransfer(buffer), type, config);
  91. }
  92. }
  93. std::unique_ptr<AbstractFramebuffer>
  94. Metal::Gfx::CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment,
  95. std::vector<AbstractTexture*> additional_color_attachments)
  96. {
  97. AbstractTexture* const either_attachment = color_attachment ? color_attachment : depth_attachment;
  98. return std::make_unique<Framebuffer>(
  99. color_attachment, depth_attachment, std::move(additional_color_attachments),
  100. either_attachment->GetWidth(), either_attachment->GetHeight(), either_attachment->GetLayers(),
  101. either_attachment->GetSamples());
  102. }
  103. // MARK: Pipeline Creation
  104. std::unique_ptr<AbstractShader> Metal::Gfx::CreateShaderFromSource(ShaderStage stage,
  105. std::string_view source,
  106. std::string_view name)
  107. {
  108. std::optional<std::string> msl = Util::TranslateShaderToMSL(stage, source);
  109. if (!msl.has_value())
  110. {
  111. PanicAlertFmt("Failed to convert shader {} to MSL", name);
  112. return nullptr;
  113. }
  114. return CreateShaderFromMSL(stage, std::move(*msl), source, name);
  115. }
  116. std::unique_ptr<AbstractShader> Metal::Gfx::CreateShaderFromBinary(ShaderStage stage,
  117. const void* data, size_t length,
  118. std::string_view name)
  119. {
  120. return CreateShaderFromMSL(stage, std::string(static_cast<const char*>(data), length), {}, name);
  121. }
  122. // clang-format off
  123. static const char* StageFilename(ShaderStage stage)
  124. {
  125. switch (stage)
  126. {
  127. case ShaderStage::Vertex: return "vs";
  128. case ShaderStage::Geometry: return "gs";
  129. case ShaderStage::Pixel: return "ps";
  130. case ShaderStage::Compute: return "cs";
  131. }
  132. }
  133. static NSString* GenericShaderName(ShaderStage stage)
  134. {
  135. switch (stage)
  136. {
  137. case ShaderStage::Vertex: return @"Vertex shader %d";
  138. case ShaderStage::Geometry: return @"Geometry shader %d";
  139. case ShaderStage::Pixel: return @"Pixel shader %d";
  140. case ShaderStage::Compute: return @"Compute shader %d";
  141. }
  142. }
  143. // clang-format on
  144. std::unique_ptr<AbstractShader> Metal::Gfx::CreateShaderFromMSL(ShaderStage stage, std::string msl,
  145. std::string_view glsl,
  146. std::string_view name)
  147. {
  148. @autoreleasepool
  149. {
  150. NSError* err = nullptr;
  151. auto DumpBadShader = [&](std::string_view msg) {
  152. static int counter = 0;
  153. std::string filename = VideoBackendBase::BadShaderFilename(StageFilename(stage), counter++);
  154. std::ofstream stream(filename);
  155. if (stream.good())
  156. {
  157. stream << msl << std::endl;
  158. stream << "/*" << std::endl;
  159. stream << msg << std::endl;
  160. stream << "Error:" << std::endl;
  161. stream << [[err localizedDescription] UTF8String] << std::endl;
  162. if (!glsl.empty())
  163. {
  164. stream << "Original GLSL:" << std::endl;
  165. stream << glsl << std::endl;
  166. }
  167. else
  168. {
  169. stream << "Shader was created with cached MSL so no GLSL is available." << std::endl;
  170. }
  171. }
  172. stream << std::endl;
  173. stream << "Dolphin Version: " << Common::GetScmRevStr() << std::endl;
  174. stream << "Video Backend: " << g_video_backend->GetDisplayName() << std::endl;
  175. stream << "*/" << std::endl;
  176. stream.close();
  177. PanicAlertFmt("{} (written to {})\n", msg, filename);
  178. };
  179. auto lib = MRCTransfer([g_device newLibraryWithSource:[NSString stringWithUTF8String:msl.data()]
  180. options:nil
  181. error:&err]);
  182. if (err)
  183. {
  184. DumpBadShader(fmt::format("Failed to compile {}", name));
  185. return nullptr;
  186. }
  187. auto fn = MRCTransfer([lib newFunctionWithName:@"main0"]);
  188. if (!fn)
  189. {
  190. DumpBadShader(fmt::format("Shader {} is missing its main0 function", name));
  191. return nullptr;
  192. }
  193. if (!name.empty())
  194. [fn setLabel:MRCTransfer([[NSString alloc] initWithBytes:name.data()
  195. length:name.size()
  196. encoding:NSUTF8StringEncoding])];
  197. else
  198. [fn setLabel:[NSString stringWithFormat:GenericShaderName(stage),
  199. m_shader_counter[static_cast<u32>(stage)]++]];
  200. [lib setLabel:[fn label]];
  201. if (stage == ShaderStage::Compute)
  202. {
  203. MTLComputePipelineReflection* reflection = nullptr;
  204. auto desc = [MTLComputePipelineDescriptor new];
  205. [desc setComputeFunction:fn];
  206. [desc setLabel:[fn label]];
  207. MRCOwned<id<MTLComputePipelineState>> pipeline =
  208. MRCTransfer([g_device newComputePipelineStateWithDescriptor:desc
  209. options:MTLPipelineOptionArgumentInfo
  210. reflection:&reflection
  211. error:&err]);
  212. if (err)
  213. {
  214. DumpBadShader(fmt::format("Failed to compile compute pipeline {}", name));
  215. return nullptr;
  216. }
  217. return std::make_unique<ComputePipeline>(stage, reflection, std::move(msl), std::move(fn),
  218. std::move(pipeline));
  219. }
  220. return std::make_unique<Shader>(stage, std::move(msl), std::move(fn));
  221. }
  222. }
  223. std::unique_ptr<NativeVertexFormat>
  224. Metal::Gfx::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl)
  225. {
  226. @autoreleasepool
  227. {
  228. return std::make_unique<VertexFormat>(vtx_decl);
  229. }
  230. }
  231. std::unique_ptr<AbstractPipeline> Metal::Gfx::CreatePipeline(const AbstractPipelineConfig& config,
  232. const void* cache_data,
  233. size_t cache_data_length)
  234. {
  235. return g_object_cache->CreatePipeline(config);
  236. }
  237. void Metal::Gfx::Flush()
  238. {
  239. @autoreleasepool
  240. {
  241. g_state_tracker->FlushEncoders();
  242. }
  243. }
  244. void Metal::Gfx::WaitForGPUIdle()
  245. {
  246. @autoreleasepool
  247. {
  248. g_state_tracker->FlushEncoders();
  249. g_state_tracker->WaitForFlushedEncoders();
  250. }
  251. }
  252. void Metal::Gfx::OnConfigChanged(u32 bits)
  253. {
  254. AbstractGfx::OnConfigChanged(bits);
  255. if (bits & CONFIG_CHANGE_BIT_VSYNC)
  256. [m_layer setDisplaySyncEnabled:g_ActiveConfig.bVSyncActive];
  257. if (bits & CONFIG_CHANGE_BIT_ANISOTROPY)
  258. {
  259. g_object_cache->ReloadSamplers();
  260. g_state_tracker->ReloadSamplers();
  261. }
  262. }
  263. void Metal::Gfx::ClearRegion(const MathUtil::Rectangle<int>& target_rc, bool color_enable,
  264. bool alpha_enable, bool z_enable, u32 color, u32 z)
  265. {
  266. u32 framebuffer_width = m_current_framebuffer->GetWidth();
  267. u32 framebuffer_height = m_current_framebuffer->GetHeight();
  268. // All Metal render passes are fullscreen, so we can only run a fast clear if the target is too
  269. if (target_rc == MathUtil::Rectangle<int>(0, 0, framebuffer_width, framebuffer_height))
  270. {
  271. // Determine whether the EFB has an alpha channel. If it doesn't, we can clear the alpha
  272. // channel to 0xFF. This hopefully allows us to use the fast path in most cases.
  273. if (bpmem.zcontrol.pixel_format == PixelFormat::RGB565_Z16 ||
  274. bpmem.zcontrol.pixel_format == PixelFormat::RGB8_Z24 ||
  275. bpmem.zcontrol.pixel_format == PixelFormat::Z24)
  276. {
  277. // Force alpha writes, and clear the alpha channel. This is different from the other backends,
  278. // where the existing values of the alpha channel are preserved.
  279. alpha_enable = true;
  280. color &= 0x00FFFFFF;
  281. }
  282. bool c_ok = (color_enable && alpha_enable) ||
  283. g_state_tracker->GetCurrentFramebuffer()->GetColorFormat() ==
  284. AbstractTextureFormat::Undefined;
  285. bool z_ok = z_enable || g_state_tracker->GetCurrentFramebuffer()->GetDepthFormat() ==
  286. AbstractTextureFormat::Undefined;
  287. if (c_ok && z_ok)
  288. {
  289. @autoreleasepool
  290. {
  291. // clang-format off
  292. MTLClearColor clear_color = MTLClearColorMake(
  293. static_cast<double>((color >> 16) & 0xFF) / 255.0,
  294. static_cast<double>((color >> 8) & 0xFF) / 255.0,
  295. static_cast<double>((color >> 0) & 0xFF) / 255.0,
  296. static_cast<double>((color >> 24) & 0xFF) / 255.0);
  297. // clang-format on
  298. float z_normalized = static_cast<float>(z & 0xFFFFFF) / 16777216.0f;
  299. if (!g_Config.backend_info.bSupportsReversedDepthRange)
  300. z_normalized = 1.f - z_normalized;
  301. g_state_tracker->BeginClearRenderPass(clear_color, z_normalized);
  302. return;
  303. }
  304. }
  305. }
  306. g_state_tracker->EnableEncoderLabel(false);
  307. AbstractGfx::ClearRegion(target_rc, color_enable, alpha_enable, z_enable, color, z);
  308. g_state_tracker->EnableEncoderLabel(true);
  309. }
  310. void Metal::Gfx::SetPipeline(const AbstractPipeline* pipeline)
  311. {
  312. g_state_tracker->SetPipeline(static_cast<const Pipeline*>(pipeline));
  313. }
  314. void Metal::Gfx::SetFramebuffer(AbstractFramebuffer* framebuffer)
  315. {
  316. // Shouldn't be bound as a texture.
  317. if (AbstractTexture* color = framebuffer->GetColorAttachment())
  318. g_state_tracker->UnbindTexture(static_cast<Texture*>(color)->GetMTLTexture());
  319. if (AbstractTexture* depth = framebuffer->GetDepthAttachment())
  320. g_state_tracker->UnbindTexture(static_cast<Texture*>(depth)->GetMTLTexture());
  321. m_current_framebuffer = framebuffer;
  322. g_state_tracker->SetCurrentFramebuffer(static_cast<Framebuffer*>(framebuffer));
  323. }
  324. void Metal::Gfx::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer)
  325. {
  326. @autoreleasepool
  327. {
  328. SetFramebuffer(framebuffer);
  329. g_state_tracker->BeginRenderPass(MTLLoadActionDontCare);
  330. }
  331. }
  332. void Metal::Gfx::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer,
  333. const ClearColor& color_value, float depth_value)
  334. {
  335. @autoreleasepool
  336. {
  337. SetFramebuffer(framebuffer);
  338. MTLClearColor color =
  339. MTLClearColorMake(color_value[0], color_value[1], color_value[2], color_value[3]);
  340. g_state_tracker->BeginClearRenderPass(color, depth_value);
  341. }
  342. }
  343. void Metal::Gfx::SetScissorRect(const MathUtil::Rectangle<int>& rc)
  344. {
  345. g_state_tracker->SetScissor(rc);
  346. }
  347. void Metal::Gfx::SetTexture(u32 index, const AbstractTexture* texture)
  348. {
  349. g_state_tracker->SetTexture(
  350. index, texture ? static_cast<const Texture*>(texture)->GetMTLTexture() : nullptr);
  351. }
  352. void Metal::Gfx::SetSamplerState(u32 index, const SamplerState& state)
  353. {
  354. g_state_tracker->SetSampler(index, state);
  355. }
  356. void Metal::Gfx::SetComputeImageTexture(u32 index, AbstractTexture* texture, bool read, bool write)
  357. {
  358. g_state_tracker->SetTexture(index + VideoCommon::MAX_COMPUTE_SHADER_SAMPLERS,
  359. texture ? static_cast<const Texture*>(texture)->GetMTLTexture() :
  360. nullptr);
  361. }
  362. void Metal::Gfx::UnbindTexture(const AbstractTexture* texture)
  363. {
  364. g_state_tracker->UnbindTexture(static_cast<const Texture*>(texture)->GetMTLTexture());
  365. }
  366. void Metal::Gfx::SetViewport(float x, float y, float width, float height, float near_depth,
  367. float far_depth)
  368. {
  369. g_state_tracker->SetViewport(x, y, width, height, near_depth, far_depth);
  370. }
  371. void Metal::Gfx::Draw(u32 base_vertex, u32 num_vertices)
  372. {
  373. @autoreleasepool
  374. {
  375. g_state_tracker->Draw(base_vertex, num_vertices);
  376. }
  377. }
  378. void Metal::Gfx::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex)
  379. {
  380. @autoreleasepool
  381. {
  382. g_state_tracker->DrawIndexed(base_index, num_indices, base_vertex);
  383. }
  384. }
  385. void Metal::Gfx::DispatchComputeShader(const AbstractShader* shader, //
  386. u32 groupsize_x, u32 groupsize_y, u32 groupsize_z,
  387. u32 groups_x, u32 groups_y, u32 groups_z)
  388. {
  389. @autoreleasepool
  390. {
  391. g_state_tracker->SetPipeline(static_cast<const ComputePipeline*>(shader));
  392. g_state_tracker->DispatchComputeShader(groupsize_x, groupsize_y, groupsize_z, //
  393. groups_x, groups_y, groups_z);
  394. }
  395. }
  396. bool Metal::Gfx::BindBackbuffer(const ClearColor& clear_color)
  397. {
  398. @autoreleasepool
  399. {
  400. CheckForSurfaceChange();
  401. CheckForSurfaceResize();
  402. m_drawable = MRCRetain([m_layer nextDrawable]);
  403. m_backbuffer->UpdateBackbufferTexture([m_drawable texture]);
  404. SetAndClearFramebuffer(m_backbuffer.get(), clear_color);
  405. return m_drawable != nullptr;
  406. }
  407. }
  408. void Metal::Gfx::PresentBackbuffer()
  409. {
  410. @autoreleasepool
  411. {
  412. g_state_tracker->EndRenderPass();
  413. if (m_drawable)
  414. {
  415. // PresentDrawable refuses to allow Dolphin to present faster than the display's refresh rate
  416. // when windowed (or fullscreen with vsync enabled, but that's more understandable).
  417. // On the other hand, it helps Xcode's GPU captures start and stop on frame boundaries
  418. // which is convenient. Put it here as a default-off config, which we can override in Xcode.
  419. // It also seems to improve frame pacing, so enable it by default with vsync
  420. if (g_ActiveConfig.iUsePresentDrawable == TriState::On ||
  421. (g_ActiveConfig.iUsePresentDrawable == TriState::Auto && g_ActiveConfig.bVSyncActive))
  422. [g_state_tracker->GetRenderCmdBuf() presentDrawable:m_drawable];
  423. else
  424. [g_state_tracker->GetRenderCmdBuf()
  425. addScheduledHandler:[drawable = std::move(m_drawable)](id) { [drawable present]; }];
  426. m_backbuffer->UpdateBackbufferTexture(nullptr);
  427. m_drawable = nullptr;
  428. }
  429. g_state_tracker->FlushEncoders();
  430. }
  431. }
  432. void Metal::Gfx::CheckForSurfaceChange()
  433. {
  434. if (!g_presenter->SurfaceChangedTestAndClear())
  435. return;
  436. m_layer = MRCRetain(static_cast<CAMetalLayer*>(g_presenter->GetNewSurfaceHandle()));
  437. SetupSurface();
  438. }
  439. void Metal::Gfx::CheckForSurfaceResize()
  440. {
  441. if (!g_presenter->SurfaceResizedTestAndClear())
  442. return;
  443. SetupSurface();
  444. }
  445. void Metal::Gfx::SetupSurface()
  446. {
  447. auto info = GetSurfaceInfo();
  448. [m_layer setDrawableSize:{static_cast<double>(info.width), static_cast<double>(info.height)}];
  449. TextureConfig cfg(info.width, info.height, 1, 1, 1, info.format, AbstractTextureFlag_RenderTarget,
  450. AbstractTextureType::Texture_2DArray);
  451. m_bb_texture = std::make_unique<Texture>(nullptr, cfg);
  452. m_backbuffer = std::make_unique<Framebuffer>(
  453. m_bb_texture.get(), nullptr, std::vector<AbstractTexture*>{}, info.width, info.height, 1, 1);
  454. if (g_presenter)
  455. g_presenter->SetBackbuffer(info);
  456. }
  457. SurfaceInfo Metal::Gfx::GetSurfaceInfo() const
  458. {
  459. if (!m_layer) // Headless
  460. return {};
  461. CGSize size = [m_layer bounds].size;
  462. const float scale = [m_layer contentsScale];
  463. return {static_cast<u32>(size.width * scale), static_cast<u32>(size.height * scale), scale,
  464. Util::ToAbstract([m_layer pixelFormat])};
  465. }