Skip to content

Commit

Permalink
Metal: add config to abandon frame if drawable cannot be acquired (#8397
Browse files Browse the repository at this point in the history
)
  • Loading branch information
bejado committed Jan 31, 2025
1 parent 3387f5b commit 07324d6
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 46 deletions.
2 changes: 1 addition & 1 deletion filament/backend/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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; }
Expand Down Expand Up @@ -54,11 +57,30 @@ class MetalPlatform final : public Platform {
*/
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
10 changes: 5 additions & 5 deletions filament/backend/src/metal/MetalBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -54,12 +54,12 @@ class ScopedAllocationTimer {
}
}

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;
Expand Down Expand Up @@ -141,7 +141,7 @@ class TrackedMetalBuffer {
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 {
Expand All @@ -152,7 +152,7 @@ class TrackedMetalBuffer {
id<MTLBuffer> mBuffer;
Type mType = Type::NONE;

static MetalPlatform* platform;
static PlatformMetal* platform;
static std::array<uint64_t, TypeCount> aliveBuffers;
};

Expand Down
4 changes: 2 additions & 2 deletions filament/backend/src/metal/MetalBuffer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
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)
Expand Down
2 changes: 2 additions & 0 deletions filament/backend/src/metal/MetalContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -156,6 +157,7 @@ struct MetalContext {
RenderPassFlags currentRenderPassFlags;
MetalRenderTarget* currentRenderTarget = nullptr;
bool validPipelineBound = false;
bool currentRenderPassAbandoned = false;

// State trackers.
PipelineStateTracker pipelineState;
Expand Down
8 changes: 4 additions & 4 deletions filament/backend/src/metal/MetalDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
namespace filament {
namespace backend {

class MetalPlatform;
class PlatformMetal;

class MetalBuffer;
class MetalProgram;
Expand All @@ -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;
Expand Down
38 changes: 31 additions & 7 deletions filament/backend/src/metal/MetalDriver.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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:
Expand Down Expand Up @@ -96,7 +96,7 @@
}

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};
Expand All @@ -109,7 +109,7 @@
}

MetalDriver::MetalDriver(
MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
PlatformMetal* platform, const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(*platform),
mContext(new MetalContext),
mHandleAllocator(
Expand Down Expand Up @@ -246,6 +246,7 @@

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)
Expand Down Expand Up @@ -631,19 +632,19 @@
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) {
Expand Down Expand Up @@ -1268,6 +1269,13 @@
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()) {
Expand Down Expand Up @@ -1329,6 +1337,10 @@
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
Expand Down Expand Up @@ -1629,6 +1641,11 @@
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";

Expand Down Expand Up @@ -1913,6 +1930,10 @@
}

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");
Expand Down Expand Up @@ -1956,6 +1977,9 @@

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;
Expand Down
4 changes: 2 additions & 2 deletions filament/backend/src/metal/MetalDriverFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 14 additions & 3 deletions filament/backend/src/metal/MetalHandles.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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();

Expand Down Expand Up @@ -86,6 +90,8 @@ class MetalSwapChain : public HwSwapChain {

bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }

bool isAbandoned() const;

private:

enum class SwapChainType {
Expand All @@ -103,6 +109,7 @@ class MetalSwapChain : public HwSwapChain {
void ensureDepthStencilTexture();

MetalContext& context;
PlatformMetal& platform;
id<CAMetalDrawable> drawable = nil;
id<MTLTexture> depthStencilTexture = nil;
id<MTLTexture> headlessDrawable = nil;
Expand All @@ -114,6 +121,8 @@ class MetalSwapChain : public HwSwapChain {
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.
Expand Down Expand Up @@ -341,6 +350,8 @@ class MetalRenderTarget : public HwRenderTarget {

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);
Expand Down
Loading

0 comments on commit 07324d6

Please sign in to comment.