From 5e69c54d849c68edfe8c209a30543ef32a6da424 Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Wed, 7 May 2025 13:11:04 -0700 Subject: [PATCH 1/2] Add initialize method to PlatformMetal --- .../include/backend/platforms/PlatformMetal.h | 16 +++++++++++ filament/backend/src/metal/PlatformMetal.mm | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/filament/backend/include/backend/platforms/PlatformMetal.h b/filament/backend/include/backend/platforms/PlatformMetal.h index f09bf26f8ed0..45ebc884e731 100644 --- a/filament/backend/include/backend/platforms/PlatformMetal.h +++ b/filament/backend/include/backend/platforms/PlatformMetal.h @@ -39,6 +39,22 @@ class PlatformMetal final : public Platform { Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) noexcept override; int getOSVersion() const noexcept override { return 0; } + /** + * Optionally initializes the Metal platform by acquiring resources necessary for rendering. + * + * This method attempts to acquire a Metal device and command queue, returning true if both are + * successfully obtained, or false otherwise. Typically, these objects are acquired when + * the Metal backend is initialized. This method allows clients to check for their availability + * earlier. + * + * Calling initialize() is optional and safe to do so multiple times. After initialize() returns + * true, subsequent calls will continue to return true but have no effect. + * + * @returns true if the device and command queue have been successfully obtained; false + * otherwise. + */ + bool initialize() noexcept; + /** * Obtain the preferred Metal device object for the backend to use. * diff --git a/filament/backend/src/metal/PlatformMetal.mm b/filament/backend/src/metal/PlatformMetal.mm index 5fd44b4ec7c5..9c279b59398b 100644 --- a/filament/backend/src/metal/PlatformMetal.mm +++ b/filament/backend/src/metal/PlatformMetal.mm @@ -28,6 +28,7 @@ namespace filament::backend { struct PlatformMetalImpl { + id mDevice = nil; id mCommandQueue = nil; // read form driver thread, read/written to from client thread std::atomic mDrawableFailureBehavior = @@ -48,7 +49,29 @@ return MetalDriverFactory::create(this, driverConfig); } + +bool PlatformMetal::initialize() noexcept { + MetalDevice device{}; + createDevice(device); + if (device.device == nil) { + return false; + } + + MetalCommandQueue commandQueue{}; + createCommandQueue(device, commandQueue); + if (commandQueue.commandQueue == nil) { + return false; + } + + return true; +} + void PlatformMetal::createDevice(MetalDevice& outDevice) noexcept { + if (pImpl->mDevice) { + outDevice.device = pImpl->mDevice; + return; + } + id result; #if !defined(FILAMENT_IOS) @@ -74,10 +97,15 @@ << utils::io::endl; outDevice.device = result; + pImpl->mDevice = result; } void PlatformMetal::createCommandQueue( MetalDevice& device, MetalCommandQueue& outCommandQueue) noexcept { + if (pImpl->mCommandQueue) { + outCommandQueue.commandQueue = pImpl->mCommandQueue; + return; + } pImpl->mCommandQueue = [device.device newCommandQueue]; pImpl->mCommandQueue.label = @"Filament"; outCommandQueue.commandQueue = pImpl->mCommandQueue; From 2c14d104685a854e1dd96b37c396170c55bffcaf Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Tue, 13 May 2025 11:00:54 -0700 Subject: [PATCH 2/2] Ensure thread-safe behavior --- .../include/backend/platforms/PlatformMetal.h | 10 +++ filament/backend/src/metal/PlatformMetal.mm | 74 ++++++++++++------- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/filament/backend/include/backend/platforms/PlatformMetal.h b/filament/backend/include/backend/platforms/PlatformMetal.h index 45ebc884e731..48a5f85cd55d 100644 --- a/filament/backend/include/backend/platforms/PlatformMetal.h +++ b/filament/backend/include/backend/platforms/PlatformMetal.h @@ -50,6 +50,8 @@ class PlatformMetal final : public Platform { * Calling initialize() is optional and safe to do so multiple times. After initialize() returns * true, subsequent calls will continue to return true but have no effect. * + * initialize() must be called from the main thread. + * * @returns true if the device and command queue have been successfully obtained; false * otherwise. */ @@ -61,12 +63,16 @@ class PlatformMetal final : public Platform { * On desktop platforms, there may be multiple GPUs suitable for rendering, and this method is * free to decide which one to use. On mobile systems with a single GPU, implementations should * simply return the result of MTLCreateSystemDefaultDevice(); + * + * createDevice is called by the Metal backend from the backend thread. */ virtual void createDevice(MetalDevice& outDevice) noexcept; /** * Create a command submission queue on the Metal device object. * + * createCommandQueue is called by the Metal backend from the backend thread. + * * @param device The device which was returned from createDevice() */ virtual void createCommandQueue( @@ -76,6 +82,8 @@ class PlatformMetal final : public Platform { * Obtain a MTLCommandBuffer enqueued on this Platform's MTLCommandQueue. The command buffer is * guaranteed to execute before all subsequent command buffers created either by Filament, or * further calls to this method. + * + * createAndEnqueueCommandBuffer must be called from the main thread. */ void createAndEnqueueCommandBuffer(MetalCommandBuffer& outCommandBuffer) noexcept; @@ -84,6 +92,8 @@ class PlatformMetal final : public Platform { * * Each frame rendered requires a CAMetalDrawable texture, which is presented on-screen at the * completion of each frame. These are limited and provided round-robin style by the system. + * + * setDrawableFailureBehavior must be called from the main thread. */ enum class DrawableFailureBehavior : uint8_t { /** diff --git a/filament/backend/src/metal/PlatformMetal.mm b/filament/backend/src/metal/PlatformMetal.mm index 9c279b59398b..b1ddc466ca65 100644 --- a/filament/backend/src/metal/PlatformMetal.mm +++ b/filament/backend/src/metal/PlatformMetal.mm @@ -24,15 +24,22 @@ #import #include +#include namespace filament::backend { struct PlatformMetalImpl { + std::mutex mLock; // locks mDevice and mCommandQueue id mDevice = nil; id mCommandQueue = nil; + // read form driver thread, read/written to from client thread std::atomic mDrawableFailureBehavior = PlatformMetal::DrawableFailureBehavior::PANIC; + + // These methods must be called with mLock held + void createDeviceImpl(MetalDevice& outDevice); + void createCommandQueueImpl(MetalDevice& device, MetalCommandQueue& outCommandQueue); }; Platform* createDefaultMetalPlatform() { @@ -51,14 +58,16 @@ bool PlatformMetal::initialize() noexcept { + std::lock_guard lock(pImpl->mLock); + MetalDevice device{}; - createDevice(device); + pImpl->createDeviceImpl(device); if (device.device == nil) { return false; } MetalCommandQueue commandQueue{}; - createCommandQueue(device, commandQueue); + pImpl->createCommandQueueImpl(device, commandQueue); if (commandQueue.commandQueue == nil) { return false; } @@ -67,8 +76,36 @@ } void PlatformMetal::createDevice(MetalDevice& outDevice) noexcept { - if (pImpl->mDevice) { - outDevice.device = pImpl->mDevice; + std::lock_guard lock(pImpl->mLock); + pImpl->createDeviceImpl(outDevice); +} + +void PlatformMetal::createCommandQueue( + MetalDevice& device, MetalCommandQueue& outCommandQueue) noexcept { + std::lock_guard lock(pImpl->mLock); + pImpl->createCommandQueueImpl(device, outCommandQueue); +} + +void PlatformMetal::createAndEnqueueCommandBuffer(MetalCommandBuffer& outCommandBuffer) noexcept { + std::lock_guard lock(pImpl->mLock); + id commandBuffer = [pImpl->mCommandQueue commandBuffer]; + [commandBuffer enqueue]; + outCommandBuffer.commandBuffer = commandBuffer; +} + +void PlatformMetal::setDrawableFailureBehavior(DrawableFailureBehavior behavior) noexcept { + pImpl->mDrawableFailureBehavior = behavior; +} + +PlatformMetal::DrawableFailureBehavior PlatformMetal::getDrawableFailureBehavior() const noexcept { + return pImpl->mDrawableFailureBehavior; +} + +// ------------------------------------------------------------------------------------------------- + +void PlatformMetalImpl::createDeviceImpl(MetalDevice& outDevice) { + if (mDevice) { + outDevice.device = mDevice; return; } @@ -97,32 +134,17 @@ << utils::io::endl; outDevice.device = result; - pImpl->mDevice = result; + mDevice = result; } -void PlatformMetal::createCommandQueue( - MetalDevice& device, MetalCommandQueue& outCommandQueue) noexcept { - if (pImpl->mCommandQueue) { - outCommandQueue.commandQueue = pImpl->mCommandQueue; +void PlatformMetalImpl::createCommandQueueImpl(MetalDevice& device, MetalCommandQueue& outCommandQueue) { + if (mCommandQueue) { + outCommandQueue.commandQueue = mCommandQueue; return; } - pImpl->mCommandQueue = [device.device newCommandQueue]; - pImpl->mCommandQueue.label = @"Filament"; - outCommandQueue.commandQueue = pImpl->mCommandQueue; -} - -void PlatformMetal::createAndEnqueueCommandBuffer(MetalCommandBuffer& outCommandBuffer) noexcept { - id commandBuffer = [pImpl->mCommandQueue commandBuffer]; - [commandBuffer enqueue]; - outCommandBuffer.commandBuffer = commandBuffer; -} - -void PlatformMetal::setDrawableFailureBehavior(DrawableFailureBehavior behavior) noexcept { - pImpl->mDrawableFailureBehavior = behavior; -} - -PlatformMetal::DrawableFailureBehavior PlatformMetal::getDrawableFailureBehavior() const noexcept { - return pImpl->mDrawableFailureBehavior; + mCommandQueue = [device.device newCommandQueue]; + mCommandQueue.label = @"Filament"; + outCommandQueue.commandQueue = mCommandQueue; } } // namespace filament