diff --git a/filament/backend/include/backend/platforms/PlatformMetal.h b/filament/backend/include/backend/platforms/PlatformMetal.h index f09bf26f8ed0..48a5f85cd55d 100644 --- a/filament/backend/include/backend/platforms/PlatformMetal.h +++ b/filament/backend/include/backend/platforms/PlatformMetal.h @@ -39,18 +39,40 @@ 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. + * + * initialize() must be called from the main thread. + * + * @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. * * 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( @@ -60,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; @@ -68,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 5fd44b4ec7c5..b1ddc466ca65 100644 --- a/filament/backend/src/metal/PlatformMetal.mm +++ b/filament/backend/src/metal/PlatformMetal.mm @@ -24,14 +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() { @@ -48,7 +56,59 @@ return MetalDriverFactory::create(this, driverConfig); } + +bool PlatformMetal::initialize() noexcept { + std::lock_guard lock(pImpl->mLock); + + MetalDevice device{}; + pImpl->createDeviceImpl(device); + if (device.device == nil) { + return false; + } + + MetalCommandQueue commandQueue{}; + pImpl->createCommandQueueImpl(device, commandQueue); + if (commandQueue.commandQueue == nil) { + return false; + } + + return true; +} + void PlatformMetal::createDevice(MetalDevice& outDevice) noexcept { + 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; + } + id result; #if !defined(FILAMENT_IOS) @@ -74,27 +134,17 @@ << utils::io::endl; outDevice.device = result; + mDevice = result; } -void PlatformMetal::createCommandQueue( - MetalDevice& device, MetalCommandQueue& outCommandQueue) noexcept { - 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; +void PlatformMetalImpl::createCommandQueueImpl(MetalDevice& device, MetalCommandQueue& outCommandQueue) { + if (mCommandQueue) { + outCommandQueue.commandQueue = mCommandQueue; + return; + } + mCommandQueue = [device.device newCommandQueue]; + mCommandQueue.label = @"Filament"; + outCommandQueue.commandQueue = mCommandQueue; } } // namespace filament