Skip to content

Add initialize method to PlatformMetal #8708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions filament/backend/include/backend/platforms/PlatformMetal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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;

Expand All @@ -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 {
/**
Expand Down
88 changes: 69 additions & 19 deletions filament/backend/src/metal/PlatformMetal.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,22 @@
#import <Foundation/Foundation.h>

#include <atomic>
#include <mutex>

namespace filament::backend {

struct PlatformMetalImpl {
std::mutex mLock; // locks mDevice and mCommandQueue
id<MTLDevice> mDevice = nil;
id<MTLCommandQueue> mCommandQueue = nil;

// read form driver thread, read/written to from client thread
std::atomic<PlatformMetal::DrawableFailureBehavior> mDrawableFailureBehavior =
PlatformMetal::DrawableFailureBehavior::PANIC;

// These methods must be called with mLock held
void createDeviceImpl(MetalDevice& outDevice);
void createCommandQueueImpl(MetalDevice& device, MetalCommandQueue& outCommandQueue);
};

Platform* createDefaultMetalPlatform() {
Expand All @@ -48,7 +56,59 @@
return MetalDriverFactory::create(this, driverConfig);
}


bool PlatformMetal::initialize() noexcept {
std::lock_guard<std::mutex> 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<std::mutex> lock(pImpl->mLock);
pImpl->createDeviceImpl(outDevice);
}

void PlatformMetal::createCommandQueue(
MetalDevice& device, MetalCommandQueue& outCommandQueue) noexcept {
std::lock_guard<std::mutex> lock(pImpl->mLock);
pImpl->createCommandQueueImpl(device, outCommandQueue);
}

void PlatformMetal::createAndEnqueueCommandBuffer(MetalCommandBuffer& outCommandBuffer) noexcept {
std::lock_guard<std::mutex> lock(pImpl->mLock);
id<MTLCommandBuffer> 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<MTLDevice> result;

#if !defined(FILAMENT_IOS)
Expand All @@ -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<MTLCommandBuffer> 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
Loading