Skip to content

Commit

Permalink
Metal: call frame scheduled callback on driver thread (#7414)
Browse files Browse the repository at this point in the history
  • Loading branch information
bejado authored Dec 14, 2023
1 parent c8aae3c commit 56a0364
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 8 deletions.
13 changes: 12 additions & 1 deletion filament/backend/src/metal/MetalDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
#include <utils/Log.h>
#include <utils/debug.h>

#include <functional>
#include <mutex>
#include <vector>

namespace filament {
namespace backend {

Expand All @@ -52,20 +56,27 @@ class MetalDriver final : public DriverBase {

public:
static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig);
void runAtNextTick(const std::function<void()>& fn) noexcept;

private:

friend class MetalSwapChain;

MetalPlatform& mPlatform;

MetalContext* mContext;

ShaderModel getShaderModel() const noexcept final;

// Overrides the default implementation by wrapping the call to fn in an @autoreleasepool block.
void execute(std::function<void(void)> const& fn) noexcept final;

/*
* Tasks run regularly on the driver thread.
*/
void executeTickOps() noexcept;
std::vector<std::function<void()>> mTickOps;
std::mutex mTickOpsLock;

/*
* Driver interface
*/
Expand Down
18 changes: 18 additions & 0 deletions filament/backend/src/metal/MetalDriver.mm
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
}

void MetalDriver::tick(int) {
executeTickOps();
}

void MetalDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
Expand Down Expand Up @@ -583,6 +584,8 @@
// This must be done before calling bufferPool->reset() to ensure no buffers are in flight.
finish();

executeTickOps();

mContext->bufferPool->reset();
mContext->commandQueue = nil;

Expand Down Expand Up @@ -1904,6 +1907,21 @@
void MetalDriver::resetState(int) {
}

void MetalDriver::runAtNextTick(const std::function<void()>& fn) noexcept {
std::lock_guard<std::mutex> const lock(mTickOpsLock);
mTickOps.push_back(fn);
}

void MetalDriver::executeTickOps() noexcept {
std::vector<std::function<void()>> ops;
mTickOpsLock.lock();
std::swap(ops, mTickOps);
mTickOpsLock.unlock();
for (const auto& f : ops) {
f();
}
}

// explicit instantiation of the Dispatcher
template class ConcreteDispatcher<MetalDriver>;

Expand Down
33 changes: 26 additions & 7 deletions filament/backend/src/metal/MetalHandles.mm
Original file line number Diff line number Diff line change
Expand Up @@ -245,13 +245,30 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) {
}
}

struct PresentDrawableData {
void* drawable = nullptr;
MetalDriver* driver = nullptr;
};

void presentDrawable(bool presentFrame, void* user) {
// CFBridgingRelease here is used to balance the CFBridgingRetain inside of acquireDrawable.
id<CAMetalDrawable> drawable = (id<CAMetalDrawable>) CFBridgingRelease(user);
auto* presentDrawableData = static_cast<PresentDrawableData*>(user);

// CFBridgingRelease here is used to balance the CFBridgingRetain inside acquireDrawable.
id<CAMetalDrawable> drawable =
(id<CAMetalDrawable>)CFBridgingRelease(presentDrawableData->drawable);
if (presentFrame) {
[drawable present];
}
// The drawable will be released here when the "drawable" variable goes out of scope.

// Schedule the drawable destruction on the driver thread.
void* voidDrawable = (void*) CFBridgingRetain(drawable);
MetalDriver* driver = presentDrawableData->driver;
driver->runAtNextTick([voidDrawable]() {
// The drawable is released here.
CFBridgingRelease(voidDrawable);
});

delete presentDrawableData;
}

void MetalSwapChain::scheduleFrameScheduledCallback() {
Expand All @@ -266,13 +283,15 @@ void presentDrawable(bool presentFrame, void* user) {
// capture the _this_ pointer (MetalSwapChain*) instead of the drawable.
id<CAMetalDrawable> d = drawable;
void* userData = frameScheduledUserData;
MetalDriver* driver = context.driver;
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
// CFBridgingRetain is used here to give the drawable a +1 retain count before
// casting it to a void*.
PresentCallable callable(presentDrawable, (void*) CFBridgingRetain(d));
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
callback(callable, userData);
});
auto* presentDrawableData = new PresentDrawableData;
presentDrawableData->drawable = (void*) CFBridgingRetain(d);
presentDrawableData->driver = driver;
PresentCallable callable(presentDrawable, (void*) presentDrawableData);
callback(callable, userData);
}];
}

Expand Down
6 changes: 6 additions & 0 deletions filament/include/filament/SwapChain.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI {
* automatically schedule itself for presentation. Instead, the application must call the
* PresentCallable passed to the FrameScheduledCallback.
*
* If your application delays the call to the PresentCallable by, for example, calling it on a
* separate thread, you must ensure all PresentCallables have been called before shutting down
* the Filament Engine. You can do this by issuing an Engine::flushAndWait before calling
* Engine::shutdown. This is necessary to ensure the Filament Engine has had a chance to clean
* up all memory related to frame presentation.
*
* @param callback A callback, or nullptr to unset.
* @param user An optional pointer to user data passed to the callback function.
*
Expand Down

0 comments on commit 56a0364

Please sign in to comment.