Metal: add config to abandon frame if drawable cannot be acquired (#8397)

This commit is contained in:
Ben Doherty
2025-01-31 13:31:07 -08:00
committed by GitHub
parent 67710723d7
commit 9a2ca8ddbb
11 changed files with 157 additions and 46 deletions
+1 -1
View File
@@ -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
@@ -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 <backend/DriverEnums.h>
#include <backend/Platform.h>
@@ -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<MTLCommandBuffer> createAndEnqueueCommandBuffer() noexcept;
private:
id<MTLCommandQueue> 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
+5 -5
View File
@@ -18,9 +18,9 @@
#define TNT_FILAMENT_DRIVER_METALBUFFER_H
#include "MetalContext.h"
#include "MetalPlatform.h"
#include <backend/DriverEnums.h>
#include <backend/platforms/PlatformMetal.h>
#include <Metal/Metal.h>
@@ -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<clock_t> 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<MTLBuffer> mBuffer;
Type mType = Type::NONE;
static MetalPlatform* platform;
static PlatformMetal* platform;
static std::array<uint64_t, TypeCount> aliveBuffers;
};
+2 -2
View File
@@ -23,8 +23,8 @@ namespace filament {
namespace backend {
std::array<uint64_t, TrackedMetalBuffer::TypeCount> 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)
@@ -128,6 +128,7 @@ struct MetalContext {
std::atomic<uint64_t> latestCompletedCommandBufferId = 0;
id<MTLCommandBuffer> pendingCommandBuffer = nil;
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
uint32_t currentFrame = 0;
std::atomic<bool> memorylessLimitsReached = false;
@@ -156,6 +157,7 @@ struct MetalContext {
RenderPassFlags currentRenderPassFlags;
MetalRenderTarget* currentRenderTarget = nullptr;
bool validPipelineBound = false;
bool currentRenderPassAbandoned = false;
// State trackers.
PipelineStateTracker pipelineState;
+4 -4
View File
@@ -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;
+31 -7
View File
@@ -30,7 +30,7 @@
#include "MetalState.h"
#include "MetalTimerQuery.h"
#include "MetalPlatform.h"
#include <backend/platforms/PlatformMetal.h>
#include <CoreVideo/CVMetalTexture.h>
#include <CoreVideo/CVPixelBuffer.h>
@@ -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<HwFence> fh, int dummy) {
void MetalDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
if (UTILS_UNLIKELY(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER)) {
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) nativeWindow;
construct_handle<MetalSwapChain>(sch, *mContext, pixelBuffer, flags);
construct_handle<MetalSwapChain>(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<MetalSwapChain>(sch, *mContext, metalLayer, flags);
construct_handle<MetalSwapChain>(sch, *mContext, mPlatform, metalLayer, flags);
}
}
void MetalDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch,
uint32_t width, uint32_t height, uint64_t flags) {
construct_handle<MetalSwapChain>(sch, *mContext, width, height, flags);
construct_handle<MetalSwapChain>(sch, *mContext, mPlatform, width, height, flags);
}
void MetalDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
@@ -1272,6 +1273,13 @@ void MetalDriver::beginRenderPass(Handle<HwRenderTarget> 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<MetalRenderTarget>(src);
auto dstTarget = handle_cast<MetalRenderTarget>(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<HwRenderPrimitive> 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<MetalRenderPrimitive>(rph);
ps.primitiveType = rp->type;
ps.vertexBufferInfo = rp->vertexBuffer->vbih;
@@ -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
+14 -3
View File
@@ -30,6 +30,7 @@
#include "MetalState.h" // for MetalState::VertexDescription
#include <backend/DriverEnums.h>
#include <backend/platforms/PlatformMetal.h>
#include <utils/bitset.h>
#include <utils/CString.h>
@@ -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<CAMetalDrawable> drawable = nil;
id<MTLTexture> depthStencilTexture = nil;
id<MTLTexture> 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);
+35 -4
View File
@@ -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<std::mutex>()),
@@ -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<MTLTexture> MetalSwapChain::acquireDrawable() {
if (drawable) {
return drawable.texture;
@@ -180,7 +190,18 @@ id<MTLTexture> 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;
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
#include "MetalPlatform.h"
#include <backend/platforms/PlatformMetal.h>
#include "MetalDriverFactory.h"
@@ -22,19 +22,32 @@
#import <Foundation/Foundation.h>
#include <atomic>
namespace filament::backend {
struct PlatformMetalImpl {
id<MTLCommandQueue> mCommandQueue = nil;
// read form driver thread, read/written to from client thread
std::atomic<PlatformMetal::DrawableFailureBehavior> 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<MTLDevice> MetalPlatform::createDevice() noexcept {
id<MTLDevice> PlatformMetal::createDevice() noexcept {
id<MTLDevice> result;
#if !defined(FILAMENT_IOS)
@@ -62,16 +75,24 @@ id<MTLDevice> MetalPlatform::createDevice() noexcept {
return result;
}
id<MTLCommandQueue> MetalPlatform::createCommandQueue(id<MTLDevice> device) noexcept {
mCommandQueue = [device newCommandQueue];
mCommandQueue.label = @"Filament";
return mCommandQueue;
id<MTLCommandQueue> PlatformMetal::createCommandQueue(id<MTLDevice> device) noexcept {
pImpl->mCommandQueue = [device newCommandQueue];
pImpl->mCommandQueue.label = @"Filament";
return pImpl->mCommandQueue;
}
id<MTLCommandBuffer> MetalPlatform::createAndEnqueueCommandBuffer() noexcept {
id<MTLCommandBuffer> commandBuffer = [mCommandQueue commandBuffer];
id<MTLCommandBuffer> PlatformMetal::createAndEnqueueCommandBuffer() noexcept {
id<MTLCommandBuffer> 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