Consolidate surface-related functionality into MetalSwapChain (#3246)

This commit is contained in:
Ben Doherty
2020-11-03 12:00:03 -07:00
committed by GitHub
parent 964a1bd7f2
commit 30f5a97ea1
5 changed files with 197 additions and 145 deletions

View File

@@ -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);

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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;
}