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