diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 30425d3f50..1628f9d06e 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -40,6 +40,7 @@ set(PUBLIC_HDRS include/filament/Skybox.h include/filament/Stream.h include/filament/SwapChain.h + include/filament/Sync.h include/filament/Texture.h include/filament/TextureSampler.h include/filament/ToneMapper.h @@ -103,6 +104,7 @@ set(SRCS src/Skybox.cpp src/Stream.cpp src/SwapChain.cpp + src/Sync.cpp src/Texture.cpp src/ToneMapper.cpp src/TransformManager.cpp @@ -132,6 +134,7 @@ set(SRCS src/details/Skybox.cpp src/details/Stream.cpp src/details/SwapChain.cpp + src/details/Sync.cpp src/details/Texture.cpp src/details/VertexBuffer.cpp src/details/View.cpp diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 46be084c60..9890786229 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -295,6 +295,7 @@ if (FILAMENT_SUPPORTS_WEBGPU) src/webgpu/WebGPUStrings.h src/webgpu/WebGPUSwapChain.cpp src/webgpu/WebGPUSwapChain.h + src/webgpu/WebGPUSync.h src/webgpu/WebGPUTexture.cpp src/webgpu/WebGPUTexture.h src/webgpu/WebGPUTextureHelpers.h diff --git a/filament/backend/include/backend/Handle.h b/filament/backend/include/backend/Handle.h index db20ca0afd..a0658e76e6 100644 --- a/filament/backend/include/backend/Handle.h +++ b/filament/backend/include/backend/Handle.h @@ -38,6 +38,7 @@ struct HwRenderPrimitive; struct HwRenderTarget; struct HwStream; struct HwSwapChain; +struct HwSync; struct HwTexture; struct HwTimerQuery; struct HwVertexBufferInfo; @@ -151,6 +152,7 @@ using RenderPrimitiveHandle = Handle; using RenderTargetHandle = Handle; using StreamHandle = Handle; using SwapChainHandle = Handle; +using SyncHandle = Handle; using TextureHandle = Handle; using TimerQueryHandle = Handle; using VertexBufferHandle = Handle; diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index f6ca680200..71564710dc 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -29,6 +29,7 @@ namespace filament::backend { +class CallbackHandler; class Driver; /** @@ -42,6 +43,9 @@ public: struct SwapChain {}; struct Fence {}; struct Stream {}; + struct Sync {}; + + using SyncCallback = void(*)(Sync* UTILS_NONNULL sync, void* UTILS_NULLABLE userData); class ExternalImageHandle; diff --git a/filament/backend/include/backend/platforms/OpenGLPlatform.h b/filament/backend/include/backend/platforms/OpenGLPlatform.h index 0cc0c1c189..66b9876d3e 100644 --- a/filament/backend/include/backend/platforms/OpenGLPlatform.h +++ b/filament/backend/include/backend/platforms/OpenGLPlatform.h @@ -276,6 +276,26 @@ public: */ virtual backend::FenceStatus waitFence(Fence* UTILS_NONNULL fence, uint64_t timeout) noexcept; + // -------------------------------------------------------------------------------------------- + // Sync support + + /** + * Creates a Sync. These can be used for frame synchronization externally + * (certain platform implementations can be exported to handles that can + * be used in other processes). + * + * @return A Sync object. + */ + virtual Platform::Sync* UTILS_NONNULL createSync() noexcept; + + /** + * Destroys a sync. If called with a sync not created by this platform + * object, this will lead to undefined behavior. + * + * @param sync The sync to destroy, that was created by this platform + * instance. + */ + virtual void destroySync(Platform::Sync* UTILS_NONNULL sync) noexcept; // -------------------------------------------------------------------------------------------- // Streaming support diff --git a/filament/backend/include/backend/platforms/PlatformEGLAndroid.h b/filament/backend/include/backend/platforms/PlatformEGLAndroid.h index a7b5814904..6a9163508c 100644 --- a/filament/backend/include/backend/platforms/PlatformEGLAndroid.h +++ b/filament/backend/include/backend/platforms/PlatformEGLAndroid.h @@ -58,6 +58,17 @@ public: ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(ExternalImageHandle externalImage) noexcept; + /** + * Converts a sync to an external file descriptor, if possible. Accepts an + * opaque handle to a sync, as well as a pointer to where the fd should be + * stored. + * @param sync The sync to be converted to a file descriptor. + * @param fd A pointer to where the file descriptor should be stored. + * @return `true` on success, `false` on failure. The default implementation + * returns `false`. + */ + bool convertSyncToFd(Platform::Sync* sync, int* fd) noexcept; + protected: // -------------------------------------------------------------------------------------------- // Platform Interface @@ -74,6 +85,10 @@ protected: // -------------------------------------------------------------------------------------------- // OpenGLPlatform Interface + struct SyncEGLAndroid : public Platform::Sync { + EGLSyncKHR sync; + }; + void terminate() noexcept override; void beginFrame( @@ -89,9 +104,10 @@ protected: */ void setPresentationTime(int64_t presentationTimeInNanosecond) noexcept override; - Stream* createStream(void* nativeStream) noexcept override; void destroyStream(Stream* stream) noexcept override; + Platform::Sync* createSync() noexcept override; + void destroySync(Platform::Sync* sync) noexcept override; void attach(Stream* stream, intptr_t tname) noexcept override; void detach(Stream* stream) noexcept override; void updateTexImage(Stream* stream, int64_t* timestamp) noexcept override; diff --git a/filament/backend/include/backend/platforms/VulkanPlatform.h b/filament/backend/include/backend/platforms/VulkanPlatform.h index b708d8acf7..163fa07605 100644 --- a/filament/backend/include/backend/platforms/VulkanPlatform.h +++ b/filament/backend/include/backend/platforms/VulkanPlatform.h @@ -17,8 +17,9 @@ #ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORM_H #define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORM_H -#include +#include #include +#include #include @@ -29,9 +30,9 @@ #include #include +#include #include #include -#include #include #include @@ -45,6 +46,10 @@ using SwapChain = Platform::SwapChain; */ struct VulkanPlatformPrivate; +// Forward declare the fence status that will be maintained by the command +// buffer manager. +struct VulkanCmdFence; + /** * A Platform interface that creates a Vulkan backend. */ @@ -229,6 +234,25 @@ public: virtual SwapChainPtr createSwapChain(void* nativeWindow, uint64_t flags = 0, VkExtent2D extent = {0, 0}); + /** + * Creates a Platform::Sync object, which tracks a fence and its status, + * and allows conversion to an external sync. + * @param fence The underlying VkFence to use for synchronization. + * @param fenceStatus An object tracking the fence's state + * @return A Platform::Sync object tracking the provided fence. + */ + virtual Platform::Sync* createSync(VkFence fence, + std::shared_ptr fenceStatus) noexcept; + + /** + * Destroys a sync. If called with a sync not created by this platform + * object, this will lead to undefined behavior. + * + * @param sync The sync to destroy, which was created by this platform + * instance. + */ + virtual void destroySync(Platform::Sync* sync) noexcept; + /** * Allows implementers to provide instance extensions that they'd like to include in the * instance creation. @@ -408,12 +432,19 @@ public: } protected: + struct VulkanSync : public Platform::Sync { + VkFence fence; + std::shared_ptr fenceStatus; + }; + virtual ExtensionSet getSwapchainInstanceExtensions() const; using SurfaceBundle = std::tuple; virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, uint64_t flags) const noexcept; + virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept; + private: // Platform dependent helper methods static ExtensionSet getSwapchainInstanceExtensionsImpl(); diff --git a/filament/backend/include/backend/platforms/VulkanPlatformAndroid.h b/filament/backend/include/backend/platforms/VulkanPlatformAndroid.h index 649158135a..53ce5573d3 100644 --- a/filament/backend/include/backend/platforms/VulkanPlatformAndroid.h +++ b/filament/backend/include/backend/platforms/VulkanPlatformAndroid.h @@ -44,6 +44,17 @@ public: virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const override; + /** + * Converts a sync to an external file descriptor, if possible. Accepts an + * opaque handle to a sync, as well as a pointer to where the fd should be + * stored. + * @param sync The sync to be converted to a file descriptor. + * @param fd A pointer to where the file descriptor should be stored. + * @return `true` on success, `false` on failure. The default implementation + * returns `false`. + */ + bool convertSyncToFd(Platform::Sync* sync, int* fd) const noexcept; + protected: virtual ExtensionSet getSwapchainInstanceExtensions() const override; @@ -51,6 +62,8 @@ protected: virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, uint64_t flags) const noexcept override; + virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override; + private: struct ExternalImageVulkanAndroid : public Platform::ExternalImage { AHardwareBuffer* aHardwareBuffer = nullptr; diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index c60395e599..6c55478c89 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -272,6 +272,8 @@ DECL_DRIVER_API_TAGGED_R_N(backend::RenderTargetHandle, createRenderTarget, DECL_DRIVER_API_TAGGED_R_N(backend::FenceHandle, createFence) +DECL_DRIVER_API_TAGGED_R_N(backend::SyncHandle, createSync) + DECL_DRIVER_API_TAGGED_R_N(backend::SwapChainHandle, createSwapChain, void*, nativeWindow, uint64_t, flags) @@ -328,6 +330,7 @@ DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch) DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh) DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh) DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh) +DECL_DRIVER_API_N(destroySync, backend::SyncHandle, sh) DECL_DRIVER_API_N(destroyDescriptorSetLayout, backend::DescriptorSetLayoutHandle, dslh) DECL_DRIVER_API_N(destroyDescriptorSet, backend::DescriptorSetHandle, dsh) @@ -344,6 +347,8 @@ DECL_DRIVER_API_SYNCHRONOUS_N(void, setStreamDimensions, backend::StreamHandle, DECL_DRIVER_API_SYNCHRONOUS_N(int64_t, getStreamTimestamp, backend::StreamHandle, stream) DECL_DRIVER_API_SYNCHRONOUS_N(void, updateStreams, backend::DriverApi*, driver) DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, getFenceStatus, backend::FenceHandle, fh) +DECL_DRIVER_API_SYNCHRONOUS_N(void, getPlatformSync, backend::SyncHandle, sh, + backend::CallbackHandler*, handler, backend::Platform::SyncCallback, cb, void*, userData) DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isTextureSwizzleSupported) DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatMipmappable, backend::TextureFormat, format) diff --git a/filament/backend/src/DriverBase.h b/filament/backend/src/DriverBase.h index 14a4450dd0..069dedc280 100644 --- a/filament/backend/src/DriverBase.h +++ b/filament/backend/src/DriverBase.h @@ -140,6 +140,10 @@ struct HwFence : public HwBase { Platform::Fence* fence = nullptr; }; +struct HwSync : public HwBase { + Platform::Sync* sync = nullptr; +}; + struct HwSwapChain : public HwBase { Platform::SwapChain* swapChain = nullptr; }; diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index b2e28eaf88..cc65810ae0 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -671,6 +671,12 @@ void MetalDriver::createSwapChainHeadlessR(Handle sch, mHandleAllocator.associateTagToHandle(sch.getId(), std::move(tag)); } +void MetalDriver::createSyncR(Handle sh, utils::CString tag) { + // TODO: Ensure sync is active, and then invoke and clear all pending + // callbacks. + mHandleAllocator.associateTagToHandle(sh.getId(), std::move(tag)); +} + void MetalDriver::createTimerQueryR(Handle tqh, utils::CString tag) { // nothing to do, timer query was constructed in createTimerQueryS mHandleAllocator.associateTagToHandle(tqh.getId(), std::move(tag)); @@ -816,6 +822,10 @@ Handle MetalDriver::createSwapChainHeadlessS() noexcept { return alloc_handle(); } +Handle MetalDriver::createSyncS() noexcept { + return alloc_handle(); +} + Handle MetalDriver::createTimerQueryS() noexcept { // The handle must be constructed here, as a synchronous call to getTimerQueryValue might happen // before createTimerQueryR is executed. @@ -1017,6 +1027,19 @@ FenceStatus MetalDriver::getFenceStatus(Handle fh) { return fence->wait(0); } +void MetalDriver::destroySync(Handle sh) { + if (sh) { + destruct_handle(sh); + } +} + +void MetalDriver::getPlatformSync(Handle sh, CallbackHandler* handler, + Platform::SyncCallback cb, void* userData) { + // TODO: If the sync has been inserted into the command stream, execute + // the callback. Otherwise, enqueue it. +} + + bool MetalDriver::isTextureFormatSupported(TextureFormat format) { return MetalTexture::decidePixelFormat(mContext, format) != MTLPixelFormatInvalid; } diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index 844d1384ae..09b89b79ca 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -437,6 +437,10 @@ private: uint64_t value; }; +// TODO: Provide implementation for MetalSync. +class MetalSync : public HwSync { +}; + struct MetalTimerQuery : public HwTimerQuery { MetalTimerQuery() : status(std::make_shared()) {} diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index 3ccb0e0fda..8034e140e7 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -16,12 +16,11 @@ #include #include +#include #include "noop/NoopDriver.h" #include "CommandStreamDispatcher.h" -#include - #include namespace filament::backend { @@ -116,6 +115,9 @@ void NoopDriver::destroySwapChain(Handle sch) { void NoopDriver::destroyStream(Handle sh) { } +void NoopDriver::destroySync(Handle sh) { +} + void NoopDriver::destroyTimerQuery(Handle tqh) { } @@ -147,6 +149,10 @@ int64_t NoopDriver::getStreamTimestamp(Handle sh) { void NoopDriver::updateStreams(CommandStream* driver) { } +void NoopDriver::getPlatformSync(Handle sh, CallbackHandler* handler, + Platform::SyncCallback cb, void* userData) { +} + void NoopDriver::destroyFence(Handle fh) { } diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index ce6a7a5dd8..58b2adb151 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -149,6 +149,18 @@ using namespace utils; namespace filament::backend { +namespace { + +static CallbackHandler::Callback syncCallbackWrapper = [](void* userData) { + std::unique_ptr cbData( + static_cast(userData)); + // This assumes the sync has not yet been destroyed. If it has, this will be + // undefined behavior. + cbData->cb(cbData->sync, cbData->userData); +}; + +} // namespace + Driver* OpenGLDriverFactory::create( OpenGLPlatform* platform, void* sharedGLContext, @@ -615,6 +627,10 @@ Handle OpenGLDriver::createFenceS() noexcept { return initHandle(); } +Handle OpenGLDriver::createSyncS() noexcept { + return initHandle(); +} + Handle OpenGLDriver::createSwapChainS() noexcept { return initHandle(); } @@ -1807,6 +1823,24 @@ void OpenGLDriver::createFenceR(Handle fh, utils::CString tag) { mHandleAllocator.associateTagToHandle(fh.getId(), std::move(tag)); } +void OpenGLDriver::createSyncR(Handle sh, utils::CString tag) { + DEBUG_MARKER() + + GLSyncFence* s = handle_cast(sh); + { + std::lock_guard guard(s->lock); + s->sync = mPlatform.createSync(); + } + + for (auto& cbData : s->conversionCallbacks) { + cbData->sync = s->sync; + scheduleCallback(cbData->handler, cbData.release(), syncCallbackWrapper); + } + + s->conversionCallbacks.clear(); + mHandleAllocator.associateTagToHandle(sh.getId(), std::move(tag)); +} + void OpenGLDriver::createSwapChainR(Handle sch, void* nativeWindow, uint64_t flags, utils::CString tag) { DEBUG_MARKER() @@ -2080,6 +2114,16 @@ void OpenGLDriver::destroyStream(Handle sh) { } } +void OpenGLDriver::destroySync(Handle sh) { + DEBUG_MARKER() + + if (sh) { + GLSyncFence* s = handle_cast(sh); + mPlatform.destroySync(s->sync); + destruct(sh, s); + } +} + void OpenGLDriver::destroyTimerQuery(Handle tqh) { DEBUG_MARKER() @@ -2268,6 +2312,33 @@ FenceStatus OpenGLDriver::getFenceStatus(Handle fh) { return FenceStatus::ERROR; } +void OpenGLDriver::getPlatformSync(Handle sh, CallbackHandler* handler, + Platform::SyncCallback cb, void* userData) { + if (!sh) { + return; + } + + GLSyncFence* s = handle_cast(sh); + auto cbData = std::make_unique(); + cbData->handler = handler; + cbData->cb = cb; + cbData->userData = userData; + + // If we haven't already set the handle, toss the conversion callback in the + // back of the list. + { + std::lock_guard guard(s->lock); + if (s->sync == nullptr) { + s->conversionCallbacks.push_back(std::move(cbData)); + return; + } + } + + // Otherwise, go ahead and schedule the callback now. + cbData->sync = s->sync; + scheduleCallback(cbData->handler, cbData.release(), syncCallbackWrapper); +} + bool OpenGLDriver::isTextureFormatSupported(TextureFormat format) { const auto& ext = mContext.ext; if (isETC2Compression(format)) { diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index aaa8bbfcf7..d5c519c73e 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -198,6 +198,19 @@ public: std::shared_ptr state{ std::make_shared() }; }; + // Note: named "GLSyncFence" to avoid confusion with the GL handle, + // "GLsync" (lowercase S) + struct GLSyncFence : public HwSync { + struct CallbackData { + CallbackHandler* handler; + Platform::SyncCallback cb; + Platform::Sync* sync; + void* userData; + }; + std::mutex lock; + std::vector> conversionCallbacks; + }; + OpenGLDriver(OpenGLDriver const&) = delete; OpenGLDriver& operator=(OpenGLDriver const&) = delete; diff --git a/filament/backend/src/opengl/OpenGLPlatform.cpp b/filament/backend/src/opengl/OpenGLPlatform.cpp index d440533f70..b47a9fdb24 100644 --- a/filament/backend/src/opengl/OpenGLPlatform.cpp +++ b/filament/backend/src/opengl/OpenGLPlatform.cpp @@ -117,6 +117,15 @@ FenceStatus OpenGLPlatform::waitFence( return FenceStatus::ERROR; } +Platform::Sync* OpenGLPlatform::createSync() noexcept { + return new Platform::Sync(); +} + +void OpenGLPlatform::destroySync(Platform::Sync* sync) noexcept { + // sync must be a Platform::Sync, since it was created by this platform + // object. + delete sync; +} Platform::Stream* OpenGLPlatform::createStream( UTILS_UNUSED void* nativeStream) noexcept { diff --git a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp index bf3089dd7f..5b2b33aa94 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp @@ -83,6 +83,7 @@ UTILS_PRIVATE PFNEGLGETCOMPOSITORTIMINGANDROIDPROC eglGetCompositorTimingANDROID UTILS_PRIVATE PFNEGLGETNEXTFRAMEIDANDROIDPROC eglGetNextFrameIdANDROID = {}; UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC eglGetFrameTimestampSupportedANDROID = {}; UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSANDROIDPROC eglGetFrameTimestampsANDROID = {}; +UTILS_PRIVATE PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = {}; } using namespace glext; @@ -226,6 +227,8 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext, "eglGetFrameTimestampSupportedANDROID"); eglGetFrameTimestampsANDROID = (PFNEGLGETFRAMETIMESTAMPSANDROIDPROC)eglGetProcAddress( "eglGetFrameTimestampsANDROID"); + eglDupNativeFenceFDANDROID = + (PFNEGLDUPNATIVEFENCEFDANDROIDPROC) eglGetProcAddress("eglDupNativeFenceFDANDROID"); } mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid; @@ -395,6 +398,31 @@ void PlatformEGLAndroid::destroyStream(Platform::Stream* stream) noexcept { mExternalStreamManager.release(stream); } +Platform::Sync* PlatformEGLAndroid::createSync() noexcept { + auto sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); + return new SyncEGLAndroid{.sync = sync}; +} + +bool PlatformEGLAndroid::convertSyncToFd(Platform::Sync* sync, int* fd) noexcept { + assert_invariant(sync && fd); + SyncEGLAndroid& eglSync = static_cast(*sync); + *fd = eglDupNativeFenceFDANDROID(mEGLDisplay, eglSync.sync); + // In the case where there was no native FD, -1 is returned. Return false + // to indicate there was an error in this case. + if (*fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + LOG(ERROR) << "Failed to convert sync to fd: " << eglGetError(); + return false; + } + return true; +} + +void PlatformEGLAndroid::destroySync(Platform::Sync* sync) noexcept { + assert_invariant(sync); + SyncEGLAndroid& eglSync = static_cast(*sync); + eglDestroySyncKHR(mEGLDisplay, eglSync.sync); + delete sync; +} + void PlatformEGLAndroid::attach(Stream* stream, intptr_t tname) noexcept { mExternalStreamManager.attach(stream, tname); } diff --git a/filament/backend/src/vulkan/VulkanAsyncHandles.h b/filament/backend/src/vulkan/VulkanAsyncHandles.h index c70fabc0b6..c0603e65e9 100644 --- a/filament/backend/src/vulkan/VulkanAsyncHandles.h +++ b/filament/backend/src/vulkan/VulkanAsyncHandles.h @@ -20,6 +20,7 @@ #include #include "DriverBase.h" +#include "backend/Platform.h" #include "vulkan/memory/Resource.h" @@ -52,6 +53,19 @@ struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource { std::shared_ptr fence; }; +struct VulkanSync : fvkmemory::ThreadSafeResource, public HwSync { + struct CallbackData { + CallbackHandler* handler; + Platform::SyncCallback cb; + Platform::Sync* sync; + void* userData; + }; + + VulkanSync() {} + std::mutex lock; + std::vector> conversionCallbacks; +}; + struct VulkanTimerQuery : public HwTimerQuery, fvkmemory::ThreadSafeResource { VulkanTimerQuery(uint32_t startingIndex, uint32_t stoppingIndex) : mStartingQueryIndex(startingIndex), diff --git a/filament/backend/src/vulkan/VulkanCommands.cpp b/filament/backend/src/vulkan/VulkanCommands.cpp index 3ad44395d6..216ae6e9d5 100644 --- a/filament/backend/src/vulkan/VulkanCommands.cpp +++ b/filament/backend/src/vulkan/VulkanCommands.cpp @@ -103,6 +103,12 @@ VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext const& context, VkDevice vkCreateSemaphore(mDevice, &sci, VKALLOC, &mSubmission); VkFenceCreateInfo fenceCreateInfo{.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + VkExportFenceCreateInfo exportFenceCreateInfo{ + .sType = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO, + .handleTypes = context.getFenceExportFlags() + }; + fenceCreateInfo.pNext = &exportFenceCreateInfo; + vkCreateFence(device, &fenceCreateInfo, VKALLOC, &mFence); } @@ -425,6 +431,9 @@ bool VulkanCommands::flush() { VkSemaphore lastSubmit = mLastSubmit; bool hasFlushed = false; + VkFence flushedFence = VK_NULL_HANDLE; + std::shared_ptr flushedFenceStatus; + // Note that we've ordered it so that the non-protected commands are followed by the protected // commands. This assumes that the protected commands will be that one doing the rendering into // the protected memory (i.e. protected render target). @@ -446,6 +455,8 @@ bool VulkanCommands::flush() { VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT); lastSubmit = VK_NULL_HANDLE; } + flushedFence = pool->getRecording().getVkFence(); + flushedFenceStatus = pool->getRecording().getFenceStatus(); dependency = pool->flush(); hasFlushed = true; } @@ -453,6 +464,8 @@ bool VulkanCommands::flush() { if (hasFlushed) { mInjectedDependency = VK_NULL_HANDLE; mLastSubmit = dependency; + mLastFence = flushedFence; + mLastFenceStatus = flushedFenceStatus; } return true; diff --git a/filament/backend/src/vulkan/VulkanCommands.h b/filament/backend/src/vulkan/VulkanCommands.h index 2ec8b2641d..acfa81f3d5 100644 --- a/filament/backend/src/vulkan/VulkanCommands.h +++ b/filament/backend/src/vulkan/VulkanCommands.h @@ -223,11 +223,19 @@ public: // it from the existing dependency chain. This is especially useful for setting up // vkQueuePresentKHR. VkSemaphore acquireFinishedSignal() { - VkSemaphore ret= mLastSubmit; + VkSemaphore ret = mLastSubmit; mLastSubmit = VK_NULL_HANDLE; return ret; } + VkFence getMostRecentFence() { + return mLastFence; + } + + std::shared_ptr getMostRecentFenceStatus() { + return mLastFenceStatus; + } + // Takes a semaphore that signals when the next flush can occur. Only one injected // semaphore is allowed per flush. Useful after calling vkAcquireNextImageKHR. // waitStage @@ -265,6 +273,9 @@ private: VkSemaphore mInjectedDependency = VK_NULL_HANDLE; VkSemaphore mLastSubmit = VK_NULL_HANDLE; + VkFence mLastFence = VK_NULL_HANDLE; + std::shared_ptr mLastFenceStatus; + VkPipelineStageFlags mInjectedDependencyWaitStage = 0; }; diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h index 08dbcc750c..29880e0fac 100644 --- a/filament/backend/src/vulkan/VulkanContext.h +++ b/filament/backend/src/vulkan/VulkanContext.h @@ -106,6 +106,10 @@ public: return mPhysicalDeviceProperties.properties.vendorID; } + inline VkExternalFenceHandleTypeFlags getFenceExportFlags() const noexcept { + return mFenceExportFlags; + } + inline bool isImageCubeArraySupported() const noexcept { return mPhysicalDeviceFeatures.features.imageCubeArray == VK_TRUE; } @@ -168,6 +172,9 @@ private: // non-conformant vulkan implementation). .imageView2DOn3DImage = VK_TRUE, }; + + VkExternalFenceHandleTypeFlags mFenceExportFlags = {}; + bool mDebugMarkersSupported = false; bool mDebugUtilsSupported = false; bool mLazilyAllocatedMemorySupported = false; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 278b2b6366..9bceec25f3 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -136,6 +136,13 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla } #endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) +static CallbackHandler::Callback syncCallbackWrapper = [](void* userData) { + std::unique_ptr cbData( + static_cast(userData)); + // This assumes the sync has not yet been destroyed. If it has, this will be + // undefined behavior. + cbData->cb(cbData->sync, cbData->userData); +}; }// anonymous namespace @@ -822,6 +829,36 @@ void VulkanDriver::createFenceR(Handle fh, utils::CString tag) { mResourceManager.associateHandle(fh.getId(), std::move(tag)); } +void VulkanDriver::createSyncR(Handle sh, utils::CString tag) { + auto sync = resource_ptr::cast(&mResourceManager, sh); + VkFence fence = VK_NULL_HANDLE; + std::shared_ptr fenceStatus; + if (mCurrentRenderPass.commandBuffer) { + VulkanCommandBuffer* cmdBuff = mCurrentRenderPass.commandBuffer; + fence = cmdBuff->getVkFence(); + fenceStatus = cmdBuff->getFenceStatus(); + // If we're currently recording, flush so that the fence only applies + // to commands already issued. + mCommands.flush(); + } else { + fence = mCommands.getMostRecentFence(); + fenceStatus = mCommands.getMostRecentFenceStatus(); + } + + { + std::lock_guard guard(sync->lock); + sync->sync = mPlatform->createSync(fence, fenceStatus); + } + + for (auto& cbData : sync->conversionCallbacks) { + cbData->sync = sync->sync; + scheduleCallback(cbData->handler, cbData.release(), syncCallbackWrapper); + } + + sync->conversionCallbacks.clear(); + mResourceManager.associateHandle(sh.getId(), std::move(tag)); +} + void VulkanDriver::createSwapChainR(Handle sch, void* nativeWindow, uint64_t flags, utils::CString tag) { FVK_SYSTRACE_SCOPE(); @@ -954,6 +991,13 @@ Handle VulkanDriver::createFenceS() noexcept { return handle; } +Handle VulkanDriver::createSyncS() noexcept { + auto handle = mResourceManager.allocHandle(); + auto sync = resource_ptr::make(&mResourceManager, handle); + sync.inc(); + return handle; +} + Handle VulkanDriver::createSwapChainS() noexcept { return mResourceManager.allocHandle(); } @@ -992,6 +1036,17 @@ void VulkanDriver::destroySwapChain(Handle sch) { void VulkanDriver::destroyStream(Handle sh) { } +void VulkanDriver::destroySync(Handle sh) { + if (!sh) { + return; + } + FVK_SYSTRACE_SCOPE(); + + auto sync = resource_ptr::cast(&mResourceManager, sh); + mPlatform->destroySync(sync->sync); + sync.dec(); +} + void VulkanDriver::destroyTimerQuery(Handle tqh) { if (!tqh) { return; @@ -1067,6 +1122,29 @@ FenceStatus VulkanDriver::getFenceStatus(Handle fh) { return FenceStatus::TIMEOUT_EXPIRED; } +void VulkanDriver::getPlatformSync(Handle sh, CallbackHandler* handler, + Platform::SyncCallback cb, void* userData) { + auto sync = resource_ptr::cast(&mResourceManager, sh); + auto cbData = std::make_unique(); + cbData->handler = handler; + cbData->cb = cb; + cbData->userData = userData; + + // If we haven't already set the handle, toss the conversion callback in the + // back of the list. + { + std::lock_guard guard(sync->lock); + if (sync->sync == nullptr) { + sync->conversionCallbacks.push_back(std::move(cbData)); + return; + } + } + + // Otherwise, go ahead and schedule the callback now. + cbData->sync = sync->sync; + scheduleCallback(cbData->handler, cbData.release(), syncCallbackWrapper); +} + // We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that // the GPU supports the given texture format with non-zero optimal tiling features. bool VulkanDriver::isTextureFormatSupported(TextureFormat format) { diff --git a/filament/backend/src/vulkan/memory/Resource.cpp b/filament/backend/src/vulkan/memory/Resource.cpp index 6c19e633d4..6732309275 100644 --- a/filament/backend/src/vulkan/memory/Resource.cpp +++ b/filament/backend/src/vulkan/memory/Resource.cpp @@ -38,6 +38,7 @@ template ResourceType getTypeEnum() noexcept; template ResourceType getTypeEnum() noexcept; template ResourceType getTypeEnum() noexcept; template ResourceType getTypeEnum() noexcept; +template ResourceType getTypeEnum() noexcept; template ResourceType getTypeEnum() noexcept { @@ -92,6 +93,9 @@ ResourceType getTypeEnum() noexcept { if constexpr (std::is_same_v) { return ResourceType::VULKAN_BUFFER; } + if constexpr (std::is_same_v) { + return ResourceType::SYNC; + } return ResourceType::UNDEFINED_TYPE; } @@ -131,6 +135,8 @@ std::string_view getTypeStr(ResourceType type) { return "Fence"; case ResourceType::VULKAN_BUFFER: return "VulkanBuffer"; + case ResourceType::SYNC: + return "Sync"; case ResourceType::UNDEFINED_TYPE: return ""; } diff --git a/filament/backend/src/vulkan/memory/Resource.h b/filament/backend/src/vulkan/memory/Resource.h index ccb2cc3a77..cc8dbbb58e 100644 --- a/filament/backend/src/vulkan/memory/Resource.h +++ b/filament/backend/src/vulkan/memory/Resource.h @@ -52,7 +52,8 @@ enum class ResourceType : uint8_t { VULKAN_BUFFER = 14, STAGE_SEGMENT = 15, STAGE_IMAGE = 16, - UNDEFINED_TYPE = 17, // Must be the last enum because we use it for iterating over the enums. + SYNC = 17, + UNDEFINED_TYPE = 18, // Must be the last enum because we use it for iterating over the enums. }; template diff --git a/filament/backend/src/vulkan/memory/ResourceManager.cpp b/filament/backend/src/vulkan/memory/ResourceManager.cpp index 227c4d84f9..5cb4a026e7 100644 --- a/filament/backend/src/vulkan/memory/ResourceManager.cpp +++ b/filament/backend/src/vulkan/memory/ResourceManager.cpp @@ -114,6 +114,9 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) { case ResourceType::VULKAN_BUFFER: destruct(Handle(id)); break; + case ResourceType::SYNC: + destruct(Handle(id)); + break; case ResourceType::UNDEFINED_TYPE: break; } diff --git a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp index 19d9c9324a..e6724ad79d 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp @@ -920,6 +920,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext, context.mBlittableDepthStencilFormats = findBlittableDepthStencilFormats(mImpl->mPhysicalDevice); + context.mFenceExportFlags = getFenceExportFlags(); + assert_invariant(context.mDepthStencilFormats.size() > 0); #if FVK_ENABLED(FVK_DEBUG_VALIDATION) @@ -1002,6 +1004,17 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags, return swapchain; } +Platform::Sync* VulkanPlatform::createSync(VkFence fence, + std::shared_ptr fenceStatus) noexcept { + return new VulkanSync{.fence = fence, .fenceStatus = fenceStatus}; +} + +void VulkanPlatform::destroySync(Platform::Sync* sync) noexcept { + // Sync must be a VulkanSync*, since it was created by VulkanPlatform's + // createSync object. + delete sync; +} + VkInstance VulkanPlatform::getInstance() const noexcept { return mImpl->mInstance; } @@ -1038,6 +1051,11 @@ VkQueue VulkanPlatform::getProtectedGraphicsQueue() const noexcept { return mImpl->mProtectedGraphicsQueue; } +VkExternalFenceHandleTypeFlagBits VulkanPlatform::getFenceExportFlags() const noexcept { + // By default, fences should not be exportable. + return static_cast(0); +} + ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const { return getSwapchainInstanceExtensionsImpl(); } diff --git a/filament/backend/src/vulkan/platform/VulkanPlatformAndroid.cpp b/filament/backend/src/vulkan/platform/VulkanPlatformAndroid.cpp index 899891d090..431693bbfc 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatformAndroid.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatformAndroid.cpp @@ -389,6 +389,37 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal( }; } +bool VulkanPlatformAndroid::convertSyncToFd(Platform::Sync* sync, int* fd) const noexcept { + assert_invariant(sync && fd); + + VulkanSync& vulkanSync = static_cast(*sync); + assert_invariant(vulkanSync.fenceStatus); + + if (vulkanSync.fenceStatus->getStatus() == VK_SUCCESS) { + // We've already signaled; return -1 so that operations will proceed + // immediately. Also, signal that fence conversion was successful. + *fd = -1; + return true; + } + + VkFenceGetFdInfoKHR getFdInfo = { + .sType = VK_STRUCTURE_TYPE_FENCE_GET_FD_INFO_KHR, + .fence = vulkanSync.fence, + .handleType = getFenceExportFlags(), + }; + VkResult res = vkGetFenceFdKHR(getDevice(), &getFdInfo, fd); + if (res != VK_SUCCESS) { + LOG(ERROR) << "Failed to convert sync to fd: " << res; + return false; + } + return true; +} + +VkExternalFenceHandleTypeFlagBits VulkanPlatformAndroid::getFenceExportFlags() const noexcept { + // On android, make fences that can be exported to fd's. + return VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT; +} + VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const { return { VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index 3bcd64a45f..be22bd02c5 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -28,6 +28,7 @@ #include "WebGPURenderTarget.h" #include "WebGPUStrings.h" #include "WebGPUSwapChain.h" +#include "WebGPUSync.h" #include "WebGPUTexture.h" #include "WebGPUTextureHelpers.h" #include "WebGPUVertexBuffer.h" @@ -377,6 +378,10 @@ Handle WebGPUDriver::createFenceS() noexcept { return allocAndConstructHandle(); } +Handle WebGPUDriver::createSyncS() noexcept { + return allocHandle(); +} + Handle WebGPUDriver::createTimerQueryS() noexcept { return Handle((Handle::HandleId) mNextFakeHandle++); } @@ -650,6 +655,12 @@ void WebGPUDriver::createFenceR(Handle fenceHandle, utils::CString tag) setDebugTag(fenceHandle.getId(), std::move(tag)); } +void WebGPUDriver::createSyncR(Handle syncHandle, utils::CString tag) { + // TODO: Ensure sync is active, and then invoke and clear all pending + // callbacks. + setDebugTag(syncHandle.getId(), std::move(tag)); +} + void WebGPUDriver::createTimerQueryR(Handle tqh, utils::CString tag) {} void WebGPUDriver::createDescriptorSetLayoutR( @@ -718,6 +729,18 @@ FenceStatus WebGPUDriver::getFenceStatus(Handle fenceHandle) { return fence->getStatus(); } +void WebGPUDriver::destroySync(Handle syncHandle) { + if (syncHandle) { + destructHandle(syncHandle); + } +} + +void WebGPUDriver::getPlatformSync(Handle syncHandle, CallbackHandler* handler, + Platform::SyncCallback cb, void* userData) { + // TODO: If the sync has been inserted into the command stream, execute + // the callback. Otherwise, enqueue it. +} + bool WebGPUDriver::isTextureFormatSupported(const TextureFormat format) { return toWGPUTextureFormat(format) != wgpu::TextureFormat::Undefined; } diff --git a/filament/backend/src/webgpu/WebGPUSync.h b/filament/backend/src/webgpu/WebGPUSync.h new file mode 100644 index 0000000000..23aea29e0a --- /dev/null +++ b/filament/backend/src/webgpu/WebGPUSync.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_WEBGPUSYNC_H +#define TNT_FILAMENT_BACKEND_WEBGPUSYNC_H + +#include "DriverBase.h" + +namespace filament::backend { + +// TODO: provide a more complete implementation of Sync. +class WebGPUSync final : public HwSync { +}; + +} + +#endif // TNT_FILAMENT_BACKEND_WEBGPUSYNC_H diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 63dbcc089e..22c8c6f622 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -63,6 +63,7 @@ class Scene; class Skybox; class Stream; class SwapChain; +class Sync; class Texture; class VertexBuffer; class View; @@ -886,9 +887,19 @@ public: */ Fence* UTILS_NONNULL createFence() noexcept; + /** + * Creates a Sync. + * @param callback A callback that will be invoked when the handle for + * the created sync is set + * + * @return A pointer to the newly created Sync. + */ + Sync* UTILS_NONNULL createSync() noexcept; + bool destroy(const BufferObject* UTILS_NULLABLE p); //!< Destroys a BufferObject object. bool destroy(const VertexBuffer* UTILS_NULLABLE p); //!< Destroys an VertexBuffer object. bool destroy(const Fence* UTILS_NULLABLE p); //!< Destroys a Fence object. + bool destroy(const Sync* UTILS_NULLABLE p); //!< Destroys a Sync object. bool destroy(const IndexBuffer* UTILS_NULLABLE p); //!< Destroys an IndexBuffer object. bool destroy(const SkinningBuffer* UTILS_NULLABLE p); //!< Destroys a SkinningBuffer object. bool destroy(const MorphTargetBuffer* UTILS_NULLABLE p); //!< Destroys a MorphTargetBuffer object. @@ -922,6 +933,8 @@ public: bool isValid(const VertexBuffer* UTILS_NULLABLE p) const; /** Tells whether a Fence object is valid */ bool isValid(const Fence* UTILS_NULLABLE p) const; + /** Tells whether a Sync object is valid */ + bool isValid(const Sync* UTILS_NULLABLE p) const; /** Tells whether an IndexBuffer object is valid */ bool isValid(const IndexBuffer* UTILS_NULLABLE p) const; /** Tells whether a SkinningBuffer object is valid */ diff --git a/filament/include/filament/Sync.h b/filament/include/filament/Sync.h new file mode 100644 index 0000000000..0398ced670 --- /dev/null +++ b/filament/include/filament/Sync.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_SYNC_H +#define TNT_FILAMENT_SYNC_H + +#include + +#include +#include +#include + +namespace filament { + +class UTILS_PUBLIC Sync : public FilamentAPI { +public: + using CallbackHandler = backend::CallbackHandler; + using Callback = backend::Platform::SyncCallback; + + /** + * Fetches a handle to the external, platform-specific representation of + * this sync object. + * + * @param handler A handler for the callback that will receive the handle + * @param callback A callback that will receive the handle when ready + * @param userData Data to be passed to the callback so that the application + * can identify what frame the sync is relevant to. + * @return The external handle for the Sync. This is valid destroy() is + * called on this Sync object. + */ + void getExternalHandle(CallbackHandler* handler, Callback callback, void* userData) noexcept; + +protected: + // prevent heap allocation + ~Sync() = default; +}; + +} // namespace filament + +#endif // TNT_FILAMENT_SYNC_H diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 89a587e783..4579d8f25c 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -30,6 +30,7 @@ #include "details/Skybox.h" #include "details/Stream.h" #include "details/SwapChain.h" +#include "details/Sync.h" #include "details/Texture.h" #include "details/VertexBuffer.h" #include "details/View.h" @@ -132,6 +133,10 @@ SwapChain* Engine::createSwapChain(uint32_t const width, uint32_t const height, return downcast(this)->createSwapChain(width, height, flags); } +Sync* Engine::createSync() noexcept { + return downcast(this)->createSync(); +} + bool Engine::destroy(const BufferObject* p) { return downcast(this)->destroy(downcast(p)); } @@ -204,6 +209,10 @@ bool Engine::destroy(const SwapChain* p) { return downcast(this)->destroy(downcast(p)); } +bool Engine::destroy(const Sync* p) { + return downcast(this)->destroy(downcast(p)); +} + bool Engine::destroy(const InstanceBuffer* p) { return downcast(this)->destroy(downcast(p)); } @@ -221,6 +230,9 @@ bool Engine::isValid(const VertexBuffer* p) const { bool Engine::isValid(const Fence* p) const { return downcast(this)->isValid(downcast(p)); } +bool Engine::isValid(const Sync* p) const { + return downcast(this)->isValid(downcast(p)); +} bool Engine::isValid(const IndexBuffer* p) const { return downcast(this)->isValid(downcast(p)); } diff --git a/filament/src/Sync.cpp b/filament/src/Sync.cpp new file mode 100644 index 0000000000..0c43f0a7a1 --- /dev/null +++ b/filament/src/Sync.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "details/Sync.h" + +#include + +namespace filament { + +void Sync::getExternalHandle(Sync::CallbackHandler* handler, Sync::Callback callback, + void* userData) noexcept { + downcast(this)->getExternalHandle(handler, callback, userData); +} + +} // namespace filament diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 06cb918e37..4f1bb4bbaf 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -34,6 +34,7 @@ #include "details/Skybox.h" #include "details/Stream.h" #include "details/SwapChain.h" +#include "details/Sync.h" #include "details/Texture.h" #include "details/VertexBuffer.h" #include "details/View.h" @@ -1011,6 +1012,15 @@ FSwapChain* FEngine::createSwapChain(uint32_t width, uint32_t height, uint64_t f return p; } +FSync* FEngine::createSync() noexcept { + FSync* p = mHeapAllocator.make(*this); + if (UTILS_LIKELY(p)) { + std::lock_guard const guard(mSyncListLock); + mSyncs.insert(p); + } + return p; +} + /* * Objects created with a component manager */ @@ -1200,6 +1210,11 @@ bool FEngine::destroy(const FSwapChain* p) { return terminateAndDestroy(p, mSwapChains); } +UTILS_NOINLINE +bool FEngine::destroy(const FSync* p) { + return terminateAndDestroyLocked(mSyncListLock, p, mSyncs); +} + UTILS_NOINLINE bool FEngine::destroy(const FStream* p) { return terminateAndDestroy(p, mStreams); @@ -1281,6 +1296,10 @@ bool FEngine::isValid(const FFence* p) const { return isValid(p, mFences); } +bool FEngine::isValid(const FSync* p) const { + return isValid(p, mSyncs); +} + bool FEngine::isValid(const FIndexBuffer* p) const { return isValid(p, mIndexBuffers); } diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 4285c795de..232d9d9baf 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -40,10 +40,11 @@ #include "details/Fence.h" #include "details/IndexBuffer.h" #include "details/InstanceBuffer.h" +#include "details/MorphTargetBuffer.h" #include "details/RenderTarget.h" #include "details/SkinningBuffer.h" -#include "details/MorphTargetBuffer.h" #include "details/Skybox.h" +#include "details/Sync.h" #include "private/backend/CommandBufferQueue.h" #include "private/backend/CommandStream.h" @@ -122,6 +123,7 @@ class FMaterialInstance; class FRenderer; class FScene; class FSwapChain; +class FSync; class FView; class ResourceAllocator; @@ -331,6 +333,7 @@ public: FScene* createScene() noexcept; FView* createView() noexcept; FFence* createFence() noexcept; + FSync* createSync() noexcept; FSwapChain* createSwapChain(void* nativeWindow, uint64_t flags) noexcept; FSwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept; @@ -342,6 +345,7 @@ public: bool destroy(const FBufferObject* p); bool destroy(const FVertexBuffer* p); bool destroy(const FFence* p); + bool destroy(const FSync* p); bool destroy(const FIndexBuffer* p); bool destroy(const FSkinningBuffer* p); bool destroy(const FMorphTargetBuffer* p); @@ -362,6 +366,7 @@ public: bool isValid(const FBufferObject* p) const; bool isValid(const FVertexBuffer* p) const; bool isValid(const FFence* p) const; + bool isValid(const FSync* p) const; bool isValid(const FIndexBuffer* p) const; bool isValid(const FSkinningBuffer* p) const; bool isValid(const FMorphTargetBuffer* p) const; @@ -613,6 +618,11 @@ private: utils::Mutex mFenceListLock; ResourceList mFences{"Fence"}; + // the sync list is accessed from multiple threads, because they are + // synchronization objects. + utils::Mutex mSyncListLock; + ResourceList mSyncs{ "Sync" }; + mutable uint32_t mMaterialId = 0; // FMaterialInstance are handled directly by FMaterial diff --git a/filament/src/details/Sync.cpp b/filament/src/details/Sync.cpp new file mode 100644 index 0000000000..ed9f505cfa --- /dev/null +++ b/filament/src/details/Sync.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "details/Sync.h" + +#include "details/Engine.h" + +#include +#include + +namespace filament { + +using DriverApi = backend::DriverApi; + +FSync::FSync(FEngine& engine) + : mEngine(engine) { + DriverApi& driverApi = engine.getDriverApi(); + mHwSync = driverApi.createSync(); +} + +void FSync::terminate(FEngine& engine) noexcept { + engine.getDriverApi().destroySync(mHwSync); +} + +void FSync::getExternalHandle(Sync::CallbackHandler* handler, Sync::Callback callback, + void* userData) noexcept { + mEngine.getDriverApi().getPlatformSync(mHwSync, handler, callback, userData); +} + +} // namespace filament diff --git a/filament/src/details/Sync.h b/filament/src/details/Sync.h new file mode 100644 index 0000000000..c9f18ffe00 --- /dev/null +++ b/filament/src/details/Sync.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_DETAILS_SYNC_H +#define TNT_FILAMENT_DETAILS_SYNC_H + +#include "downcast.h" + +#include + +#include +#include +#include + +namespace filament { + +class FEngine; + +class FSync : public Sync { +public: + FSync(FEngine& engine); + + void terminate(FEngine& engine) noexcept; + + backend::SyncHandle getHwHandle() const noexcept { return mHwSync; } + + /** + * Fetches a handle to the external, platform-specific representation of + * this sync object. + * + * @param handler A handler for the callback that will receive the handle + * @param callback A callback that will receive the handle when ready + * @param userData Data to be passed to the callback so that the application + * can identify what frame the sync is relevant to. + * @return The external handle for the Sync. This is valid destroy() is + * called on this Sync object. + */ + void getExternalHandle(Sync::CallbackHandler* handler, Sync::Callback callback, + void* userData) noexcept; + +private: + FEngine& mEngine; + backend::SyncHandle mHwSync; +}; + +FILAMENT_DOWNCAST(Sync) + +} // namespace filament + +#endif // TNT_FILAMENT_DETAILS_SYNC_H