From 38ceee8d757d47b74fae9032efcf4c2b27385597 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Fri, 13 Oct 2023 10:04:39 -0700 Subject: [PATCH] Support stencil buffer when post-processing is disabled (#7227) --- NEW_RELEASE_NOTES.md | 3 +- .../google/android/filament/SwapChain.java | 25 +++++++ .../com/google/android/filament/View.java | 3 +- .../backend/include/backend/DriverEnums.h | 5 ++ filament/backend/src/metal/MetalHandles.h | 11 ++- filament/backend/src/metal/MetalHandles.mm | 75 ++++++++++++------- filament/include/filament/SwapChain.h | 25 +++++++ filament/include/filament/View.h | 3 +- filament/src/details/Renderer.cpp | 7 ++ filament/src/details/SwapChain.h | 4 + libs/filamentapp/src/FilamentApp.cpp | 4 +- 11 files changed, 130 insertions(+), 35 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 87484203df..7b4725159c 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -11,4 +11,5 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). - engine: Added parameter for configuring JobSystem thread count - engine: In Java, introduce Engine.Builder - engine: New tone mapper: `AgXTonemapper`. -- matinfo: Add support for viewing ESSL 1.0 shaders \ No newline at end of file +- matinfo: Add support for viewing ESSL 1.0 shaders +- engine: Add support for stencil buffer when post-processing is disabled (Metal backend only). diff --git a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java index 9c0867fee2..429af3e140 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java +++ b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java @@ -103,6 +103,31 @@ public class SwapChain { */ public static final long CONFIG_SRGB_COLORSPACE = 0x10; + /** + * Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer. + * + * This flag is necessary when using View::setStencilBufferEnabled and rendering directly into + * the SwapChain (when post-processing is disabled). + * + * The specific format of the stencil buffer depends on platform support. The following pixel + * formats are tried, in order of preference: + * + * Depth only (without CONFIG_HAS_STENCIL_BUFFER): + * - DEPTH32F + * - DEPTH24 + * + * Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER): + * - DEPTH32F_STENCIL8 + * - DEPTH24F_STENCIL8 + * + * Note that enabling the stencil buffer may hinder depth precision and should only be used if + * necessary. + * + * @see View#setStencilBufferEnabled + * @see View#setPostProcessingEnabled + */ + public static final long CONFIG_HAS_STENCIL_BUFFER = 0x20; + SwapChain(long nativeSwapChain, Object surface) { mNativeObject = nativeSwapChain; mSurface = surface; diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java index 326b795479..6c7b794f30 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/View.java +++ b/android/filament-android/src/main/java/com/google/android/filament/View.java @@ -1032,7 +1032,8 @@ public class View { *

* *

- * Post-processing must be enabled in order to use the stencil buffer. + * If post-processing is disabled, then the SwapChain must have the CONFIG_HAS_STENCIL_BUFFER + * flag set in order to use the stencil buffer. *

* *

diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index a502e3ed79..227759769d 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -77,6 +77,11 @@ static constexpr uint64_t SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER = 0x8; */ static constexpr uint64_t SWAP_CHAIN_CONFIG_SRGB_COLORSPACE = 0x10; +/** + * Indicates that the SwapChain should also contain a stencil component. + */ +static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = 0x20; + static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES. static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3. diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index f21891a701..cd3a4e8c68 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -66,6 +66,7 @@ public: id acquireDrawable(); id acquireDepthTexture(); + id acquireStencilTexture(); void releaseDrawable(); @@ -94,12 +95,16 @@ private: void scheduleFrameScheduledCallback(); void scheduleFrameCompletedCallback(); + static MTLPixelFormat decideDepthStencilFormat(uint64_t flags); + void ensureDepthStencilTexture(); + MetalContext& context; id drawable = nil; - id depthTexture = nil; + id depthStencilTexture = nil; id headlessDrawable = nil; - NSUInteger headlessWidth; - NSUInteger headlessHeight; + MTLPixelFormat depthStencilFormat = MTLPixelFormatInvalid; + NSUInteger headlessWidth = 0; + NSUInteger headlessHeight = 0; CAMetalLayer* layer = nullptr; MetalExternalImage externalImage; SwapChainType type; diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index ed8a894ffb..595ec6f9cb 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -57,8 +57,11 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { } MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow, uint64_t flags) - : context(context), layer(nativeWindow), externalImage(context), - type(SwapChainType::CAMETALLAYER) { + : context(context), + depthStencilFormat(decideDepthStencilFormat(flags)), + layer(nativeWindow), + externalImage(context), + type(SwapChainType::CAMETALLAYER) { if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) { utils::slog.w << "Warning: Filament SwapChain has no CONFIG_TRANSPARENT flag, " @@ -79,17 +82,30 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow } MetalSwapChain::MetalSwapChain(MetalContext& context, int32_t width, int32_t height, uint64_t flags) - : context(context), headlessWidth(width), headlessHeight(height), externalImage(context), - type(SwapChainType::HEADLESS) { } + : context(context), + depthStencilFormat(decideDepthStencilFormat(flags)), + headlessWidth(width), + headlessHeight(height), + externalImage(context), + type(SwapChainType::HEADLESS) {} MetalSwapChain::MetalSwapChain(MetalContext& context, CVPixelBufferRef pixelBuffer, uint64_t flags) - : context(context), externalImage(context), type(SwapChainType::CVPIXELBUFFERREF) { + : context(context), + depthStencilFormat(decideDepthStencilFormat(flags)), + externalImage(context), + type(SwapChainType::CVPIXELBUFFERREF) { assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER); MetalExternalImage::assertWritableImage(pixelBuffer); externalImage.set(pixelBuffer); assert_invariant(externalImage.isValid()); } +MTLPixelFormat MetalSwapChain::decideDepthStencilFormat(uint64_t flags) { + // These formats are supported on all devices, both iOS and macOS. + return flags & SwapChain::CONFIG_HAS_STENCIL_BUFFER ? MTLPixelFormatDepth32Float_Stencil8 + : MTLPixelFormatDepth32Float; +} + MetalSwapChain::~MetalSwapChain() { externalImage.set(nullptr); } @@ -156,37 +172,40 @@ void MetalSwapChain::releaseDrawable() { } id 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; + ensureDepthStencilTexture(); + assert_invariant(depthStencilTexture); + return depthStencilTexture; +} + +id MetalSwapChain::acquireStencilTexture() { + if (!isMetalFormatStencil(depthStencilFormat)) { + return nil; + } + ensureDepthStencilTexture(); + assert_invariant(depthStencilTexture); + return depthStencilTexture; +} + +void MetalSwapChain::ensureDepthStencilTexture() { + NSUInteger width = getSurfaceWidth(); + NSUInteger height = getSurfaceHeight(); + if (UTILS_LIKELY(depthStencilTexture)) { + // If the surface size has changed, we'll need to allocate a new depth/stencil texture. + if (UTILS_UNLIKELY( + depthStencilTexture.width != width || depthStencilTexture.height != height)) { + depthStencilTexture = nil; } else { - return depthTexture; + return; } } - - 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 + [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:depthStencilFormat width:width height:height mipmapped:NO]; descriptor.usage = MTLTextureUsageRenderTarget; descriptor.resourceOptions = MTLResourceStorageModePrivate; - - depthTexture = [context.device newTextureWithDescriptor:descriptor]; - - return depthTexture; + depthStencilTexture = [context.device newTextureWithDescriptor:descriptor]; } void MetalSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) { @@ -1123,7 +1142,7 @@ MetalRenderTarget::Attachment MetalRenderTarget::getDepthAttachment() { MetalRenderTarget::Attachment MetalRenderTarget::getStencilAttachment() { Attachment result = stencil; if (defaultRenderTarget) { - // TODO: do we want the default SwapChain to have a default stencil buffer? + result.texture = context->currentDrawSwapChain->acquireStencilTexture(); } return result; } diff --git a/filament/include/filament/SwapChain.h b/filament/include/filament/SwapChain.h index 29413275a4..26c3da4a04 100644 --- a/filament/include/filament/SwapChain.h +++ b/filament/include/filament/SwapChain.h @@ -201,6 +201,31 @@ public: */ static constexpr uint64_t CONFIG_SRGB_COLORSPACE = backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE; + /** + * Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer. + * + * This flag is necessary when using View::setStencilBufferEnabled and rendering directly into + * the SwapChain (when post-processing is disabled). + * + * The specific format of the stencil buffer depends on platform support. The following pixel + * formats are tried, in order of preference: + * + * Depth only (without CONFIG_HAS_STENCIL_BUFFER): + * - DEPTH32F + * - DEPTH24 + * + * Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER): + * - DEPTH32F_STENCIL8 + * - DEPTH24F_STENCIL8 + * + * Note that enabling the stencil buffer may hinder depth precision and should only be used if + * necessary. + * + * @see View.setStencilBufferEnabled + * @see View.setPostProcessingEnabled + */ + static constexpr uint64_t CONFIG_HAS_STENCIL_BUFFER = backend::SWAP_CHAIN_HAS_STENCIL_BUFFER; + /** * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag. * The default implementation returns false. diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index 7c0f1683e4..4bba745d1f 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -662,7 +662,8 @@ public: * Material's stencil comparison function and reference value. Fragments that don't pass the * stencil test are then discarded. * - * Post-processing must be enabled in order to use the stencil buffer. + * If post-processing is disabled, then the SwapChain must have the CONFIG_HAS_STENCIL_BUFFER + * flag set in order to use the stencil buffer. * * A renderable's priority (see RenderableManager::setPriority) is useful to control the order * in which primitives are drawn. diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index a3cc9026ca..4ee8e2b205 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -1095,6 +1095,13 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { } } + if (UTILS_UNLIKELY(outputIsSwapChain && view.isStencilBufferEnabled())) { + assert_invariant(mSwapChain); + ASSERT_PRECONDITION(mSwapChain->hasStencilBuffer(), + "View has stencil buffer enabled, but SwapChain does not have " + "SwapChain::CONFIG_HAS_STENCIL_BUFFER flag set."); + } + // auto debug = structure // fg.forwardResource(fgViewRenderTarget, debug ? debug : input); diff --git a/filament/src/details/SwapChain.h b/filament/src/details/SwapChain.h index 032b5e3f91..3c820b9799 100644 --- a/filament/src/details/SwapChain.h +++ b/filament/src/details/SwapChain.h @@ -58,6 +58,10 @@ public: return (mConfigFlags & CONFIG_READABLE) != 0; } + constexpr bool hasStencilBuffer() const noexcept { + return (mConfigFlags & CONFIG_HAS_STENCIL_BUFFER) != 0; + } + backend::Handle getHwHandle() const noexcept { return mSwapChain; } diff --git a/libs/filamentapp/src/FilamentApp.cpp b/libs/filamentapp/src/FilamentApp.cpp index 8a7ef84cff..17804511a5 100644 --- a/libs/filamentapp/src/FilamentApp.cpp +++ b/libs/filamentapp/src/FilamentApp.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #ifndef NDEBUG @@ -644,7 +645,8 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, mFilamentApp->mEngine->getSupportedFeatureLevel()); mFilamentApp->mEngine->setActiveFeatureLevel(config.featureLevel); - mSwapChain = mFilamentApp->mEngine->createSwapChain(nativeSwapChain); + mSwapChain = mFilamentApp->mEngine->createSwapChain( + nativeSwapChain, filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER); } mRenderer = mFilamentApp->mEngine->createRenderer();