diff --git a/renderdoc/core/core.cpp b/renderdoc/core/core.cpp index 3e638b99d..167caaf9b 100644 --- a/renderdoc/core/core.cpp +++ b/renderdoc/core/core.cpp @@ -195,6 +195,7 @@ rdcstr DoStringise(const RDCDriver &el) STRINGISE_ENUM_CLASS(D3D8); STRINGISE_ENUM_CLASS(Image); STRINGISE_ENUM_CLASS(Vulkan); + STRINGISE_ENUM_CLASS(Metal); } END_ENUM_STRINGISE(); } diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index d728933cb..850783b00 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -219,6 +219,7 @@ enum class RDCDriver : uint32_t Vulkan = 8, OpenGLES = 9, D3D8 = 10, + Metal = 11, MaxBuiltin, Custom = 100000, Custom0 = Custom, diff --git a/renderdoc/driver/metal/metal_buffer.cpp b/renderdoc/driver/metal/metal_buffer.cpp index 2b6203159..55738c174 100644 --- a/renderdoc/driver/metal/metal_buffer.cpp +++ b/renderdoc/driver/metal/metal_buffer.cpp @@ -100,8 +100,7 @@ void WrappedMTLBuffer::didModifyRange(NS::Range &range) Serialise_didModifyRange(ser, range); chunk = scope.Get(); } - // TODO: add to the frame capture record chunk - // m_Device->AddFrameCaptureRecordChunk(chunk); + m_Device->AddFrameCaptureRecordChunk(chunk); } } else diff --git a/renderdoc/driver/metal/metal_command_buffer.cpp b/renderdoc/driver/metal/metal_command_buffer.cpp index 488fe7233..097c0ef00 100644 --- a/renderdoc/driver/metal/metal_command_buffer.cpp +++ b/renderdoc/driver/metal/metal_command_buffer.cpp @@ -179,7 +179,7 @@ void WrappedMTLCommandBuffer::presentDrawable(MTL::Drawable *drawable) } MetalResourceRecord *bufferRecord = GetRecord(this); bufferRecord->AddChunk(chunk); - bufferRecord->cmdInfo->flags |= MetalCmdBufferStatus::Presented; + bufferRecord->cmdInfo->presented = true; bufferRecord->cmdInfo->outputLayer = ObjC::Get_Layer(drawable); bufferRecord->cmdInfo->backBuffer = GetWrapped(mtlBackBuffer); } @@ -206,26 +206,17 @@ bool WrappedMTLCommandBuffer::Serialise_commit(SerialiserType &ser) void WrappedMTLCommandBuffer::commit() { - SERIALISE_TIME_CALL(Unwrap(this)->commit()); - if(IsCaptureMode(m_State)) + MTL::CommandBuffer *mtlCommandBuffer = Unwrap(this); + bool isCapture = IsCaptureMode(m_State); + // During capture keep the real resource alive + // It will be released when it is no longer required to be tracked + if(isCapture) + mtlCommandBuffer->retain(); + SERIALISE_TIME_CALL(mtlCommandBuffer->commit()); + if(isCapture) { - Chunk *chunk = NULL; - { - CACHE_THREAD_SERIALISER(); - SCOPED_SERIALISE_CHUNK(MetalChunk::MTLCommandBuffer_commit); - Serialise_commit(ser); - chunk = scope.Get(); - } MetalResourceRecord *bufferRecord = GetRecord(this); - bufferRecord->AddChunk(chunk); - - if(IsActiveCapturing(m_State)) - { - bufferRecord->AddRef(); - bufferRecord->MarkResourceFrameReferenced(GetResID(m_CommandQueue), eFrameRef_Read); - // pull in frame refs from this command buffer - bufferRecord->AddResourceReferences(GetResourceManager()); - } + m_Device->CaptureCmdBufCommit(bufferRecord); } else { @@ -261,6 +252,7 @@ void WrappedMTLCommandBuffer::enqueue() } MetalResourceRecord *bufferRecord = GetRecord(this); bufferRecord->AddChunk(chunk); + m_Device->CaptureCmdBufEnqueue(bufferRecord); } else { diff --git a/renderdoc/driver/metal/metal_core.cpp b/renderdoc/driver/metal/metal_core.cpp index b49fcaa00..4eae8e585 100644 --- a/renderdoc/driver/metal/metal_core.cpp +++ b/renderdoc/driver/metal/metal_core.cpp @@ -23,6 +23,8 @@ ******************************************************************************/ #include "metal_core.h" +#include "serialise/rdcfile.h" +#include "metal_command_buffer.h" #include "metal_device.h" WriteSerialiser &WrappedMTLDevice::GetThreadSerialiser() @@ -53,3 +55,464 @@ WriteSerialiser &WrappedMTLDevice::GetThreadSerialiser() return *ser; } + +void WrappedMTLDevice::WaitForGPU() +{ + MTL::CommandBuffer *mtlCommandBuffer = m_mtlCommandQueue->commandBuffer(); + mtlCommandBuffer->commit(); + mtlCommandBuffer->waitUntilCompleted(); +} + +template +bool WrappedMTLDevice::Serialise_BeginCaptureFrame(SerialiserType &ser) +{ + // TODO: serialise image references and states + + SERIALISE_CHECK_READ_ERRORS(); + + return true; +} + +void WrappedMTLDevice::StartFrameCapture(DeviceOwnedWindow devWnd) +{ + if(!IsBackgroundCapturing(m_State)) + return; + + RDCLOG("Starting capture"); + { + SCOPED_LOCK(m_CaptureCommandBuffersLock); + RDCASSERT(m_CaptureCommandBuffersSubmitted.empty()); + } + + m_CaptureTimer.Restart(); + + GetResourceManager()->ResetCaptureStartTime(); + + m_AppControlledCapture = true; + + FrameDescription frame; + frame.frameNumber = ~0U; + frame.captureTime = Timing::GetUnixTimestamp(); + m_CapturedFrames.push_back(frame); + + GetResourceManager()->ClearReferencedResources(); + // TODO: handle tracked memory + + // need to do all this atomically so that no other commands + // will check to see if they need to mark dirty or + // mark pending dirty and go into the frame record. + { + SCOPED_WRITELOCK(m_CapTransitionLock); + + GetResourceManager()->PrepareInitialContents(); + + RDCDEBUG("Attempting capture"); + m_FrameCaptureRecord->DeleteChunks(); + m_State = CaptureState::ActiveCapturing; + } + + GetResourceManager()->MarkResourceFrameReferenced(GetResID(this), eFrameRef_Read); + + // TODO: are there other resources that need to be marked as frame referenced +} + +void WrappedMTLDevice::EndCaptureFrame() +{ + CACHE_THREAD_SERIALISER(); + ser.SetActionChunk(); + SCOPED_SERIALISE_CHUNK(SystemChunk::CaptureEnd); + + // TODO: serialise the presented image + + m_FrameCaptureRecord->AddChunk(scope.Get()); +} + +bool WrappedMTLDevice::EndFrameCapture(DeviceOwnedWindow devWnd) +{ + if(!IsActiveCapturing(m_State)) + return true; + + // TODO: find the window and drawable being captured + + RDCLOG("Finished capture, Frame %u", m_CapturedFrames.back().frameNumber); + + // TODO: mark the drawable and its images as frame referenced + + // atomically transition to IDLE + { + SCOPED_WRITELOCK(m_CapTransitionLock); + EndCaptureFrame(); + m_State = CaptureState::BackgroundCapturing; + } + + { + SCOPED_LOCK(m_CaptureCommandBuffersLock); + // wait for the GPU to be idle + for(MetalResourceRecord *record : m_CaptureCommandBuffersSubmitted) + { + WrappedMTLCommandBuffer *commandBuffer = (WrappedMTLCommandBuffer *)(record->m_Resource); + Unwrap(commandBuffer)->waitUntilCompleted(); + // Remove the reference on the real resource added during commit() + Unwrap(commandBuffer)->release(); + } + + if(m_CaptureCommandBuffersSubmitted.empty()) + WaitForGPU(); + } + + // TODO: get the backbuffer to generate the thumbnail image + RenderDoc::FramePixels fp; + + RDCFile *rdc = + RenderDoc::Inst().CreateRDC(RDCDriver::Metal, m_CapturedFrames.back().frameNumber, fp); + + StreamWriter *captureWriter = NULL; + + if(rdc) + { + SectionProperties props; + + // Compress with LZ4 so that it's fast + props.flags = SectionFlags::LZ4Compressed; + props.version = m_SectionVersion; + props.type = SectionType::FrameCapture; + + captureWriter = rdc->WriteSection(props); + } + else + { + captureWriter = new StreamWriter(StreamWriter::InvalidStream); + } + + uint64_t captureSectionSize = 0; + + { + WriteSerialiser ser(captureWriter, Ownership::Stream); + + ser.SetChunkMetadataRecording(GetThreadSerialiser().GetChunkMetadataRecording()); + ser.SetUserData(GetResourceManager()); + + { + m_InitParams.Set(Unwrap(this), m_ID); + SCOPED_SERIALISE_CHUNK(SystemChunk::DriverInit, m_InitParams.GetSerialiseSize()); + SERIALISE_ELEMENT(m_InitParams); + } + + RDCDEBUG("Inserting Resource Serialisers"); + GetResourceManager()->InsertReferencedChunks(ser); + GetResourceManager()->InsertInitialContentsChunks(ser); + + RDCDEBUG("Creating Capture Scope"); + GetResourceManager()->Serialise_InitialContentsNeeded(ser); + // TODO: memory references + + // need over estimate of chunk size when writing directly to file + { + SCOPED_SERIALISE_CHUNK(SystemChunk::CaptureScope, 16); + Serialise_CaptureScope(ser); + } + + { + uint64_t maxCaptureBeginChunkSizeInBytes = 16; + SCOPED_SERIALISE_CHUNK(SystemChunk::CaptureBegin, maxCaptureBeginChunkSizeInBytes); + Serialise_BeginCaptureFrame(ser); + } + + // don't need to lock access to m_CaptureCommandBuffersSubmitted as + // no longer in active capture (the transition is thread-protected) + // nothing will be pushed to the vector + + { + std::map recordlist; + size_t countCmdBuffers = m_CaptureCommandBuffersSubmitted.size(); + // ensure all command buffer records within the frame even if recorded before + // serialised order must be preserved + for(MetalResourceRecord *record : m_CaptureCommandBuffersSubmitted) + { + size_t prevSize = recordlist.size(); + (void)prevSize; + record->Insert(recordlist); + } + + size_t prevSize = recordlist.size(); + (void)prevSize; + m_FrameCaptureRecord->Insert(recordlist); + RDCDEBUG("Adding %zu/%zu frame capture chunks to file serialiser", + recordlist.size() - prevSize, recordlist.size()); + + float num = float(recordlist.size()); + float idx = 0.0f; + + for(auto it = recordlist.begin(); it != recordlist.end(); ++it) + { + RenderDoc::Inst().SetProgress(CaptureProgress::SerialiseFrameContents, idx / num); + idx += 1.0f; + it->second->Write(ser); + } + } + captureSectionSize = captureWriter->GetOffset(); + } + + RDCLOG("Captured Metal frame with %f MB capture section in %f seconds", + double(captureSectionSize) / (1024.0 * 1024.0), m_CaptureTimer.GetMilliseconds() / 1000.0); + + RenderDoc::Inst().FinishCaptureWriting(rdc, m_CapturedFrames.back().frameNumber); + + // delete tracked cmd buffers - had to keep them alive until after serialiser flush. + CaptureClearSubmittedCmdBuffers(); + + GetResourceManager()->ResetLastWriteTimes(); + GetResourceManager()->MarkUnwrittenResources(); + + // TODO: handle memory resources in the resource manager + + GetResourceManager()->ClearReferencedResources(); + GetResourceManager()->FreeInitialContents(); + + // TODO: handle memory resources in the initial contents + + return true; +} + +bool WrappedMTLDevice::DiscardFrameCapture(DeviceOwnedWindow devWnd) +{ + if(!IsActiveCapturing(m_State)) + return true; + + RDCLOG("Discarding frame capture."); + + RenderDoc::Inst().FinishCaptureWriting(NULL, m_CapturedFrames.back().frameNumber); + + m_CapturedFrames.pop_back(); + + // atomically transition to IDLE + { + SCOPED_WRITELOCK(m_CapTransitionLock); + m_State = CaptureState::BackgroundCapturing; + } + + CaptureClearSubmittedCmdBuffers(); + + GetResourceManager()->MarkUnwrittenResources(); + + // TODO: handle memory resources in the resource manager + + GetResourceManager()->ClearReferencedResources(); + GetResourceManager()->FreeInitialContents(); + + // TODO: handle memory resources in the initial contents + + return true; +} + +template +bool WrappedMTLDevice::Serialise_CaptureScope(SerialiserType &ser) +{ + SERIALISE_ELEMENT_LOCAL(frameNumber, m_CapturedFrames.back().frameNumber); + + SERIALISE_CHECK_READ_ERRORS(); + + if(IsReplayingAndReading()) + { + // TODO: implement RD MTL replay + } + return true; +} + +void WrappedMTLDevice::CaptureCmdBufSubmit(MetalResourceRecord *record) +{ + RDCASSERTEQUAL(record->cmdInfo->status, MetalCmdBufferStatus::Submitted); + RDCASSERT(IsCaptureMode(m_State)); + WrappedMTLCommandBuffer *commandBuffer = (WrappedMTLCommandBuffer *)(record->m_Resource); + if(IsActiveCapturing(m_State)) + { + Chunk *chunk = NULL; + std::unordered_set refIDs; + // The record will get deleted at the end of active frame capture + record->AddRef(); + record->AddReferencedIDs(refIDs); + // snapshot/detect any CPU modifications to the contents + // of referenced MTLBuffer with shared storage mode + for(auto it = refIDs.begin(); it != refIDs.end(); ++it) + { + ResourceId id = *it; + MetalResourceRecord *refRecord = GetResourceManager()->GetResourceRecord(id); + if(refRecord->m_Type == eResBuffer) + { + // TODO: capture CPU modified buffers + } + } + record->MarkResourceFrameReferenced(GetResID(commandBuffer->GetCommandQueue()), eFrameRef_Read); + // pull in frame refs from this command buffer + record->AddResourceReferences(GetResourceManager()); + { + CACHE_THREAD_SERIALISER(); + SCOPED_SERIALISE_CHUNK(MetalChunk::MTLCommandBuffer_commit); + commandBuffer->Serialise_commit(ser); + chunk = scope.Get(); + } + record->AddChunk(chunk); + m_CaptureCommandBuffersSubmitted.push_back(record); + } + else + { + // Remove the reference on the real resource added during commit() + Unwrap(commandBuffer)->release(); + } + if(record->cmdInfo->presented) + { + AdvanceFrame(); + Present(record); + } + // In background or active capture mode the record reference is incremented in + // CaptureCmdBufEnqueue + record->Delete(GetResourceManager()); +} + +void WrappedMTLDevice::CaptureCmdBufCommit(MetalResourceRecord *record) +{ + SCOPED_LOCK(m_CaptureCommandBuffersLock); + if(record->cmdInfo->status != MetalCmdBufferStatus::Enqueued) + CaptureCmdBufEnqueue(record); + + RDCASSERTEQUAL(record->cmdInfo->status, MetalCmdBufferStatus::Enqueued); + record->cmdInfo->status = MetalCmdBufferStatus::Committed; + + size_t countSubmitted = 0; + for(MetalResourceRecord *record : m_CaptureCommandBuffersEnqueued) + { + if(record->cmdInfo->status == MetalCmdBufferStatus::Committed) + { + record->cmdInfo->status = MetalCmdBufferStatus::Submitted; + ++countSubmitted; + CaptureCmdBufSubmit(record); + continue; + } + break; + }; + m_CaptureCommandBuffersEnqueued.erase(0, countSubmitted); +} + +void WrappedMTLDevice::CaptureCmdBufEnqueue(MetalResourceRecord *record) +{ + SCOPED_LOCK(m_CaptureCommandBuffersLock); + RDCASSERTEQUAL(record->cmdInfo->status, MetalCmdBufferStatus::Unknown); + record->cmdInfo->status = MetalCmdBufferStatus::Enqueued; + record->AddRef(); + m_CaptureCommandBuffersEnqueued.push_back(record); + + RDCDEBUG("Enqueing CommandBufferRecord %s %d", ToStr(record->GetResourceID()).c_str(), + m_CaptureCommandBuffersEnqueued.count()); +} + +void WrappedMTLDevice::AdvanceFrame() +{ + if(IsBackgroundCapturing(m_State)) + RenderDoc::Inst().Tick(); + + m_FrameCounter++; // first present becomes frame #1, this function is at the end of the frame +} + +void WrappedMTLDevice::FirstFrame() +{ + // if we have to capture the first frame, begin capturing immediately + if(IsBackgroundCapturing(m_State) && RenderDoc::Inst().ShouldTriggerCapture(0)) + { + RenderDoc::Inst().StartFrameCapture(DeviceOwnedWindow(this, NULL)); + + m_AppControlledCapture = false; + m_CapturedFrames.back().frameNumber = 0; + } +} + +void WrappedMTLDevice::Present(MetalResourceRecord *record) +{ + WrappedMTLTexture *backBuffer = record->cmdInfo->backBuffer; + { + SCOPED_LOCK(m_CapturePotentialBackBuffersLock); + if(m_CapturePotentialBackBuffers.count(backBuffer) == 0) + { + RDCERR("Capture ignoring Present called on unknown backbuffer"); + return; + } + } + + CA::MetalLayer *outputLayer = record->cmdInfo->outputLayer; + DeviceOwnedWindow devWnd(this, outputLayer); + + bool activeWindow = RenderDoc::Inst().IsActiveWindow(devWnd); + + RenderDoc::Inst().AddActiveDriver(RDCDriver::Metal, true); + + if(!activeWindow) + return; + + if(IsActiveCapturing(m_State) && !m_AppControlledCapture) + RenderDoc::Inst().EndFrameCapture(devWnd); + + if(RenderDoc::Inst().ShouldTriggerCapture(m_FrameCounter) && IsBackgroundCapturing(m_State)) + { + RenderDoc::Inst().StartFrameCapture(devWnd); + + m_AppControlledCapture = false; + m_CapturedFrames.back().frameNumber = m_FrameCounter; + } +} + +void WrappedMTLDevice::CaptureClearSubmittedCmdBuffers() +{ + SCOPED_LOCK(m_CaptureCommandBuffersLock); + for(MetalResourceRecord *record : m_CaptureCommandBuffersSubmitted) + { + record->Delete(GetResourceManager()); + } + + m_CaptureCommandBuffersSubmitted.clear(); +} + +void WrappedMTLDevice::RegisterMetalLayer(CA::MetalLayer *mtlLayer) +{ + SCOPED_LOCK(m_CaptureOutputLayersLock); + if(m_CaptureOutputLayers.count(mtlLayer) == 0) + { + m_CaptureOutputLayers.insert(mtlLayer); + TrackedCAMetalLayer::Track(mtlLayer, this); + + DeviceOwnedWindow devWnd(this, mtlLayer); + RenderDoc::Inst().AddFrameCapturer(devWnd, &m_Capturer); + } +} + +void WrappedMTLDevice::UnregisterMetalLayer(CA::MetalLayer *mtlLayer) +{ + SCOPED_LOCK(m_CaptureOutputLayersLock); + RDCASSERT(m_CaptureOutputLayers.count(mtlLayer)); + m_CaptureOutputLayers.erase(mtlLayer); + + DeviceOwnedWindow devWnd(this, mtlLayer); + RenderDoc::Inst().RemoveFrameCapturer(devWnd); +} + +MetalInitParams::MetalInitParams() +{ + memset(this, 0, sizeof(MetalInitParams)); +} + +uint64_t MetalInitParams::GetSerialiseSize() +{ + size_t ret = sizeof(*this); + return (uint64_t)ret; +} + +void MetalInitParams::Set(MTL::Device *pRealDevice, ResourceId device) +{ + DeviceID = device; +} + +template +void DoSerialise(SerialiserType &ser, MetalInitParams &el) +{ + SERIALISE_MEMBER(DeviceID).TypedAs("MTLDevice"_lit); +} + +INSTANTIATE_SERIALISE_TYPE(MetalInitParams); diff --git a/renderdoc/driver/metal/metal_core.h b/renderdoc/driver/metal/metal_core.h index 53dc3a68d..750cdcf0f 100644 --- a/renderdoc/driver/metal/metal_core.h +++ b/renderdoc/driver/metal/metal_core.h @@ -28,8 +28,16 @@ struct MetalInitParams { + MetalInitParams(); + void Set(MTL::Device *pDevice, ResourceId device); + + // update this when adding/removing members + uint64_t GetSerialiseSize(); + // check if a frame capture section version is supported static const uint64_t CurrentVersion = 0x1; + + ResourceId DeviceID; }; DECLARE_REFLECTION_STRUCT(MetalInitParams); diff --git a/renderdoc/driver/metal/metal_device.cpp b/renderdoc/driver/metal/metal_device.cpp index 551b116b0..f48e887bb 100644 --- a/renderdoc/driver/metal/metal_device.cpp +++ b/renderdoc/driver/metal/metal_device.cpp @@ -33,7 +33,7 @@ #include "metal_texture.h" WrappedMTLDevice::WrappedMTLDevice(MTL::Device *realMTLDevice, ResourceId objId) - : WrappedMTLObject(realMTLDevice, objId, this, GetStateRef()) + : WrappedMTLObject(realMTLDevice, objId, this, GetStateRef()), m_Capturer(*this) { AllocateObjCBridge(this); m_Device = this; @@ -46,21 +46,53 @@ WrappedMTLDevice::WrappedMTLDevice(MTL::Device *realMTLDevice, ResourceId objId) m_State = CaptureState::BackgroundCapturing; } + m_SectionVersion = MetalInitParams::CurrentVersion; + threadSerialiserTLSSlot = Threading::AllocateTLSSlot(); m_ResourceManager = new MetalResourceManager(m_State, this); + + if(!RenderDoc::Inst().IsReplayApp()) + { + m_FrameCaptureRecord = GetResourceManager()->AddResourceRecord(ResourceIDGen::GetNewUniqueID()); + m_FrameCaptureRecord->DataInSerialiser = false; + m_FrameCaptureRecord->Length = 0; + m_FrameCaptureRecord->InternalResource = true; + } + else + { + m_FrameCaptureRecord = NULL; + + ResourceIDGen::SetReplayResourceIDs(); + } + RDCASSERT(m_Device == this); GetResourceManager()->AddCurrentResource(objId, this); -} -WrappedMTLDevice *WrappedMTLDevice::MTLCreateSystemDefaultDevice(MTL::Device *realMTLDevice) -{ - MTLFixupForMetalDriverAssert(); - MTLHookObjcMethods(); - ResourceId objId = ResourceIDGen::GetNewUniqueID(); - WrappedMTLDevice *wrappedMTLDevice = new WrappedMTLDevice(realMTLDevice, objId); + if(IsCaptureMode(m_State)) + { + Chunk *chunk = NULL; - return wrappedMTLDevice; + { + CACHE_THREAD_SERIALISER(); + + SCOPED_SERIALISE_CHUNK(MetalChunk::MTLCreateSystemDefaultDevice); + Serialise_MTLCreateSystemDefaultDevice(ser); + chunk = scope.Get(); + } + + MetalResourceRecord *record = GetResourceManager()->AddResourceRecord(this); + record->AddChunk(chunk); + } + else + { + // TODO: implement RD MTL replay + } + + RenderDoc::Inst().AddDeviceFrameCapturer(this, &m_Capturer); + + m_mtlCommandQueue = Unwrap(this)->newCommandQueue(); + FirstFrame(); } IMP WrappedMTLDevice::g_real_CAMetalLayer_nextDrawable; @@ -68,6 +100,11 @@ uint64_t WrappedMTLDevice::g_nextDrawableTLSSlot; MTL::Drawable *hooked_CAMetalLayer_nextDrawable(id self, SEL _cmd) { + CA::MetalLayer *mtlLayer = (CA::MetalLayer *)self; + MTL::Device *mtlDevice = mtlLayer->device(); + RDCASSERT(object_getClass(mtlDevice) == objc_getClass("ObjCBridgeMTLDevice")); + GetWrapped(mtlDevice)->RegisterMetalLayer(mtlLayer); + RDCASSERTEQUAL(Threading::GetTLSValue(WrappedMTLDevice::g_nextDrawableTLSSlot), 0); Threading::SetTLSValue(WrappedMTLDevice::g_nextDrawableTLSSlot, (void *)(uintptr_t) true); MTL::Drawable *drawable = @@ -108,6 +145,30 @@ void WrappedMTLDevice::MTLFixupForMetalDriverAssert() // Serialised MTLDevice APIs +template +bool WrappedMTLDevice::Serialise_MTLCreateSystemDefaultDevice(SerialiserType &ser) +{ + SERIALISE_ELEMENT_LOCAL(Device, GetResID(this)).TypedAs("MTLDevice"_lit); + + SERIALISE_CHECK_READ_ERRORS(); + + if(IsReplayingAndReading()) + { + // TODO: implement RD MTL replay + } + return true; +} + +WrappedMTLDevice *WrappedMTLDevice::MTLCreateSystemDefaultDevice(MTL::Device *realMTLDevice) +{ + MTLFixupForMetalDriverAssert(); + MTLHookObjcMethods(); + ResourceId objId = ResourceIDGen::GetNewUniqueID(); + WrappedMTLDevice *wrappedMTLDevice = new WrappedMTLDevice(realMTLDevice, objId); + + return wrappedMTLDevice; +} + template bool WrappedMTLDevice::Serialise_newCommandQueue(SerialiserType &ser, WrappedMTLCommandQueue *queue) { @@ -539,8 +600,8 @@ WrappedMTLTexture *WrappedMTLDevice::Common_NewTexture(RDMTL::TextureDescriptor if(IsCaptureMode(m_State)) { { - SCOPED_LOCK(m_PotentialBackBuffersLock); - m_PotentialBackBuffers.insert(wrappedMTLTexture); + SCOPED_LOCK(m_CapturePotentialBackBuffersLock); + m_CapturePotentialBackBuffers.insert(wrappedMTLTexture); } } } @@ -578,6 +639,7 @@ WrappedMTLBuffer *WrappedMTLDevice::Common_NewBuffer(bool withBytes, const void return wrappedMTLBuffer; } +INSTANTIATE_FUNCTION_SERIALISED(WrappedMTLDevice, bool, MTLCreateSystemDefaultDevice); INSTANTIATE_FUNCTION_WITH_RETURN_SERIALISED(WrappedMTLDevice, WrappedMTLCommandQueue *, newCommandQueue); INSTANTIATE_FUNCTION_WITH_RETURN_SERIALISED(WrappedMTLDevice, WrappedMTLLibrary *, newDefaultLibrary); diff --git a/renderdoc/driver/metal/metal_device.h b/renderdoc/driver/metal/metal_device.h index 66036a657..84e25bfab 100644 --- a/renderdoc/driver/metal/metal_device.h +++ b/renderdoc/driver/metal/metal_device.h @@ -25,15 +25,33 @@ #pragma once #include "metal_common.h" +#include "metal_core.h" #include "metal_manager.h" +class WrappedMTLDevice; + +class MetalCapturer : public IFrameCapturer +{ +public: + MetalCapturer(WrappedMTLDevice &device) : m_Device(device) {} + // IFrameCapturer interface + RDCDriver GetFrameCaptureDriver() { return RDCDriver::Metal; } + void StartFrameCapture(DeviceOwnedWindow devWnd); + bool EndFrameCapture(DeviceOwnedWindow devWnd); + bool DiscardFrameCapture(DeviceOwnedWindow devWnd); + // IFrameCapturer interface + +private: + WrappedMTLDevice &m_Device; +}; + class WrappedMTLDevice : public WrappedMTLObject { - friend class MetalResourceManager; - public: WrappedMTLDevice(MTL::Device *realMTLDevice, ResourceId objId); ~WrappedMTLDevice() {} + template + bool Serialise_MTLCreateSystemDefaultDevice(SerialiserType &ser); static WrappedMTLDevice *MTLCreateSystemDefaultDevice(MTL::Device *realMTLDevice); // Serialised MTLDevice APIs @@ -85,8 +103,33 @@ public: CaptureState &GetStateRef() { return m_State; } CaptureState GetState() { return m_State; } MetalResourceManager *GetResourceManager() { return m_ResourceManager; }; + void WaitForGPU(); WriteSerialiser &GetThreadSerialiser(); + // IFrameCapturer interface + RDCDriver GetFrameCaptureDriver() { return RDCDriver::Metal; } + void StartFrameCapture(DeviceOwnedWindow devWnd); + bool EndFrameCapture(DeviceOwnedWindow devWnd); + bool DiscardFrameCapture(DeviceOwnedWindow devWnd); + // IFrameCapturer interface + + void CaptureCmdBufCommit(MetalResourceRecord *record); + void CaptureCmdBufEnqueue(MetalResourceRecord *record); + + void AddFrameCaptureRecordChunk(Chunk *chunk) { m_FrameCaptureRecord->AddChunk(chunk); } + // From ResourceManager interface + bool Prepare_InitialState(WrappedMTLObject *res); + uint64_t GetSize_InitialState(ResourceId id, const MetalInitialContents &initial); + template + bool Serialise_InitialState(SerialiserType &ser, ResourceId id, MetalResourceRecord *record, + const MetalInitialContents *initial); + void Create_InitialState(ResourceId id, WrappedMTLObject *live, bool hasData); + void Apply_InitialState(WrappedMTLObject *live, const MetalInitialContents &initial); + // From ResourceManager interface + + void RegisterMetalLayer(CA::MetalLayer *mtlLayer); + void UnregisterMetalLayer(CA::MetalLayer *mtlLayer); + enum { TypeEnum = eResDevice @@ -98,13 +141,18 @@ public: private: static void MTLFixupForMetalDriverAssert(); static void MTLHookObjcMethods(); - bool Prepare_InitialState(WrappedMTLObject *res); - uint64_t GetSize_InitialState(ResourceId id, const MetalInitialContents &initial); + void FirstFrame(); + void AdvanceFrame(); + void Present(MetalResourceRecord *record); + + void CaptureClearSubmittedCmdBuffers(); + void CaptureCmdBufSubmit(MetalResourceRecord *record); + void EndCaptureFrame(); + template - bool Serialise_InitialState(SerialiserType &ser, ResourceId id, MetalResourceRecord *record, - const MetalInitialContents *initial); - void Create_InitialState(ResourceId id, WrappedMTLObject *live, bool hasData); - void Apply_InitialState(WrappedMTLObject *live, const MetalInitialContents &initial); + bool Serialise_CaptureScope(SerialiserType &ser); + template + bool Serialise_BeginCaptureFrame(SerialiserType &ser); WrappedMTLTexture *Common_NewTexture(RDMTL::TextureDescriptor &descriptor, MetalChunk chunkType, bool ioSurfaceTexture, IOSurfaceRef iosurface, @@ -112,15 +160,50 @@ private: WrappedMTLBuffer *Common_NewBuffer(bool withBytes, const void *pointer, NS::UInteger length, MTL::ResourceOptions options); - MetalResourceManager *m_ResourceManager; + MetalResourceManager *m_ResourceManager = NULL; // Back buffer and swap chain emulation - Threading::CriticalSection m_PotentialBackBuffersLock; - std::unordered_set m_PotentialBackBuffers; + Threading::CriticalSection m_CapturePotentialBackBuffersLock; + std::unordered_set m_CapturePotentialBackBuffers; + Threading::CriticalSection m_CaptureOutputLayersLock; + std::unordered_set m_CaptureOutputLayers; CaptureState m_State; + bool m_AppControlledCapture = false; uint64_t threadSerialiserTLSSlot; Threading::CriticalSection m_ThreadSerialisersLock; rdcarray m_ThreadSerialisers; + uint64_t m_SectionVersion = 0; + + MetalCapturer m_Capturer; + uint32_t m_FrameCounter = 0; + rdcarray m_CapturedFrames; + Threading::RWLock m_CapTransitionLock; + MetalResourceRecord *m_FrameCaptureRecord = NULL; + + // record the command buffer records to insert them individually + // (even if they were recorded locklessly in parallel) + // queue submit order will enforce/display ordering, record order is not important + Threading::CriticalSection m_CaptureCommandBuffersLock; + rdcarray m_CaptureCommandBuffersEnqueued; + rdcarray m_CaptureCommandBuffersSubmitted; + + PerformanceTimer m_CaptureTimer; + MetalInitParams m_InitParams; + + MTL::CommandQueue *m_mtlCommandQueue = NULL; }; + +inline void MetalCapturer::StartFrameCapture(DeviceOwnedWindow devWnd) +{ + return m_Device.StartFrameCapture(devWnd); +} +inline bool MetalCapturer::EndFrameCapture(DeviceOwnedWindow devWnd) +{ + return m_Device.EndFrameCapture(devWnd); +} +inline bool MetalCapturer::DiscardFrameCapture(DeviceOwnedWindow devWnd) +{ + return m_Device.DiscardFrameCapture(devWnd); +} diff --git a/renderdoc/driver/metal/metal_helpers_bridge.mm b/renderdoc/driver/metal/metal_helpers_bridge.mm index b2b9fa909..396cf299e 100644 --- a/renderdoc/driver/metal/metal_helpers_bridge.mm +++ b/renderdoc/driver/metal/metal_helpers_bridge.mm @@ -104,3 +104,19 @@ CA::MetalDrawable *ObjC::CAMetalLayer_nextDrawable(void *layerHandle) CA::MetalDrawable *drawable = (__bridge CA::MetalDrawable *)[metalLayer nextDrawable]; return drawable; } + +@interface ObjCTrackedCAMetalLayer : NSObject +@end + +@implementation ObjCTrackedCAMetalLayer + +// Silence compiler warning +// error: method possibly missing a [super dealloc] call [-Werror,-Wobjc-missing-super-calls] +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" +- (void)dealloc +{ + ((TrackedCAMetalLayer *)self)->StopTracking(); +} +#pragma clang diagnostic pop +@end diff --git a/renderdoc/driver/metal/metal_init_state.cpp b/renderdoc/driver/metal/metal_init_state.cpp index 0e2d27f0f..a7b6b8e8f 100644 --- a/renderdoc/driver/metal/metal_init_state.cpp +++ b/renderdoc/driver/metal/metal_init_state.cpp @@ -62,9 +62,5 @@ void WrappedMTLDevice::Apply_InitialState(WrappedMTLObject *live, const MetalIni METAL_NOT_IMPLEMENTED(); } -template bool WrappedMTLDevice::Serialise_InitialState(ReadSerialiser &ser, ResourceId id, - MetalResourceRecord *record, - const MetalInitialContents *initial); -template bool WrappedMTLDevice::Serialise_InitialState(WriteSerialiser &ser, ResourceId id, - MetalResourceRecord *record, - const MetalInitialContents *initial); +INSTANTIATE_FUNCTION_SERIALISED(WrappedMTLDevice, void, InitialState, ResourceId id, + MetalResourceRecord *record, const MetalInitialContents *initial); diff --git a/renderdoc/driver/metal/metal_resources.h b/renderdoc/driver/metal/metal_resources.h index c2b206b46..ffea08a3b 100644 --- a/renderdoc/driver/metal/metal_resources.h +++ b/renderdoc/driver/metal/metal_resources.h @@ -122,17 +122,14 @@ inline MTL::Resource *Unwrap(WrappedMTLResource *obj) return Unwrap((WrappedMTLObject *)obj); } -enum class MetalCmdBufferStatus : uint32_t +enum class MetalCmdBufferStatus : uint8_t { - NoFlags = 0, - Enqueued = 1 << 0, - Committed = 1 << 1, - Submitted = 1 << 2, - Presented = 1 << 3, + Unknown, + Enqueued, + Committed, + Submitted, }; -BITMASK_OPERATORS(MetalCmdBufferStatus); - struct MetalCmdBufferRecordingInfo { MetalCmdBufferRecordingInfo(WrappedMTLCommandQueue *parentQueue) : queue(parentQueue) {} @@ -147,7 +144,8 @@ struct MetalCmdBufferRecordingInfo CA::MetalLayer *outputLayer = NULL; // The texture to present WrappedMTLTexture *backBuffer = NULL; - MetalCmdBufferStatus flags = MetalCmdBufferStatus::NoFlags; + MetalCmdBufferStatus status = MetalCmdBufferStatus::Unknown; + bool presented = false; }; struct MetalResourceRecord : public ResourceRecord diff --git a/renderdoc/driver/metal/metal_stringise.cpp b/renderdoc/driver/metal/metal_stringise.cpp index 9190a206b..0d66fdddb 100644 --- a/renderdoc/driver/metal/metal_stringise.cpp +++ b/renderdoc/driver/metal/metal_stringise.cpp @@ -1149,13 +1149,12 @@ rdcstr DoStringise(const MetalResourceType &el) template <> rdcstr DoStringise(const MetalCmdBufferStatus &el) { - BEGIN_BITFIELD_STRINGISE(MetalCmdBufferStatus) + BEGIN_ENUM_STRINGISE(MetalCmdBufferStatus) { - STRINGISE_BITFIELD_CLASS_VALUE(NoFlags); - STRINGISE_BITFIELD_CLASS_BIT(Enqueued); - STRINGISE_BITFIELD_CLASS_BIT(Committed); - STRINGISE_BITFIELD_CLASS_BIT(Submitted); - STRINGISE_BITFIELD_CLASS_BIT(Presented); + STRINGISE_ENUM_CLASS(Unknown); + STRINGISE_ENUM_CLASS(Enqueued); + STRINGISE_ENUM_CLASS(Committed); + STRINGISE_ENUM_CLASS(Submitted); } - END_BITFIELD_STRINGISE() + END_ENUM_STRINGISE() } diff --git a/renderdoc/driver/metal/metal_types.cpp b/renderdoc/driver/metal/metal_types.cpp index 888fb60ba..555aadbc6 100644 --- a/renderdoc/driver/metal/metal_types.cpp +++ b/renderdoc/driver/metal/metal_types.cpp @@ -75,6 +75,36 @@ RDCCOMPILE_ASSERT(sizeof(NS::UInteger) == sizeof(std::uintptr_t), METALCPP_WRAPPED_PROTOCOLS(DEFINE_OBJC_HELPERS) #undef DEFINE_OBJC_HELPERS +TrackedCAMetalLayer::TrackedCAMetalLayer(CA::MetalLayer *mtlLayer, WrappedMTLDevice *device) +{ + m_mtlLayer = mtlLayer; + m_Device = device; + + RDCCOMPILE_ASSERT((offsetof(TrackedCAMetalLayer, m_ObjcBridge) == 0), + "m_ObjcBridge must be at offsetof 0"); + const char *const className = "ObjCTrackedCAMetalLayer"; + static Class klass = objc_lookUpClass(className); + static size_t classSize = class_getInstanceSize(klass); + if(classSize != sizeof(m_ObjcBridge)) + { + RDCFATAL("'%s' classSize != sizeof(m_ObjcBridge) %lu != %lu", className, classSize, + sizeof(m_ObjcBridge)); + } + id objc = objc_constructInstance(klass, &m_ObjcBridge); + if(objc != (id)&m_ObjcBridge) + { + RDCFATAL("'%s' objc != m_ObjcBridge %p != %p", className, objc, &m_ObjcBridge); + } + objc_setAssociatedObject((id)m_mtlLayer, objc, objc, OBJC_ASSOCIATION_RETAIN); + ((NS::Object *)objc)->release(); +} + +void TrackedCAMetalLayer::StopTracking() +{ + m_Device->UnregisterMetalLayer(m_mtlLayer); + delete this; +} + namespace RDMTL { static bool ValidData(MTL::VertexAttributeDescriptor *attribute) diff --git a/renderdoc/driver/metal/metal_types.h b/renderdoc/driver/metal/metal_types.h index 718abcbf4..b81f920b5 100644 --- a/renderdoc/driver/metal/metal_types.h +++ b/renderdoc/driver/metal/metal_types.h @@ -91,6 +91,26 @@ METALCPP_WRAPPED_PROTOCOLS(DECLARE_OBJC_HELPERS) METALCPP_UNIMPLEMENTED_WRAPPED_PROTOCOLS(DECLARE_UNIMPLEMENTED_WRAPPED_CPP_HELPERS) #undef DECLARE_UNIMPLEMENTED_WRAPPED_CPP_HELPERS +class TrackedCAMetalLayer +{ +public: + TrackedCAMetalLayer() = delete; + ~TrackedCAMetalLayer() = default; + + static void Track(CA::MetalLayer *mtlLayer, WrappedMTLDevice *device) + { + new TrackedCAMetalLayer(mtlLayer, device); + } + void StopTracking(); + +private: + TrackedCAMetalLayer(CA::MetalLayer *real, WrappedMTLDevice *device); + + void *m_ObjcBridge = NULL; + WrappedMTLDevice *m_Device = NULL; + CA::MetalLayer *m_mtlLayer = NULL; +}; + #define MTL_DECLARE_REFLECTION_TYPE(TYPE) \ template <> \ inline rdcliteral TypeName() \ diff --git a/renderdoccmd/renderdoccmd_apple.cpp b/renderdoccmd/renderdoccmd_apple.cpp index 0ba3d8add..ec28d4967 100644 --- a/renderdoccmd/renderdoccmd_apple.cpp +++ b/renderdoccmd/renderdoccmd_apple.cpp @@ -113,6 +113,11 @@ int main(int argc, char *argv[]) count++; #endif +#if defined(RENDERDOC_SUPPORT_METAL) + support += "Metal, "; + count++; +#endif + if(count == 0) { support += "None.";