Consolidate surface-related functionality into MetalSwapChain (#3246)
This commit is contained in:
@@ -45,15 +45,6 @@ struct MetalContext {
|
||||
id<MTLCommandBuffer> pendingCommandBuffer = nullptr;
|
||||
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nullptr;
|
||||
|
||||
// These two fields store a callback and user data to notify the client that a frame is ready
|
||||
// for presentation.
|
||||
// If frameFinishedCallback is nullptr, then the Metal backend automatically calls
|
||||
// presentDrawable when the frame is commited.
|
||||
// Otherwise, the Metal backend will not automatically present the frame. Instead, clients bear
|
||||
// the responsibility of presenting the frame by calling the PresentCallable object.
|
||||
backend::FrameFinishedCallback frameFinishedCallback = nullptr;
|
||||
void* frameFinishedUserData = nullptr;
|
||||
|
||||
// Tracks resources used by command buffers.
|
||||
MetalResourceTracker resourceTracker;
|
||||
|
||||
@@ -75,11 +66,7 @@ struct MetalContext {
|
||||
|
||||
MetalBufferPool* bufferPool;
|
||||
|
||||
// Surface-related properties.
|
||||
MetalSwapChain* currentSurface = nullptr;
|
||||
id<CAMetalDrawable> currentDrawable = nil;
|
||||
id<MTLTexture> currentDepthTexture = nil;
|
||||
id<MTLTexture> headlessDrawable = nil;
|
||||
MetalSwapChain* currentSurface = nil;
|
||||
|
||||
// External textures.
|
||||
CVMetalTextureCacheRef textureCache = nullptr;
|
||||
@@ -98,14 +85,6 @@ struct MetalContext {
|
||||
TimerQueryInterface* timerQueryImpl;
|
||||
};
|
||||
|
||||
// Acquire the current surface's CAMetalDrawable for the current frame if it has not already been
|
||||
// acquired. This method stores it in the context's currentDrawable field and returns the
|
||||
// drawable's texture.
|
||||
// For headless swapchains a new texture is created.
|
||||
id<MTLTexture> acquireDrawable(MetalContext* context);
|
||||
|
||||
id<MTLTexture> acquireDepthTexture(MetalContext* context);
|
||||
|
||||
id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context);
|
||||
|
||||
void submitPendingCommands(MetalContext* context);
|
||||
|
||||
@@ -24,94 +24,6 @@ namespace filament {
|
||||
namespace backend {
|
||||
namespace metal {
|
||||
|
||||
void presentDrawable(bool presentFrame, void* user) {
|
||||
// CFBridgingRelease here is used to balance the CFBridgingRetain inside of acquireDrawable.
|
||||
id<CAMetalDrawable> drawable = (id<CAMetalDrawable>) CFBridgingRelease(user);
|
||||
if (presentFrame) {
|
||||
[drawable present];
|
||||
}
|
||||
// The drawable will be released here when the "drawable" variable goes out of scope.
|
||||
}
|
||||
|
||||
id<MTLTexture> acquireDrawable(MetalContext* context) {
|
||||
if (context->currentDrawable) {
|
||||
return context->currentDrawable.texture;
|
||||
}
|
||||
if (context->currentSurface->isHeadless()) {
|
||||
if (context->headlessDrawable) {
|
||||
return context->headlessDrawable;
|
||||
}
|
||||
// For headless surfaces we construct a "fake" drawable, which is simply a renderable
|
||||
// texture.
|
||||
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor new];
|
||||
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
textureDescriptor.width = context->currentSurface->getSurfaceWidth();
|
||||
textureDescriptor.height = context->currentSurface->getSurfaceHeight();
|
||||
// Specify MTLTextureUsageShaderRead so the headless surface can be blitted from.
|
||||
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||||
#if defined(IOS)
|
||||
textureDescriptor.storageMode = MTLStorageModeShared;
|
||||
#else
|
||||
textureDescriptor.storageMode = MTLStorageModeManaged;
|
||||
#endif
|
||||
context->headlessDrawable = [context->device newTextureWithDescriptor:textureDescriptor];
|
||||
return context->headlessDrawable;
|
||||
}
|
||||
|
||||
context->currentDrawable = [context->currentSurface->getLayer() nextDrawable];
|
||||
|
||||
if (context->frameFinishedCallback) {
|
||||
id<CAMetalDrawable> drawable = context->currentDrawable;
|
||||
backend::FrameFinishedCallback callback = context->frameFinishedCallback;
|
||||
void* userData = context->frameFinishedUserData;
|
||||
// This block strongly captures drawable to keep it alive until the handler executes.
|
||||
[getPendingCommandBuffer(context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
|
||||
// CFBridgingRetain is used here to give the drawable a +1 retain count before
|
||||
// casting it to a void*.
|
||||
PresentCallable callable(presentDrawable, (void*) CFBridgingRetain(drawable));
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
callback(callable, userData);
|
||||
});
|
||||
}];
|
||||
}
|
||||
ASSERT_POSTCONDITION(context->currentDrawable != nil, "Could not obtain drawable.");
|
||||
return context->currentDrawable.texture;
|
||||
}
|
||||
|
||||
id<MTLTexture> acquireDepthTexture(MetalContext* context) {
|
||||
if (context->currentDepthTexture) {
|
||||
// If the surface size has changed, we'll need to allocate a new depth texture.
|
||||
if (context->currentDepthTexture.width != context->currentSurface->getSurfaceWidth() ||
|
||||
context->currentDepthTexture.height != context->currentSurface->getSurfaceHeight()) {
|
||||
context->currentDepthTexture = nil;
|
||||
} else {
|
||||
return context->currentDepthTexture;
|
||||
}
|
||||
}
|
||||
|
||||
const MTLPixelFormat depthFormat =
|
||||
#if defined(IOS)
|
||||
MTLPixelFormatDepth32Float;
|
||||
#else
|
||||
context->device.depth24Stencil8PixelFormatSupported ?
|
||||
MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float;
|
||||
#endif
|
||||
|
||||
const NSUInteger width = context->currentSurface->getSurfaceWidth();
|
||||
const NSUInteger height = context->currentSurface->getSurfaceHeight();
|
||||
MTLTextureDescriptor* descriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:depthFormat
|
||||
width:width
|
||||
height:height
|
||||
mipmapped:NO];
|
||||
descriptor.usage = MTLTextureUsageRenderTarget;
|
||||
descriptor.resourceOptions = MTLResourceStorageModePrivate;
|
||||
|
||||
context->currentDepthTexture = [context->device newTextureWithDescriptor:descriptor];
|
||||
|
||||
return context->currentDepthTexture;
|
||||
}
|
||||
|
||||
id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
|
||||
if (context->pendingCommandBuffer) {
|
||||
return context->pendingCommandBuffer;
|
||||
|
||||
@@ -103,9 +103,7 @@ void MetalDriver::tick(int) {
|
||||
|
||||
void MetalDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId,
|
||||
backend::FrameFinishedCallback callback, void* user) {
|
||||
// If a callback was specified, then the client is responsible for presenting the frame.
|
||||
mContext->frameFinishedCallback = callback;
|
||||
mContext->frameFinishedUserData = user;
|
||||
mContext->currentSurface->setFrameFinishedCallback(callback, user);
|
||||
}
|
||||
|
||||
void MetalDriver::execute(std::function<void(void)> fn) noexcept {
|
||||
@@ -125,8 +123,7 @@ void MetalDriver::endFrame(uint32_t frameId) {
|
||||
mContext->bufferPool->gc();
|
||||
|
||||
// If we acquired a drawable for this frame, ensure that we release it here.
|
||||
mContext->currentDrawable = nil;
|
||||
mContext->headlessDrawable = nil;
|
||||
mContext->currentSurface->releaseDrawable();
|
||||
|
||||
CVMetalTextureCacheFlush(mContext->textureCache, 0);
|
||||
}
|
||||
@@ -281,12 +278,12 @@ void MetalDriver::createSyncR(Handle<HwSync> sh, int) {
|
||||
|
||||
void MetalDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
auto* metalLayer = (__bridge CAMetalLayer*) nativeWindow;
|
||||
construct_handle<MetalSwapChain>(mHandleMap, sch, mContext->device, metalLayer, flags);
|
||||
construct_handle<MetalSwapChain>(mHandleMap, sch, *mContext, metalLayer, flags);
|
||||
}
|
||||
|
||||
void MetalDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch,
|
||||
uint32_t width, uint32_t height, uint64_t flags) {
|
||||
construct_handle<MetalSwapChain>(mHandleMap, sch, width, height, flags);
|
||||
construct_handle<MetalSwapChain>(mHandleMap, sch, *mContext, width, height, flags);
|
||||
}
|
||||
|
||||
void MetalDriver::createStreamFromTextureIdR(Handle<HwStream>, intptr_t externalTextureId,
|
||||
@@ -800,12 +797,10 @@ void MetalDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain> s
|
||||
}
|
||||
|
||||
void MetalDriver::commit(Handle<HwSwapChain> sch) {
|
||||
if (mContext->currentDrawable != nil && !mContext->frameFinishedCallback) {
|
||||
[getPendingCommandBuffer(mContext) presentDrawable:mContext->currentDrawable];
|
||||
}
|
||||
auto* swapChain = handle_cast<MetalSwapChain>(mHandleMap, sch);
|
||||
swapChain->present();
|
||||
submitPendingCommands(mContext);
|
||||
mContext->currentDrawable = nil;
|
||||
mContext->headlessDrawable = nil;
|
||||
swapChain->releaseDrawable();
|
||||
}
|
||||
|
||||
void MetalDriver::bindUniformBuffer(size_t index, Handle<HwUniformBuffer> ubh) {
|
||||
|
||||
@@ -44,33 +44,63 @@ namespace metal {
|
||||
class MetalSwapChain : public HwSwapChain {
|
||||
public:
|
||||
|
||||
MetalSwapChain(id<MTLDevice> device, CAMetalLayer* nativeWindow, uint64_t flags);
|
||||
// Instantiate a SwapChain from a CAMetalLayer
|
||||
MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow, uint64_t flags);
|
||||
|
||||
// Instantiate a headless SwapChain.
|
||||
MetalSwapChain(int32_t width, int32_t height, uint64_t flags);
|
||||
MetalSwapChain(MetalContext& context, int32_t width, int32_t height, uint64_t flags);
|
||||
|
||||
bool isHeadless() const { return layer == nullptr; }
|
||||
CAMetalLayer* getLayer() const { return layer; }
|
||||
~MetalSwapChain();
|
||||
|
||||
NSUInteger getSurfaceWidth() const {
|
||||
if (isHeadless()) {
|
||||
return headlessWidth;
|
||||
}
|
||||
return (NSUInteger) layer.drawableSize.width;
|
||||
}
|
||||
// Acquires a texture that can be used to render into this SwapChain.
|
||||
// The texture source depends on the type of SwapChain:
|
||||
// - CAMetalLayer-backed: acquires the CAMetalDrawable and returns its texture.
|
||||
// - Headless: lazily creates and returns a headless texture.
|
||||
id<MTLTexture> acquireDrawable();
|
||||
|
||||
NSUInteger getSurfaceHeight() const {
|
||||
if (isHeadless()) {
|
||||
return headlessHeight;
|
||||
}
|
||||
return (NSUInteger) layer.drawableSize.height;
|
||||
}
|
||||
id<MTLTexture> acquireDepthTexture();
|
||||
|
||||
void releaseDrawable();
|
||||
|
||||
void setFrameFinishedCallback(FrameFinishedCallback callback, void* user);
|
||||
|
||||
// For CAMetalLayer-backed SwapChains, presents the drawable or schedules a
|
||||
// FrameFinishedCallback.
|
||||
void present();
|
||||
|
||||
NSUInteger getSurfaceWidth() const;
|
||||
NSUInteger getSurfaceHeight() const;
|
||||
|
||||
private:
|
||||
|
||||
enum class SwapChainType {
|
||||
CAMETALLAYER,
|
||||
CVPIXELBUFFERREF,
|
||||
HEADLESS
|
||||
};
|
||||
bool isCaMetalLayer() const { return type == SwapChainType::CAMETALLAYER; }
|
||||
bool isHeadless() const { return type == SwapChainType::HEADLESS; }
|
||||
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
|
||||
|
||||
void scheduleFrameFinishedCallback();
|
||||
|
||||
MetalContext& context;
|
||||
id<CAMetalDrawable> drawable = nil;
|
||||
id<MTLTexture> depthTexture = nil;
|
||||
id<MTLTexture> headlessDrawable = nil;
|
||||
NSUInteger headlessWidth;
|
||||
NSUInteger headlessHeight;
|
||||
CAMetalLayer* layer = nullptr;
|
||||
SwapChainType type;
|
||||
|
||||
// These two fields store a callback and user data to notify the client that a frame is ready
|
||||
// for presentation.
|
||||
// If frameFinishedCallback is nullptr, then the Metal backend automatically calls
|
||||
// presentDrawable when the frame is commited.
|
||||
// Otherwise, the Metal backend will not automatically present the frame. Instead, clients bear
|
||||
// the responsibility of presenting the frame by calling the PresentCallable object.
|
||||
FrameFinishedCallback frameFinishedCallback = nullptr;
|
||||
void* frameFinishedUserData = nullptr;
|
||||
};
|
||||
|
||||
struct MetalVertexBuffer : public HwVertexBuffer {
|
||||
|
||||
@@ -49,8 +49,8 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) {
|
||||
return MTLTextureUsage(u);
|
||||
}
|
||||
|
||||
MetalSwapChain::MetalSwapChain(id<MTLDevice> device, CAMetalLayer* nativeWindow, uint64_t flags)
|
||||
: layer(nativeWindow) {
|
||||
MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow, uint64_t flags)
|
||||
: context(context), layer(nativeWindow), type(SwapChainType::CAMETALLAYER) {
|
||||
|
||||
if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) {
|
||||
utils::slog.w << "Warning: Filament SwapChain has no CONFIG_TRANSPARENT flag, "
|
||||
@@ -68,11 +68,147 @@ MetalSwapChain::MetalSwapChain(id<MTLDevice> device, CAMetalLayer* nativeWindow,
|
||||
nativeWindow.framebufferOnly = NO;
|
||||
}
|
||||
|
||||
layer.device = device;
|
||||
layer.device = context.device;
|
||||
}
|
||||
|
||||
MetalSwapChain::MetalSwapChain(int32_t width, int32_t height, uint64_t flags) : headlessWidth(width),
|
||||
headlessHeight(height) { }
|
||||
MetalSwapChain::MetalSwapChain(MetalContext& context, int32_t width, int32_t height, uint64_t flags)
|
||||
: context(context), headlessWidth(width), headlessHeight(height),
|
||||
type(SwapChainType::HEADLESS) { }
|
||||
|
||||
|
||||
NSUInteger MetalSwapChain::getSurfaceWidth() const {
|
||||
if (isHeadless()) {
|
||||
return headlessWidth;
|
||||
}
|
||||
return (NSUInteger) layer.drawableSize.width;
|
||||
}
|
||||
|
||||
NSUInteger MetalSwapChain::getSurfaceHeight() const {
|
||||
if (isHeadless()) {
|
||||
return headlessHeight;
|
||||
}
|
||||
return (NSUInteger) layer.drawableSize.height;
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::acquireDrawable() {
|
||||
if (drawable) {
|
||||
return drawable.texture;
|
||||
}
|
||||
|
||||
if (isHeadless()) {
|
||||
if (headlessDrawable) {
|
||||
return headlessDrawable;
|
||||
}
|
||||
// For headless surfaces we construct a "fake" drawable, which is simply a renderable
|
||||
// texture.
|
||||
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor new];
|
||||
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
textureDescriptor.width = headlessWidth;
|
||||
textureDescriptor.height = headlessHeight;
|
||||
// Specify MTLTextureUsageShaderRead so the headless surface can be blitted from.
|
||||
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||||
#if defined(IOS)
|
||||
textureDescriptor.storageMode = MTLStorageModeShared;
|
||||
#else
|
||||
textureDescriptor.storageMode = MTLStorageModeManaged;
|
||||
#endif
|
||||
headlessDrawable = [context.device newTextureWithDescriptor:textureDescriptor];
|
||||
return headlessDrawable;
|
||||
}
|
||||
|
||||
assert(isCaMetalLayer());
|
||||
drawable = [layer nextDrawable];
|
||||
|
||||
ASSERT_POSTCONDITION(drawable != nil, "Could not obtain drawable.");
|
||||
return drawable.texture;
|
||||
}
|
||||
|
||||
void MetalSwapChain::releaseDrawable() {
|
||||
drawable = nil;
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::acquireDepthTexture() {
|
||||
if (depthTexture) {
|
||||
// If the surface size has changed, we'll need to allocate a new depth texture.
|
||||
if (depthTexture.width != getSurfaceWidth() ||
|
||||
depthTexture.height != getSurfaceHeight()) {
|
||||
depthTexture = nil;
|
||||
} else {
|
||||
return depthTexture;
|
||||
}
|
||||
}
|
||||
|
||||
const MTLPixelFormat depthFormat =
|
||||
#if defined(IOS)
|
||||
MTLPixelFormatDepth32Float;
|
||||
#else
|
||||
context.device.depth24Stencil8PixelFormatSupported ?
|
||||
MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float;
|
||||
#endif
|
||||
|
||||
const NSUInteger width = getSurfaceWidth();
|
||||
const NSUInteger height = getSurfaceHeight();
|
||||
MTLTextureDescriptor* descriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:depthFormat
|
||||
width:width
|
||||
height:height
|
||||
mipmapped:NO];
|
||||
descriptor.usage = MTLTextureUsageRenderTarget;
|
||||
descriptor.resourceOptions = MTLResourceStorageModePrivate;
|
||||
|
||||
depthTexture = [context.device newTextureWithDescriptor:descriptor];
|
||||
|
||||
return depthTexture;
|
||||
}
|
||||
|
||||
void MetalSwapChain::setFrameFinishedCallback(FrameFinishedCallback callback, void* user) {
|
||||
frameFinishedCallback = callback;
|
||||
frameFinishedUserData = user;
|
||||
}
|
||||
|
||||
void MetalSwapChain::present() {
|
||||
if (drawable) {
|
||||
if (frameFinishedCallback) {
|
||||
scheduleFrameFinishedCallback();
|
||||
} else {
|
||||
[getPendingCommandBuffer(&context) presentDrawable:drawable];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetalSwapChain::~MetalSwapChain() {
|
||||
}
|
||||
|
||||
void presentDrawable(bool presentFrame, void* user) {
|
||||
// CFBridgingRelease here is used to balance the CFBridgingRetain inside of acquireDrawable.
|
||||
id<CAMetalDrawable> drawable = (id<CAMetalDrawable>) CFBridgingRelease(user);
|
||||
if (presentFrame) {
|
||||
[drawable present];
|
||||
}
|
||||
// The drawable will be released here when the "drawable" variable goes out of scope.
|
||||
}
|
||||
|
||||
void MetalSwapChain::scheduleFrameFinishedCallback() {
|
||||
if (!frameFinishedCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(drawable);
|
||||
backend::FrameFinishedCallback callback = frameFinishedCallback;
|
||||
// This block strongly captures drawable to keep it alive until the handler executes.
|
||||
// We cannot simply reference this->drawable inside the block because the block would then only
|
||||
// capture the _this_ pointer (MetalSwapChain*) instead of the drawable.
|
||||
id<CAMetalDrawable> d = drawable;
|
||||
void* userData = frameFinishedUserData;
|
||||
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
|
||||
// CFBridgingRetain is used here to give the drawable a +1 retain count before
|
||||
// casting it to a void*.
|
||||
PresentCallable callable(presentDrawable, (void*) CFBridgingRetain(d));
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
callback(callable, userData);
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
MetalVertexBuffer::MetalVertexBuffer(MetalContext& context, uint8_t bufferCount,
|
||||
uint8_t attributeCount, uint32_t vertexCount, AttributeArray const& attributes)
|
||||
@@ -604,7 +740,7 @@ MetalRenderTarget::Attachment MetalRenderTarget::getColorAttachment(size_t index
|
||||
assert(index < MRT::TARGET_COUNT);
|
||||
Attachment result = color[index];
|
||||
if (index == 0 && defaultRenderTarget) {
|
||||
result.texture = acquireDrawable(context);
|
||||
result.texture = context->currentSurface->acquireDrawable();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -612,7 +748,7 @@ MetalRenderTarget::Attachment MetalRenderTarget::getColorAttachment(size_t index
|
||||
MetalRenderTarget::Attachment MetalRenderTarget::getDepthAttachment() {
|
||||
Attachment result = depth;
|
||||
if (defaultRenderTarget) {
|
||||
result.texture = acquireDepthTexture(context);
|
||||
result.texture = context->currentSurface->acquireDepthTexture();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user