diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 89c29cf53e..208aec0ee0 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -139,11 +139,11 @@ if (FILAMENT_SUPPORTS_METAL) src/metal/MetalEnums.mm src/metal/MetalExternalImage.mm src/metal/MetalHandles.mm - src/metal/MetalPlatform.mm src/metal/MetalShaderCompiler.mm src/metal/MetalState.mm src/metal/MetalTimerQuery.mm src/metal/MetalUtils.mm + src/metal/PlatformMetal.mm ) set(METAL_CPP_SRCS diff --git a/filament/backend/src/metal/MetalPlatform.h b/filament/backend/include/backend/platforms/PlatformMetal.h similarity index 63% rename from filament/backend/src/metal/MetalPlatform.h rename to filament/backend/include/backend/platforms/PlatformMetal.h index 82730ff6bb..b308935bb9 100644 --- a/filament/backend/src/metal/MetalPlatform.h +++ b/filament/backend/include/backend/platforms/PlatformMetal.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef TNT_FILAMENT_BACKEND_PRIVATE_METALPLATFORM_H -#define TNT_FILAMENT_BACKEND_PRIVATE_METALPLATFORM_H +#ifndef TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_H +#define TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_H #include #include @@ -24,9 +24,12 @@ namespace filament::backend { -class MetalPlatform final : public Platform { +struct PlatformMetalImpl; + +class PlatformMetal final : public Platform { public: - ~MetalPlatform() override; + PlatformMetal(); + ~PlatformMetal() noexcept override; Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) noexcept override; int getOSVersion() const noexcept override { return 0; } @@ -54,11 +57,30 @@ public: */ id createAndEnqueueCommandBuffer() noexcept; -private: - id mCommandQueue = nil; + /** + * The action to take if a Drawable cannot be acquired. + * + * 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. + */ + enum class DrawableFailureBehavior : uint8_t { + /** + * Terminates the application and reports an error message (default). + */ + PANIC, + /* + * Aborts execution of the current frame. The Metal backend will attempt to acquire a new + * drawable at the next frame. + */ + ABORT_FRAME + }; + void setDrawableFailureBehavior(DrawableFailureBehavior behavior) noexcept; + DrawableFailureBehavior getDrawableFailureBehavior() const noexcept; +private: + PlatformMetalImpl* pImpl = nullptr; }; } // namespace filament::backend -#endif // TNT_FILAMENT_BACKEND_PRIVATE_METALPLATFORM_H +#endif // TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_H diff --git a/filament/backend/src/metal/MetalBuffer.h b/filament/backend/src/metal/MetalBuffer.h index ec724a9f0b..b40e6714cd 100644 --- a/filament/backend/src/metal/MetalBuffer.h +++ b/filament/backend/src/metal/MetalBuffer.h @@ -18,9 +18,9 @@ #define TNT_FILAMENT_DRIVER_METALBUFFER_H #include "MetalContext.h" -#include "MetalPlatform.h" #include +#include #include @@ -54,12 +54,12 @@ public: } } - static void setPlatform(MetalPlatform* p) { platform = p; } + static void setPlatform(PlatformMetal* p) { platform = p; } private: typedef std::chrono::steady_clock clock_t; - static MetalPlatform* platform; + static PlatformMetal* platform; std::chrono::time_point mBeginning; const char* mName; @@ -141,7 +141,7 @@ public: assert_invariant(type != Type::NONE); return aliveBuffers[toIndex(type)]; } - static void setPlatform(MetalPlatform* p) { platform = p; } + static void setPlatform(PlatformMetal* p) { platform = p; } private: void swap(TrackedMetalBuffer& other) noexcept { @@ -152,7 +152,7 @@ private: id mBuffer; Type mType = Type::NONE; - static MetalPlatform* platform; + static PlatformMetal* platform; static std::array aliveBuffers; }; diff --git a/filament/backend/src/metal/MetalBuffer.mm b/filament/backend/src/metal/MetalBuffer.mm index 6347ab70c6..13675f2706 100644 --- a/filament/backend/src/metal/MetalBuffer.mm +++ b/filament/backend/src/metal/MetalBuffer.mm @@ -23,8 +23,8 @@ namespace filament { namespace backend { std::array TrackedMetalBuffer::aliveBuffers = { 0 }; -MetalPlatform* TrackedMetalBuffer::platform = nullptr; -MetalPlatform* ScopedAllocationTimer::platform = nullptr; +PlatformMetal* TrackedMetalBuffer::platform = nullptr; +PlatformMetal* ScopedAllocationTimer::platform = nullptr; MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage, size_t size, bool forceGpuBuffer) diff --git a/filament/backend/src/metal/MetalContext.h b/filament/backend/src/metal/MetalContext.h index f85e08545b..7535acd5e7 100644 --- a/filament/backend/src/metal/MetalContext.h +++ b/filament/backend/src/metal/MetalContext.h @@ -128,6 +128,7 @@ struct MetalContext { std::atomic latestCompletedCommandBufferId = 0; id pendingCommandBuffer = nil; id currentRenderPassEncoder = nil; + uint32_t currentFrame = 0; std::atomic memorylessLimitsReached = false; @@ -156,6 +157,7 @@ struct MetalContext { RenderPassFlags currentRenderPassFlags; MetalRenderTarget* currentRenderTarget = nullptr; bool validPipelineBound = false; + bool currentRenderPassAbandoned = false; // State trackers. PipelineStateTracker pipelineState; diff --git a/filament/backend/src/metal/MetalDriver.h b/filament/backend/src/metal/MetalDriver.h index 562bafb4aa..7bea1119bf 100644 --- a/filament/backend/src/metal/MetalDriver.h +++ b/filament/backend/src/metal/MetalDriver.h @@ -37,7 +37,7 @@ namespace filament { namespace backend { -class MetalPlatform; +class PlatformMetal; class MetalBuffer; class MetalProgram; @@ -51,19 +51,19 @@ struct BufferState; #endif class MetalDriver final : public DriverBase { - explicit MetalDriver(MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept; + explicit MetalDriver(PlatformMetal* platform, const Platform::DriverConfig& driverConfig) noexcept; ~MetalDriver() noexcept override; Dispatcher getDispatcher() const noexcept final; public: - static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig); + static Driver* create(PlatformMetal* platform, const Platform::DriverConfig& driverConfig); private: friend class MetalSwapChain; friend struct MetalDescriptorSet; - MetalPlatform& mPlatform; + PlatformMetal& mPlatform; MetalContext* mContext; ShaderModel getShaderModel() const noexcept final; diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index e5b5dc6131..5e14c55b52 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -30,7 +30,7 @@ #include "MetalState.h" #include "MetalTimerQuery.h" -#include "MetalPlatform.h" +#include #include #include @@ -57,7 +57,7 @@ namespace filament { namespace backend { -Driver* MetalDriverFactory::create(MetalPlatform* const platform, const Platform::DriverConfig& driverConfig) { +Driver* MetalDriverFactory::create(PlatformMetal* const platform, const Platform::DriverConfig& driverConfig) { #if 0 // this is useful for development, but too verbose even for debug builds // For reference on a 64-bits machine in Release mode: @@ -96,7 +96,7 @@ Driver* MetalDriverFactory::create(MetalPlatform* const platform, const Platform } UTILS_NOINLINE -Driver* MetalDriver::create(MetalPlatform* const platform, const Platform::DriverConfig& driverConfig) { +Driver* MetalDriver::create(PlatformMetal* const platform, const Platform::DriverConfig& driverConfig) { assert_invariant(platform); size_t defaultSize = FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U; Platform::DriverConfig validConfig {driverConfig}; @@ -109,7 +109,7 @@ Dispatcher MetalDriver::getDispatcher() const noexcept { } MetalDriver::MetalDriver( - MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept + PlatformMetal* platform, const Platform::DriverConfig& driverConfig) noexcept : mPlatform(*platform), mContext(new MetalContext), mHandleAllocator( @@ -246,6 +246,7 @@ void MetalDriver::tick(int) { void MetalDriver::beginFrame(int64_t monotonic_clock_ns, int64_t refreshIntervalNs, uint32_t frameId) { + mContext->currentFrame = frameId; DEBUG_LOG("beginFrame(monotonic_clock_ns = %lld, refreshIntervalNs = %lld, frameId = %d)\n", monotonic_clock_ns, refreshIntervalNs, frameId); #if defined(FILAMENT_METAL_PROFILING) @@ -631,19 +632,19 @@ void MetalDriver::createFenceR(Handle fh, int dummy) { void MetalDriver::createSwapChainR(Handle sch, void* nativeWindow, uint64_t flags) { if (UTILS_UNLIKELY(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER)) { CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) nativeWindow; - construct_handle(sch, *mContext, pixelBuffer, flags); + construct_handle(sch, *mContext, mPlatform, pixelBuffer, flags); // This release matches the retain call in setupExternalImage. The MetalSwapchain will have // retained the buffer by now. CVPixelBufferRelease((CVPixelBufferRef)pixelBuffer); } else { auto* metalLayer = (__bridge CAMetalLayer*) nativeWindow; - construct_handle(sch, *mContext, metalLayer, flags); + construct_handle(sch, *mContext, mPlatform, metalLayer, flags); } } void MetalDriver::createSwapChainHeadlessR(Handle sch, uint32_t width, uint32_t height, uint64_t flags) { - construct_handle(sch, *mContext, width, height, flags); + construct_handle(sch, *mContext, mPlatform, width, height, flags); } void MetalDriver::createTimerQueryR(Handle tqh, int) { @@ -1272,6 +1273,13 @@ void MetalDriver::beginRenderPass(Handle rth, MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor renderPassDescriptor]; renderTarget->setUpRenderPassAttachments(descriptor, params); + if (renderTarget->involvesAbandonedSwapChain()) { + mContext->currentRenderPassAbandoned = true; + return; + } else { + mContext->currentRenderPassAbandoned = false; + } + mContext->currentRenderPassEncoder = [getPendingCommandBuffer(mContext) renderCommandEncoderWithDescriptor:descriptor]; if (!mContext->groupMarkers.empty()) { @@ -1333,6 +1341,10 @@ void MetalDriver::endRenderPass(int dummy) { os_signpost_interval_end(mContext->log, OS_SIGNPOST_ID_EXCLUSIVE, "Render pass"); #endif + if (UTILS_UNLIKELY(mContext->currentRenderPassAbandoned)) { + return; + } + [mContext->currentRenderPassEncoder endEncoding]; // Command encoders are one time use. Set it to nil to release the encoder and ensure we don't @@ -1633,6 +1645,11 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers, auto srcTarget = handle_cast(src); auto dstTarget = handle_cast(dst); + if (UTILS_UNLIKELY(srcTarget->involvesAbandonedSwapChain() || + dstTarget->involvesAbandonedSwapChain())) { + return; + } + FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0) << "blitDEPRECATED only supports COLOR0"; @@ -1917,6 +1934,10 @@ void MetalDriver::bindDescriptorSet( } void MetalDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { + if (UTILS_UNLIKELY(mContext->currentRenderPassAbandoned)) { + return; + } + FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr) << "draw() without a valid command encoder."; DEBUG_LOG("draw2(...)\n"); @@ -1960,6 +1981,9 @@ void MetalDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t inst void MetalDriver::draw(PipelineState ps, Handle rph, uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) { + if (UTILS_UNLIKELY(mContext->currentRenderPassAbandoned)) { + return; + } MetalRenderPrimitive const* const rp = handle_cast(rph); ps.primitiveType = rp->type; ps.vertexBufferInfo = rp->vertexBuffer->vbih; diff --git a/filament/backend/src/metal/MetalDriverFactory.h b/filament/backend/src/metal/MetalDriverFactory.h index bff7439077..083a592e37 100644 --- a/filament/backend/src/metal/MetalDriverFactory.h +++ b/filament/backend/src/metal/MetalDriverFactory.h @@ -21,12 +21,12 @@ namespace filament { namespace backend { -class MetalPlatform; +class PlatformMetal; class Driver; class MetalDriverFactory { public: - static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig); + static Driver* create(PlatformMetal* platform, const Platform::DriverConfig& driverConfig); }; } // namespace backend diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index 973e5d19eb..cc17d950d8 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -30,6 +30,7 @@ #include "MetalState.h" // for MetalState::VertexDescription #include +#include #include #include @@ -51,13 +52,16 @@ class MetalSwapChain : public HwSwapChain { public: // Instantiate a SwapChain from a CAMetalLayer - MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow, uint64_t flags); + MetalSwapChain(MetalContext& context, PlatformMetal& platform, CAMetalLayer* nativeWindow, + uint64_t flags); // Instantiate a SwapChain from a CVPixelBuffer - MetalSwapChain(MetalContext& context, CVPixelBufferRef pixelBuffer, uint64_t flags); + MetalSwapChain(MetalContext& context, PlatformMetal& platform, CVPixelBufferRef pixelBuffer, + uint64_t flags); // Instantiate a headless SwapChain. - MetalSwapChain(MetalContext& context, int32_t width, int32_t height, uint64_t flags); + MetalSwapChain(MetalContext& context, PlatformMetal& platform, int32_t width, int32_t height, + uint64_t flags); ~MetalSwapChain(); @@ -86,6 +90,8 @@ public: bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; } + bool isAbandoned() const; + private: enum class SwapChainType { @@ -103,6 +109,7 @@ private: void ensureDepthStencilTexture(); MetalContext& context; + PlatformMetal& platform; id drawable = nil; id depthStencilTexture = nil; id headlessDrawable = nil; @@ -114,6 +121,8 @@ private: MetalExternalImage externalImage; SwapChainType type; + int64_t abandonedUntilFrame = -1; + // These fields store a callback to notify the client that a frame is ready for presentation. If // !frameScheduled.callback, then the Metal backend automatically calls presentDrawable when the // frame is committed. Otherwise, the Metal backend will not automatically present the frame. @@ -341,6 +350,8 @@ public: void setUpRenderPassAttachments(MTLRenderPassDescriptor* descriptor, const RenderPassParams& params); + bool involvesAbandonedSwapChain() const noexcept; + MTLViewport getViewportFromClientViewport( Viewport rect, float depthRangeNear, float depthRangeFar) { const int32_t height = int32_t(getAttachmentSize().y); diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index fc12fe0a9a..da10317f51 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -69,8 +69,10 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { return MTLTextureUsage(u); } -MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow, uint64_t flags) +MetalSwapChain::MetalSwapChain( + MetalContext& context, PlatformMetal& platform, CAMetalLayer* nativeWindow, uint64_t flags) : context(context), + platform(platform), depthStencilFormat(decideDepthStencilFormat(flags)), layer(nativeWindow), layerDrawableMutex(std::make_shared()), @@ -94,15 +96,19 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow layer.device = context.device; } -MetalSwapChain::MetalSwapChain(MetalContext& context, int32_t width, int32_t height, uint64_t flags) +MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform, int32_t width, + int32_t height, uint64_t flags) : context(context), + platform(platform), depthStencilFormat(decideDepthStencilFormat(flags)), headlessWidth(width), headlessHeight(height), type(SwapChainType::HEADLESS) {} -MetalSwapChain::MetalSwapChain(MetalContext& context, CVPixelBufferRef pixelBuffer, uint64_t flags) +MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform, + CVPixelBufferRef pixelBuffer, uint64_t flags) : context(context), + platform(platform), depthStencilFormat(decideDepthStencilFormat(flags)), externalImage(MetalExternalImage::createFromImage(context, pixelBuffer)), type(SwapChainType::CVPIXELBUFFERREF) { @@ -140,6 +146,10 @@ NSUInteger MetalSwapChain::getSurfaceHeight() const { return (NSUInteger) layer.drawableSize.height; } +bool MetalSwapChain::isAbandoned() const { + return context.currentFrame < abandonedUntilFrame; +} + id MetalSwapChain::acquireDrawable() { if (drawable) { return drawable.texture; @@ -180,7 +190,18 @@ id MetalSwapChain::acquireDrawable() { drawable = [layer nextDrawable]; } - FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable."; + if (UTILS_UNLIKELY(drawable == nil)) { + switch (platform.getDrawableFailureBehavior()) { + case PlatformMetal::DrawableFailureBehavior::PANIC: + FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable."; + break; + + case PlatformMetal::DrawableFailureBehavior::ABORT_FRAME: + abandonedUntilFrame = context.currentFrame + 1; + return nil; + } + } + return drawable.texture; } @@ -1117,12 +1138,20 @@ void MetalRenderTarget::setUpRenderPassAttachments(MTLRenderPassDescriptor* desc } } +bool MetalRenderTarget::involvesAbandonedSwapChain() const noexcept { + const auto* draw = context->currentDrawSwapChain; + const auto* read = context->currentReadSwapChain; + return (draw && draw->isAbandoned()) || (read && read->isAbandoned()); +} + MetalRenderTarget::Attachment MetalRenderTarget::getDrawColorAttachment(size_t index) { assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT); Attachment result = color[index]; if (index == 0 && defaultRenderTarget) { assert_invariant(context->currentDrawSwapChain); result.texture = context->currentDrawSwapChain->acquireDrawable(); + // acquireDrawable may fail to acquire the drawable, in which case result.texture will be + // nil, and an invalid attachment } return result; } @@ -1133,6 +1162,8 @@ MetalRenderTarget::Attachment MetalRenderTarget::getReadColorAttachment(size_t i if (index == 0 && defaultRenderTarget) { assert_invariant(context->currentReadSwapChain); result.texture = context->currentReadSwapChain->acquireDrawable(); + // acquireDrawable may fail to acquire the drawable, in which case result.texture will be + // nil, and an invalid attachment } return result; } diff --git a/filament/backend/src/metal/MetalPlatform.mm b/filament/backend/src/metal/PlatformMetal.mm similarity index 59% rename from filament/backend/src/metal/MetalPlatform.mm rename to filament/backend/src/metal/PlatformMetal.mm index 4979e602ba..e446698b08 100644 --- a/filament/backend/src/metal/MetalPlatform.mm +++ b/filament/backend/src/metal/PlatformMetal.mm @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "MetalPlatform.h" +#include #include "MetalDriverFactory.h" @@ -22,19 +22,32 @@ #import +#include + namespace filament::backend { +struct PlatformMetalImpl { + id mCommandQueue = nil; + // read form driver thread, read/written to from client thread + std::atomic mDrawableFailureBehavior = + PlatformMetal::DrawableFailureBehavior::PANIC; +}; + Platform* createDefaultMetalPlatform() { - return new MetalPlatform(); + return new PlatformMetal(); } -MetalPlatform::~MetalPlatform() = default; +PlatformMetal::PlatformMetal() : pImpl(new PlatformMetalImpl) {} -Driver* MetalPlatform::createDriver(void* /*sharedContext*/, const Platform::DriverConfig& driverConfig) noexcept { +PlatformMetal::~PlatformMetal() noexcept { + delete pImpl; +} + +Driver* PlatformMetal::createDriver(void* /*sharedContext*/, const Platform::DriverConfig& driverConfig) noexcept { return MetalDriverFactory::create(this, driverConfig); } -id MetalPlatform::createDevice() noexcept { +id PlatformMetal::createDevice() noexcept { id result; #if !defined(FILAMENT_IOS) @@ -62,16 +75,24 @@ id MetalPlatform::createDevice() noexcept { return result; } -id MetalPlatform::createCommandQueue(id device) noexcept { - mCommandQueue = [device newCommandQueue]; - mCommandQueue.label = @"Filament"; - return mCommandQueue; +id PlatformMetal::createCommandQueue(id device) noexcept { + pImpl->mCommandQueue = [device newCommandQueue]; + pImpl->mCommandQueue.label = @"Filament"; + return pImpl->mCommandQueue; } -id MetalPlatform::createAndEnqueueCommandBuffer() noexcept { - id commandBuffer = [mCommandQueue commandBuffer]; +id PlatformMetal::createAndEnqueueCommandBuffer() noexcept { + id commandBuffer = [pImpl->mCommandQueue commandBuffer]; [commandBuffer enqueue]; return commandBuffer; } +void PlatformMetal::setDrawableFailureBehavior(DrawableFailureBehavior behavior) noexcept { + pImpl->mDrawableFailureBehavior = behavior; +} + +PlatformMetal::DrawableFailureBehavior PlatformMetal::getDrawableFailureBehavior() const noexcept { + return pImpl->mDrawableFailureBehavior; +} + } // namespace filament