diff --git a/include/mbgl/mtl/mtl_fwd.hpp b/include/mbgl/mtl/mtl_fwd.hpp index 9f31d01c8..9a56a34f4 100644 --- a/include/mbgl/mtl/mtl_fwd.hpp +++ b/include/mbgl/mtl/mtl_fwd.hpp @@ -19,6 +19,7 @@ namespace MTL { class BlitCommandEncoder; class BlitPassDescriptor; class Buffer; +class CaptureScope; class CommandBuffer; class CommandQueue; class Device; @@ -43,6 +44,7 @@ using CAMetalDrawablePtr = NS::SharedPtr; using MTLBlitCommandEncoderPtr = NS::SharedPtr; using MTLBlitPassDescriptorPtr = NS::SharedPtr; using MTLBufferPtr = NS::SharedPtr; +using MTLCaptureScopePtr = NS::SharedPtr; using MTLCommandBufferPtr = NS::SharedPtr; using MTLCommandQueuePtr = NS::SharedPtr; using MTLDevicePtr = NS::SharedPtr; diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index e54245f88..f74cf2e6e 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -25,7 +25,16 @@ #include #endif // MLN_DRAWABLE_RENDERER -#if !MLN_RENDER_BACKEND_METAL +#if MLN_RENDER_BACKEND_METAL +#include +#include +#include +/// Enable programmatic Metal frame captures for specific frame numbers. +/// Requries iOS 13 +constexpr auto EnableMetalCapture = 0; +constexpr auto CaptureFrameStart = 0; // frames are 0-based +constexpr auto CaptureFrameCount = 1; +#else // !MLN_RENDER_BACKEND_METAL #include #if MLN_DRAWABLE_RENDERER #include @@ -64,6 +73,67 @@ void Renderer::Impl::setObserver(RendererObserver* observer_) { void Renderer::Impl::render(const RenderTree& renderTree, [[maybe_unused]] const std::shared_ptr& updateParameters) { auto& context = backend.getContext(); +#if MLN_RENDER_BACKEND_METAL + if constexpr (EnableMetalCapture) { + const auto& mtlBackend = static_cast(backend); + const auto& mtlDevice = mtlBackend.getDevice(); + + if (!commandCaptureScope) { + if (const auto& cmdQueue = mtlBackend.getCommandQueue()) { + if (const auto captureManager = NS::RetainPtr(MTL::CaptureManager::sharedCaptureManager())) { + if ((commandCaptureScope = NS::TransferPtr(captureManager->newCaptureScope(cmdQueue.get())))) { + const auto label = "Renderer::Impl frame=" + util::toString(frameCount); + commandCaptureScope->setLabel(NS::String::string(label.c_str(), NS::UTF8StringEncoding)); + captureManager->setDefaultCaptureScope(commandCaptureScope.get()); + } + } + } + } + + // "When you capture a frame programmatically, you can capture Metal commands that span multiple + // frames by using a custom capture scope. For example, by calling begin() at the start of frame + // 1 and end() after frame 3, the trace will contain command data from all the buffers that were + // committed in the three frames." + // https://developer.apple.com/documentation/metal/debugging_tools/capturing_gpu_command_data_programmatically + if constexpr (0 < CaptureFrameStart && 0 < CaptureFrameCount) { + if (commandCaptureScope) { + const auto captureManager = NS::RetainPtr(MTL::CaptureManager::sharedCaptureManager()); + if (frameCount == CaptureFrameStart) { + constexpr auto captureDest = MTL::CaptureDestination::CaptureDestinationDeveloperTools; + if (captureManager && !captureManager->isCapturing() && + captureManager->supportsDestination(captureDest)) { + if (auto captureDesc = NS::TransferPtr(MTL::CaptureDescriptor::alloc()->init())) { + captureDesc->setCaptureObject(mtlDevice.get()); + captureDesc->setDestination(captureDest); + NS::Error* errorPtr = nullptr; + if (captureManager->startCapture(captureDesc.get(), &errorPtr)) { + Log::Warning(Event::Render, "Capture Started"); + } else { + std::string errStr = ""; + if (auto error = NS::TransferPtr(errorPtr)) { + if (auto str = error->localizedDescription()) { + if (auto cstr = str->utf8String()) { + errStr = cstr; + } + } + } + Log::Warning(Event::Render, "Capture Failed: " + errStr); + } + } + } + } + } + } + if (commandCaptureScope) { + commandCaptureScope->beginScope(); + + const auto captureManager = NS::RetainPtr(MTL::CaptureManager::sharedCaptureManager()); + if (captureManager->isCapturing()) { + Log::Info(Event::Render, "Capturing frame " + util::toString(frameCount)); + } + } + } +#endif // MLN_RENDER_BACKEND_METAL // Blocks execution until the renderable is available. backend.getDefaultRenderable().wait(); @@ -402,6 +472,19 @@ void Renderer::Impl::render(const RenderTree& renderTree, // CommandEncoder destructor submits render commands. parameters.encoder.reset(); +#if MLN_RENDER_BACKEND_METAL + if constexpr (EnableMetalCapture) { + if (commandCaptureScope) { + commandCaptureScope->endScope(); + + const auto captureManager = NS::RetainPtr(MTL::CaptureManager::sharedCaptureManager()); + if (frameCount == CaptureFrameStart + CaptureFrameCount - 1 && captureManager->isCapturing()) { + captureManager->stopCapture(); + } + } + } +#endif // MLN_RENDER_BACKEND_METAL + const auto encodingTime = renderTree.getElapsedTime() - renderingTime; observer->onDidFinishRenderingFrame( diff --git a/src/mbgl/renderer/renderer_impl.hpp b/src/mbgl/renderer/renderer_impl.hpp index c869773df..e67c0cc52 100644 --- a/src/mbgl/renderer/renderer_impl.hpp +++ b/src/mbgl/renderer/renderer_impl.hpp @@ -2,6 +2,11 @@ #include +#if MLN_RENDER_BACKEND_METAL +#include +#include +#endif // MLN_RENDER_BACKEND_METAL + #include #include @@ -49,6 +54,10 @@ private: RenderState renderState = RenderState::Never; uint64_t frameCount = 0; + +#if MLN_RENDER_BACKEND_METAL + mtl::MTLCaptureScopePtr commandCaptureScope; +#endif // MLN_RENDER_BACKEND_METAL }; } // namespace mbgl