Compare commits

...

12 Commits

Author SHA1 Message Date
Mathias Agopian
5c3cdbb7fe AndroidNativeWindow must also work on 32-bits architectures 2025-11-10 14:41:47 -08:00
Benjamin Doherty
85d0543eb1 Add workaround for invalid handle assertion 2025-11-05 10:32:03 -08:00
Anish Goyal
b03180650c Check for null fences to avoid segfault in tests (#9389)
* Check for null fences to avoid segfault in tests

In some test environments, creating a sync or fence backed by an Android
fence returns null. In order to avoid NPEs, cover this scenario with a
null check.

* Address PR - add log statement

Log that native fences are not supported when unable to create a native
sync fence.
2025-11-04 12:56:25 -05:00
Benjamin Doherty
d9fd0823ae Add missing mutex header 2025-11-03 18:28:25 -05:00
Benjamin Doherty
d1c331529e Bump MATERIAL_VERSION to 67 2025-11-03 16:03:30 -05:00
Mathias Agopian
8b97948149 vkWaitForFences() must be called with the read-lock held 2025-11-03 12:08:27 -08:00
Mathias Agopian
6e3da09ec2 fix a possible deadlock on exit (#9377)
The deadlock happened because FrameInfoManager needs to wait that all
fences have signaled before it can be destroyed.

The fix here is simply to destroy the fence without waiting (for safety
we do wait after we destroy them).

This uncovered another problem where all backends didn't handle this
correctly.

We now explicitly handle this when destroying a fence: we make sure it
unblocks all the waiters and returns an error.

FIXES=[457243841]
2025-11-03 13:27:40 -05:00
Mathias Agopian
a27cb66257 fix transparent picking (#9390)
FIXES=[456489383]
2025-11-03 13:22:24 -05:00
Mathias Agopian
230a5d8a93 Fix GLES2 UBO emulation (#9392)
The binding cache didn't take into account the UBO offset.

FIXES=[456802974]
2025-11-03 13:22:18 -05:00
Ben Doherty
66186ba396 Metal: support MSAA SwapChain flag (#9361) 2025-11-03 13:22:11 -05:00
Ben Doherty
40a66263ed Refactor Metal command buffer error logging (#9383) 2025-11-03 13:22:05 -05:00
Powei Feng
65200838b9 Bump version to 1.67.0 2025-10-29 11:09:39 -07:00
32 changed files with 680 additions and 350 deletions

View File

@@ -6,3 +6,5 @@
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.

View File

@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.66.2'
implementation 'com.google.android.filament:filament-android:1.67.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.66.2'
pod 'Filament', '~> 1.67.0'
```
## Documentation

View File

@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.66.2
VERSION_NAME=1.67.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.

View File

@@ -592,6 +592,7 @@ if (APPLE OR LINUX)
test/test_BufferUpdates.cpp
test/test_Callbacks.cpp
test/test_MemoryMappedBuffer.cpp
test/test_MsaaSwapChain.cpp
test/test_MRT.cpp
test/test_PushConstants.cpp
test/test_LoadImage.cpp

View File

@@ -37,7 +37,12 @@ struct NativeWindow {
GET_COMPOSITOR_TIMING = 26,
GET_FRAME_TIMESTAMPS = 27,
};
#if defined(__LP64__)
uint64_t pad[18];
#else
uint32_t pad[21];
#endif
int (*query)(ANativeWindow const*, int, int*);
int (*perform)(ANativeWindow*, int, ...);

View File

@@ -95,6 +95,38 @@ void initializeSupportedGpuFamilies(MetalContext* context) {
}
}
void logMTLCommandBufferError(MTLCommandBufferError error) {
#define MTL_COMMAND_ERROR_CASE(ERR) \
if (error == (ERR)) { \
LOG(ERROR) << "Filament Metal error: " #ERR "."; \
return; \
}
#if !defined(FILAMENT_IOS)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorDeviceRemoved)
#endif
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorNone)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorInternal)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorTimeout)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorPageFault)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorAccessRevoked)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorNotPermitted)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorOutOfMemory)
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorInvalidResource)
if (@available(macOS 11.0, *)) {
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorMemoryless)
}
if (@available(iOS 15.0, macOS 12.0, *)) {
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorStackOverflow)
}
LOG(ERROR) << "Filament Metal unknown error.";
#undef MTL_COMMAND_ERROR_CASE
}
id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
if (context->pendingCommandBuffer) {
return context->pendingCommandBuffer;
@@ -120,8 +152,7 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
}
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
LOG(ERROR) << "Filament Metal command buffer errored with code: " << errorCode << " ("
<< stringifyMTLCommandBufferError(errorCode) << ").";
logMTLCommandBufferError(errorCode);
}
}];
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)

View File

@@ -621,7 +621,9 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
auto& sc = mContext->sampleCountLookup;
samples = sc[std::min(MAX_SAMPLE_COUNT, samples)];
MetalRenderTarget::Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {{}};
using AttachmentInfo = MetalRenderTarget::AttachmentInfo;
AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { {} };
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (none(targetBufferFlags & getTargetBufferFlagsAt(i))) {
continue;
@@ -635,7 +637,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
colorAttachments[i] = { colorTexture, color[i].level, color[i].layer };
}
MetalRenderTarget::Attachment depthAttachment = {};
AttachmentInfo depthAttachment = {};
if (any(targetBufferFlags & TargetBufferFlags::DEPTH)) {
FILAMENT_CHECK_PRECONDITION(depth.handle)
<< "The DEPTH flag was specified, but invalid depth handle provided.";
@@ -645,7 +647,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
depthAttachment = { depthTexture, depth.level, depth.layer };
}
MetalRenderTarget::Attachment stencilAttachment = {};
AttachmentInfo stencilAttachment = {};
if (any(targetBufferFlags & TargetBufferFlags::STENCIL)) {
FILAMENT_CHECK_PRECONDITION(stencil.handle)
<< "The STENCIL flag was specified, but invalid stencil handle provided.";
@@ -669,8 +671,6 @@ void MetalDriver::createFenceR(Handle<HwFence> fh, utils::ImmutableCString&& tag
void MetalDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags,
utils::ImmutableCString&& tag) {
// TODO: support MSAA swapchain
if (UTILS_UNLIKELY(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER)) {
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) nativeWindow;
construct_handle<MetalSwapChain>(sch, *mContext, mPlatform, pixelBuffer, flags);
@@ -1039,6 +1039,8 @@ void MetalDriver::updateStreams(DriverApi* driver) {
void MetalDriver::destroyFence(Handle<HwFence> fh) {
if (fh) {
auto* fence = handle_cast<MetalFence>(fh);
fence->cancel();
destruct_handle<MetalFence>(fh);
}
}
@@ -1150,8 +1152,7 @@ bool MetalDriver::isSRGBSwapChainSupported() {
}
bool MetalDriver::isMSAASwapChainSupported(uint32_t) {
// TODO: support MSAA swapchain
return false;
return true;
}
bool MetalDriver::isProtectedContentSupported() {
@@ -1561,9 +1562,9 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
auto srcTarget = handle_cast<MetalRenderTarget>(src);
// We always readPixels from the COLOR0 attachment.
MetalRenderTarget::Attachment color = srcTarget->getDrawColorAttachment(0);
MetalAttachment color = srcTarget->getDrawColorAttachment(0);
id<MTLTexture> srcTexture = color.getTexture();
size_t miplevel = color.level;
size_t miplevel = color.getLevel();
// Clamp height and width to actual texture's height and width
MTLSize srcTextureSize = MTLSizeMake(srcTexture.width >> miplevel, srcTexture.height >> miplevel, 1);
@@ -1771,8 +1772,8 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
};
// We always blit from/to the COLOR0 attachment.
MetalRenderTarget::Attachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
MetalRenderTarget::Attachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
MetalAttachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
MetalAttachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
if (srcColorAttachment && dstColorAttachment) {
FILAMENT_CHECK_PRECONDITION(
@@ -1784,13 +1785,13 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
args.filter = filter;
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
args.source.texture = srcColorAttachment.getTexture();
args.source.level = srcColorAttachment.level;
args.source.slice = srcColorAttachment.layer;
args.source.level = srcColorAttachment.getLevel();
args.source.slice = srcColorAttachment.getLayer();
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
args.destination.texture = dstColorAttachment.getTexture();
args.destination.level = dstColorAttachment.level;
args.destination.slice = dstColorAttachment.layer;
args.destination.level = dstColorAttachment.getLevel();
args.destination.slice = dstColorAttachment.getLayer();
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blitDEPRECATED");
}
@@ -1826,24 +1827,28 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
auto [fragment, vertex] = functions.getRasterFunctions();
// Pipeline state
MetalRenderTarget* const rt = mContext->currentRenderTarget;
MTLPixelFormat colorPixelFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { MTLPixelFormatInvalid };
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
const auto& attachment = mContext->currentRenderTarget->getDrawColorAttachment(i);
const auto& attachment = rt->getDrawColorAttachment(i);
if (!attachment) {
continue;
}
colorPixelFormat[i] = attachment.getPixelFormat();
assert_invariant(attachment.getSampleCount() == rt->getSampleCount());
}
MTLPixelFormat depthPixelFormat = MTLPixelFormatInvalid;
const auto& depthAttachment = mContext->currentRenderTarget->getDepthAttachment();
const auto& depthAttachment = rt->getDepthAttachment();
if (depthAttachment) {
depthPixelFormat = depthAttachment.getPixelFormat();
assert_invariant(depthAttachment.getSampleCount() == rt->getSampleCount());
}
MTLPixelFormat stencilPixelFormat = MTLPixelFormatInvalid;
const auto& stencilAttachment = mContext->currentRenderTarget->getStencilAttachment();
const auto& stencilAttachment = rt->getStencilAttachment();
if (stencilAttachment) {
stencilPixelFormat = stencilAttachment.getPixelFormat();
assert_invariant(isMetalFormatStencil(stencilPixelFormat));
assert_invariant(stencilAttachment.getSampleCount() == rt->getSampleCount());
}
MetalPipelineState const pipelineState {
.vertexFunction = vertex,
@@ -1861,7 +1866,7 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
},
.depthAttachmentPixelFormat = depthPixelFormat,
.stencilAttachmentPixelFormat = stencilPixelFormat,
.sampleCount = mContext->currentRenderTarget->getSamples(),
.sampleCount = rt->getSampleCount(),
.blendState = BlendState {
.alphaBlendOperation = getMetalBlendOperation(rs.blendEquationAlpha),
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),

View File

@@ -451,38 +451,6 @@ inline MTLTextureSwizzleChannels getSwizzleChannels(TextureSwizzle r, TextureSwi
getSwizzle(a));
}
inline const char* stringifyMTLCommandBufferError(MTLCommandBufferError error) {
#if !defined(FILAMENT_IOS)
if (error == MTLCommandBufferErrorDeviceRemoved) {
return "MTLCommandBufferErrorDeviceRemoved";
}
#endif
switch (error) {
case MTLCommandBufferErrorNone:
return "MTLCommandBufferErrorNone";
case MTLCommandBufferErrorInternal:
return "MTLCommandBufferErrorInternal";
case MTLCommandBufferErrorTimeout:
return "MTLCommandBufferErrorTimeout";
case MTLCommandBufferErrorPageFault:
return "MTLCommandBufferErrorPageFault";
case MTLCommandBufferErrorAccessRevoked:
return "MTLCommandBufferErrorAccessRevoked";
case MTLCommandBufferErrorNotPermitted:
return "MTLCommandBufferErrorNotPermitted";
case MTLCommandBufferErrorOutOfMemory:
return "MTLCommandBufferErrorOutOfMemory";
case MTLCommandBufferErrorInvalidResource:
return "MTLCommandBufferErrorInvalidResource";
case MTLCommandBufferErrorMemoryless:
return "MTLCommandBufferErrorMemoryless";
case MTLCommandBufferErrorStackOverflow:
return "MTLCommandBufferErrorStackOverflow";
default:
return "Unknown";
}
}
} // namespace backend
} // namespace filament

View File

@@ -49,6 +49,61 @@
namespace filament {
namespace backend {
class MetalAttachment {
public:
MetalAttachment() = default;
MetalAttachment(id<MTLTexture> texture, uint8_t level = 0, uint16_t layer = 0)
: mLevel(level),
mLayer(layer),
mTexture(texture) {
assert_invariant(texture);
}
explicit operator bool() const { return mTexture != nil; }
id<MTLTexture> getTexture() const { return mTexture; }
id<MTLTexture> getMsaaTexture() const { return mMsaaTexture; }
MTLPixelFormat getPixelFormat() const {
return mTexture ? mTexture.pixelFormat : MTLPixelFormatInvalid;
}
NSUInteger getSampleCount() const {
if (mMsaaTexture) {
return mMsaaTexture.sampleCount;
}
if (mTexture) {
return mTexture.sampleCount;
}
return 1u;
}
uint8_t getLevel() const { return mLevel; }
uint16_t getLayer() const { return mLayer; }
MetalAttachment withMsaaTexture(id<MTLTexture> msaa) const {
assert_invariant(mTexture != nil);
assert_invariant(mTexture.sampleCount == 1u);
MetalAttachment result = *this;
result.mMsaaTexture = msaa;
return result;
}
static MetalAttachment invalidAttachment() { return MetalAttachment(); }
private:
uint8_t mLevel = 0;
uint16_t mLayer = 0;
// The main texture for this Attachment. May be single-sampled or MSAA.
id<MTLTexture> mTexture = nil;
// The MSAA "sidecar" texture that will resolve into mTexture.
// If this is non-nil, mTexture must be single-sampled.
id<MTLTexture> mMsaaTexture = nil;
};
class MetalSwapChain : public HwSwapChain {
public:
@@ -66,14 +121,14 @@ public:
~MetalSwapChain();
// Acquires a texture that can be used to render into this SwapChain.
// The texture source depends on the type of SwapChain:
// Acquires a texture that can be used to render into this SwapChain and returns a
// MetalAttachment. 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();
id<MTLTexture> acquireDepthTexture();
id<MTLTexture> acquireStencilTexture();
// May return an invalid attachemnt if a drawable cannot be acquired.
MetalAttachment acquireDrawable();
MetalAttachment acquireDepthTexture();
MetalAttachment acquireStencilTexture();
void releaseDrawable();
@@ -88,6 +143,7 @@ public:
NSUInteger getSurfaceWidth() const;
NSUInteger getSurfaceHeight() const;
NSUInteger getSampleCount() const;
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
@@ -106,13 +162,24 @@ private:
void scheduleFrameScheduledCallback();
void scheduleFrameCompletedCallback();
MetalAttachment acquireBaseDrawable();
id<MTLTexture> ensureDepthStencilTexture(uint32_t width, uint32_t height);
id<MTLTexture> ensureMsaaColorTexture(MTLPixelFormat format, uint32_t width, uint32_t height,
uint8_t samples);
id<MTLTexture> ensureMsaaDepthStencilTexture(MTLPixelFormat format, uint32_t width,
uint32_t height, uint8_t samples);
static MTLPixelFormat decideDepthStencilFormat(uint64_t flags);
void ensureDepthStencilTexture();
static id<MTLTexture> createMultisampledTexture(MetalContext const& context,
MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples);
MetalContext& context;
PlatformMetal& platform;
id<CAMetalDrawable> drawable = nil;
id<MTLTexture> depthStencilTexture = nil;
id<MTLTexture> msaaColor = nil;
id<MTLTexture> msaaDepthStencil = nil;
id<MTLTexture> headlessDrawable = nil;
MTLPixelFormat depthStencilFormat = MTLPixelFormatInvalid;
NSUInteger headlessWidth = 0;
@@ -121,6 +188,7 @@ private:
std::shared_ptr<std::mutex> layerDrawableMutex;
MetalExternalImage externalImage;
SwapChainType type;
uint64_t flags;
int64_t abandonedUntilFrame = -1;
@@ -307,51 +375,15 @@ private:
class MetalRenderTarget : public HwRenderTarget {
public:
class Attachment {
public:
friend class MetalRenderTarget;
Attachment() = default;
Attachment(MetalTexture* metalTexture, uint8_t level = 0, uint16_t layer = 0) :
level(level), layer(layer),
texture(metalTexture->getMtlTextureForWrite()),
metalTexture(metalTexture) { }
id<MTLTexture> getTexture() const {
return texture;
}
NSUInteger getSampleCount() const {
return texture ? texture.sampleCount : 0u;
}
MTLPixelFormat getPixelFormat() const {
return texture ? texture.pixelFormat : MTLPixelFormatInvalid;
}
explicit operator bool() const {
return texture != nil;
}
struct AttachmentInfo {
MetalTexture* texture = nullptr;
uint8_t level = 0;
uint16_t layer = 0;
private:
id<MTLTexture> getMSAASidecarTexture() const {
// This should only be called from render targets associated with a MetalTexture.
assert_invariant(metalTexture);
return metalTexture->msaaSidecar;
}
id<MTLTexture> texture = nil;
MetalTexture* metalTexture = nullptr;
};
MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, uint8_t samples,
Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
Attachment depthAttachment, Attachment stencilAttachment);
AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
AttachmentInfo depthAttachment, AttachmentInfo stencilAttachment);
explicit MetalRenderTarget(MetalContext* context)
: HwRenderTarget(0, 0), context(context), defaultRenderTarget(true) {}
@@ -387,12 +419,14 @@ public:
math::uint2 getAttachmentSize() noexcept;
uint8_t getSamples() const { return samples; }
MetalAttachment getDrawColorAttachment(size_t index);
MetalAttachment getReadColorAttachment(size_t index);
MetalAttachment getDepthAttachment();
MetalAttachment getStencilAttachment();
Attachment getDrawColorAttachment(size_t index);
Attachment getReadColorAttachment(size_t index);
Attachment getDepthAttachment();
Attachment getStencilAttachment();
// Returns the number of samples that should be used in the pipeline state that renders to this
// render target. Takes into account "automagic" resolve and MSAA SwapChains.
NSUInteger getSampleCount() const;
private:
@@ -402,12 +436,12 @@ private:
MetalContext* context;
bool defaultRenderTarget = false;
uint8_t samples = 1;
Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
Attachment depth = {};
Attachment stencil = {};
MetalAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
MetalAttachment depth = {};
MetalAttachment stencil = {};
math::uint2 attachmentSize = {};
uint8_t samples = 1;
};
// MetalFence is used to implement both Fences and Syncs.
@@ -429,6 +463,8 @@ public:
API_AVAILABLE(ios(12.0))
void onSignal(MetalFenceSignalBlock block);
void cancel();
private:
MetalContext& context;

View File

@@ -116,7 +116,8 @@ MetalSwapChain::MetalSwapChain(
depthStencilFormat(decideDepthStencilFormat(flags)),
layer(nativeWindow),
layerDrawableMutex(std::make_shared<std::mutex>()),
type(SwapChainType::CAMETALLAYER) {
type(SwapChainType::CAMETALLAYER),
flags(flags) {
FILAMENT_CHECK_PRECONDITION([nativeWindow isKindOfClass:[CAMetalLayer class]])
<< "nativeWindow pointer of class "
@@ -147,7 +148,8 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform, i
depthStencilFormat(decideDepthStencilFormat(flags)),
headlessWidth(width),
headlessHeight(height),
type(SwapChainType::HEADLESS) {}
type(SwapChainType::HEADLESS),
flags(flags) {}
MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform,
CVPixelBufferRef pixelBuffer, uint64_t flags)
@@ -155,7 +157,8 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform,
platform(platform),
depthStencilFormat(decideDepthStencilFormat(flags)),
externalImage(MetalExternalImage::createFromImage(context, pixelBuffer)),
type(SwapChainType::CVPIXELBUFFERREF) {
type(SwapChainType::CVPIXELBUFFERREF),
flags(flags) {
assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
MetalExternalImage::assertWritableImage(pixelBuffer);
assert_invariant(externalImage.isValid());
@@ -167,6 +170,27 @@ MTLPixelFormat MetalSwapChain::decideDepthStencilFormat(uint64_t flags) {
: MTLPixelFormatDepth32Float;
}
id<MTLTexture> MetalSwapChain::createMultisampledTexture(MetalContext const& context,
MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples) {
MTLTextureDescriptor* descriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
width:width
height:height
mipmapped:NO];
descriptor.textureType = MTLTextureType2DMultisample;
descriptor.sampleCount = samples;
descriptor.usage = MTLTextureUsageRenderTarget;
descriptor.resourceOptions = MTLResourceStorageModePrivate;
if (context.supportsMemorylessRenderTargets) {
if (@available(macOS 11.0, *)) {
descriptor.resourceOptions = MTLResourceStorageModeMemoryless;
}
}
return [context.device newTextureWithDescriptor:descriptor];
}
MetalSwapChain::~MetalSwapChain() {
}
@@ -190,63 +214,16 @@ NSUInteger MetalSwapChain::getSurfaceHeight() const {
return (NSUInteger) layer.drawableSize.height;
}
bool MetalSwapChain::isAbandoned() const {
return context.currentFrame < abandonedUntilFrame;
NSUInteger MetalSwapChain::getSampleCount() const {
if (flags & flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return 4u;
}
return 1u;
}
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(FILAMENT_IOS)
textureDescriptor.storageMode = MTLStorageModeShared;
#else
textureDescriptor.storageMode = MTLStorageModeManaged;
#endif
headlessDrawable = [context.device newTextureWithDescriptor:textureDescriptor];
return headlessDrawable;
}
if (isPixelBuffer()) {
return externalImage.getMtlTexture();
}
assert_invariant(isCaMetalLayer());
// CAMetalLayer's drawable pool is not thread safe. Use a mutex when
// calling -nextDrawable, or when releasing the last known reference
// to any CAMetalDrawable returned from a previous -nextDrawable.
{
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
drawable = [layer nextDrawable];
}
if (UTILS_UNLIKELY(drawable == nil)) {
switch (platform.getDrawableFailureBehavior()) {
case PlatformMetal::DrawableFailureBehavior::PANIC:
FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable.";
break;
case PlatformMetal::DrawableFailureBehavior::ABORT_FRAME:
abandonedUntilFrame = context.currentFrame + 1;
return nil;
}
}
return drawable.texture;
bool MetalSwapChain::isAbandoned() const {
return context.currentFrame < abandonedUntilFrame;
}
void MetalSwapChain::releaseDrawable() {
@@ -256,32 +233,45 @@ void MetalSwapChain::releaseDrawable() {
}
}
id<MTLTexture> MetalSwapChain::acquireDepthTexture() {
ensureDepthStencilTexture();
assert_invariant(depthStencilTexture);
return depthStencilTexture;
}
id<MTLTexture> MetalSwapChain::acquireStencilTexture() {
if (!isMetalFormatStencil(depthStencilFormat)) {
return nil;
MetalAttachment MetalSwapChain::acquireDrawable() {
MetalAttachment attachment = acquireBaseDrawable();
if (auto texture = attachment.getTexture();
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return attachment.withMsaaTexture(
ensureMsaaColorTexture(texture.pixelFormat, texture.width, texture.height, 4u));
}
ensureDepthStencilTexture();
assert_invariant(depthStencilTexture);
return depthStencilTexture;
return attachment;
}
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;
}
MetalAttachment MetalSwapChain::acquireDepthTexture() {
MetalAttachment attachment =
MetalAttachment(ensureDepthStencilTexture(getSurfaceWidth(), getSurfaceHeight()));
if (auto texture = attachment.getTexture();
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return attachment.withMsaaTexture(ensureMsaaDepthStencilTexture(texture.pixelFormat,
texture.width, texture.height, 4u));
}
return attachment;
}
MetalAttachment MetalSwapChain::acquireStencilTexture() {
if (!(flags & SwapChain::CONFIG_HAS_STENCIL_BUFFER)) {
return MetalAttachment::invalidAttachment();
}
MetalAttachment attachment =
MetalAttachment(ensureDepthStencilTexture(getSurfaceWidth(), getSurfaceHeight()));
if (auto texture = attachment.getTexture();
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
return attachment.withMsaaTexture(ensureMsaaDepthStencilTexture(texture.pixelFormat,
texture.width, texture.height, 4u));
}
return attachment;
}
id<MTLTexture> MetalSwapChain::ensureDepthStencilTexture(uint32_t width, uint32_t height) {
if (UTILS_LIKELY(depthStencilTexture && depthStencilTexture.width == width &&
depthStencilTexture.height == height)) {
return depthStencilTexture;
}
MTLTextureDescriptor* descriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:depthStencilFormat
@@ -290,7 +280,26 @@ void MetalSwapChain::ensureDepthStencilTexture() {
mipmapped:NO];
descriptor.usage = MTLTextureUsageRenderTarget;
descriptor.resourceOptions = MTLResourceStorageModePrivate;
depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
return depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
}
id<MTLTexture> MetalSwapChain::ensureMsaaColorTexture(MTLPixelFormat format, uint32_t width, uint32_t height,
uint8_t samples) {
if (UTILS_LIKELY(msaaColor && msaaColor.pixelFormat == format && msaaColor.width == width &&
msaaColor.height == height && msaaColor.sampleCount == samples)) {
return msaaColor;
}
return msaaColor = createMultisampledTexture(context, format, width, height, samples);
}
id<MTLTexture> MetalSwapChain::ensureMsaaDepthStencilTexture(MTLPixelFormat format, uint32_t width,
uint32_t height, uint8_t samples) {
if (UTILS_LIKELY(msaaDepthStencil && msaaDepthStencil.pixelFormat == format &&
msaaDepthStencil.width == width && msaaDepthStencil.height == height &&
msaaDepthStencil.sampleCount == samples)) {
return msaaDepthStencil;
}
return msaaDepthStencil = createMultisampledTexture(context, format, width, height, samples);
}
void MetalSwapChain::setFrameScheduledCallback(
@@ -454,6 +463,62 @@ void MetalSwapChain::scheduleFrameCompletedCallback() {
}];
}
MetalAttachment MetalSwapChain::acquireBaseDrawable() {
if (drawable) {
return MetalAttachment(drawable.texture);
}
if (isHeadless()) {
if (headlessDrawable) {
return MetalAttachment(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(FILAMENT_IOS)
textureDescriptor.storageMode = MTLStorageModeShared;
#else
textureDescriptor.storageMode = MTLStorageModeManaged;
#endif
headlessDrawable = [context.device newTextureWithDescriptor:textureDescriptor];
return MetalAttachment(headlessDrawable);
}
if (isPixelBuffer()) {
return MetalAttachment(externalImage.getMtlTexture());
}
assert_invariant(isCaMetalLayer());
// CAMetalLayer's drawable pool is not thread safe. Use a mutex when
// calling -nextDrawable, or when releasing the last known reference
// to any CAMetalDrawable returned from a previous -nextDrawable.
{
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
drawable = [layer nextDrawable];
}
if (UTILS_UNLIKELY(drawable == nil)) {
switch (platform.getDrawableFailureBehavior()) {
case PlatformMetal::DrawableFailureBehavior::PANIC:
FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable.";
break;
case PlatformMetal::DrawableFailureBehavior::ABORT_FRAME:
abandonedUntilFrame = context.currentFrame + 1;
return MetalAttachment::invalidAttachment();
}
}
return MetalAttachment(drawable.texture);
}
MetalBufferObject::MetalBufferObject(MetalContext& context, BufferObjectBinding bindingType,
BufferUsage usage, uint32_t byteCount)
: HwBufferObject(byteCount), buffer(context, bindingType, usage, byteCount) {}
@@ -970,90 +1035,108 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
}
MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height,
uint8_t samples, Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
Attachment depthAttachment, Attachment stencilAttachment) :
HwRenderTarget(width, height), context(context), samples(samples) {
math::uint2 tmin = {std::numeric_limits<uint32_t>::max()};
UTILS_UNUSED_IN_RELEASE math::uint2 tmax = {0};
uint8_t samples, AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
AttachmentInfo depthAttachment, AttachmentInfo stencilAttachment)
: HwRenderTarget(width, height),
context(context),
samples(samples) {
math::uint2 tmin = { std::numeric_limits<uint32_t>::max() };
UTILS_UNUSED_IN_RELEASE math::uint2 tmax = { 0 };
UTILS_UNUSED_IN_RELEASE size_t attachmentCount = 0;
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (!colorAttachments[i]) {
MetalTexture* const texture = colorAttachments[i].texture;
const auto& level = colorAttachments[i].level;
const auto& layer = colorAttachments[i].layer;
if (!texture) {
continue;
}
color[i] = colorAttachments[i];
FILAMENT_CHECK_PRECONDITION(color[i].getSampleCount() <= samples)
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
<< "MetalRenderTarget was initialized with a MSAA COLOR" << i
<< " texture, but sample count is " << samples << ".";
auto t = color[i].metalTexture;
const auto twidth = std::max(1u, t->width >> color[i].level);
const auto theight = std::max(1u, t->height >> color[i].level);
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
attachmentCount++;
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
color[i] = MetalAttachment(mtlTexture, level, layer);
// If we were given a single-sampled texture but the samples parameter is > 1, we create
// a multisampled sidecar texture and do a resolve automatically.
if (samples > 1 && color[i].getSampleCount() == 1) {
auto& sidecar = color[i].metalTexture->msaaSidecar;
if (samples > 1 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(color[i].getPixelFormat(),
color[i].metalTexture->width, color[i].metalTexture->height, samples);
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
}
color[i] = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
if (depthAttachment) {
depth = depthAttachment;
if (depthAttachment.texture) {
MetalTexture* const texture = depthAttachment.texture;
const auto& level = depthAttachment.level;
const auto& layer = depthAttachment.layer;
FILAMENT_CHECK_PRECONDITION(depth.getSampleCount() <= samples)
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
<< "MetalRenderTarget was initialized with a MSAA DEPTH texture, but sample count "
"is "
<< samples << ".";
auto t = depth.metalTexture;
const auto twidth = std::max(1u, t->width >> depth.level);
const auto theight = std::max(1u, t->height >> depth.level);
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
tmin = { math::min(tmin.x, twidth), math::min(tmin.y, theight) };
tmax = { math::max(tmax.x, twidth), math::max(tmax.y, theight) };
attachmentCount++;
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
depth = MetalAttachment(mtlTexture, level, layer);
// If we were given a single-sampled texture but the samples parameter is > 1, we create
// a multisampled sidecar texture and do a resolve automatically.
if (samples > 1 && depth.getSampleCount() == 1) {
auto& sidecar = depth.metalTexture->msaaSidecar;
if (samples > 1 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(depth.getPixelFormat(),
depth.metalTexture->width, depth.metalTexture->height, samples);
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
}
depth = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
if (stencilAttachment) {
stencil = stencilAttachment;
if (stencilAttachment.texture) {
// stencil = stencilAttachment;
MetalTexture* const texture = stencilAttachment.texture;
const auto& level = stencilAttachment.level;
const auto& layer = stencilAttachment.layer;
FILAMENT_CHECK_PRECONDITION(stencil.getSampleCount() <= samples)
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
<< "MetalRenderTarget was initialized with a MSAA STENCIL texture, but sample "
"count is "
<< samples << ".";
auto t = stencil.metalTexture;
const auto twidth = std::max(1u, t->width >> stencil.level);
const auto theight = std::max(1u, t->height >> stencil.level);
const auto twidth = std::max(1u, texture->width >> level);
const auto theight = std::max(1u, texture->height >> level);
tmin = { math::min(tmin.x, twidth), math::min(tmin.y, theight) };
tmax = { math::max(tmax.x, twidth), math::max(tmax.y, theight) };
attachmentCount++;
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
stencil = MetalAttachment(mtlTexture, level, layer);
// If we were given a single-sampled texture but the samples parameter is > 1, we create
// a multisampled sidecar texture and do a resolve automatically.
if (samples > 1 && stencil.getSampleCount() == 1) {
auto& sidecar = stencil.metalTexture->msaaSidecar;
if (samples > 1 && texture->samples == 1) {
auto& sidecar = texture->msaaSidecar;
if (!sidecar) {
sidecar = createMultisampledTexture(stencil.getPixelFormat(),
stencil.metalTexture->width, stencil.metalTexture->height, samples);
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
texture->height, samples);
}
stencil = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
}
}
@@ -1076,105 +1159,105 @@ void MetalRenderTarget::setUpRenderPassAttachments(MTLRenderPassDescriptor* desc
const auto discardFlags = params.flags.discardEnd;
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
Attachment attachment = getDrawColorAttachment(i);
MetalAttachment attachment = getDrawColorAttachment(i);
if (!attachment) {
continue;
}
descriptor.colorAttachments[i].texture = attachment.getTexture();
descriptor.colorAttachments[i].level = attachment.level;
descriptor.colorAttachments[i].slice = attachment.layer;
descriptor.colorAttachments[i].level = attachment.getLevel();
descriptor.colorAttachments[i].slice = attachment.getLayer();
descriptor.colorAttachments[i].loadAction = getLoadAction(params, getTargetBufferFlagsAt(i));
descriptor.colorAttachments[i].storeAction = getStoreAction(params,
getTargetBufferFlagsAt(i));
descriptor.colorAttachments[i].clearColor = MTLClearColorMake(
params.clearColor.r, params.clearColor.g, params.clearColor.b, params.clearColor.a);
const bool automaticResolve = samples > 1 && attachment.getSampleCount() == 1;
if (automaticResolve) {
if (attachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
// We should not be attempting to load anything into the MSAA texture.
assert_invariant(descriptor.colorAttachments[i].loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
// which is not supported. In that case, ignore the load action.
if (descriptor.colorAttachments[i].loadAction == MTLLoadActionLoad) {
descriptor.colorAttachments[i].loadAction = MTLLoadActionDontCare;
}
id<MTLTexture> sidecar = attachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.colorAttachments[i].texture = sidecar;
descriptor.colorAttachments[i].texture = attachment.getMsaaTexture();
descriptor.colorAttachments[i].level = 0;
descriptor.colorAttachments[i].slice = 0;
const bool discard = any(discardFlags & getTargetBufferFlagsAt(i));
if (!discard) {
descriptor.colorAttachments[i].resolveTexture = attachment.texture;
descriptor.colorAttachments[i].resolveLevel = attachment.level;
descriptor.colorAttachments[i].resolveSlice = attachment.layer;
descriptor.colorAttachments[i].resolveTexture = attachment.getTexture();
descriptor.colorAttachments[i].resolveLevel = attachment.getLevel();
descriptor.colorAttachments[i].resolveSlice = attachment.getLayer();
descriptor.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
}
}
}
Attachment depthAttachment = getDepthAttachment();
MetalAttachment depthAttachment = getDepthAttachment();
if (depthAttachment) {
descriptor.depthAttachment.texture = depthAttachment.getTexture();
descriptor.depthAttachment.level = depthAttachment.level;
descriptor.depthAttachment.slice = depthAttachment.layer;
descriptor.depthAttachment.level = depthAttachment.getLevel();
descriptor.depthAttachment.slice = depthAttachment.getLayer();
descriptor.depthAttachment.loadAction = getLoadAction(params, TargetBufferFlags::DEPTH);
descriptor.depthAttachment.storeAction = getStoreAction(params, TargetBufferFlags::DEPTH);
descriptor.depthAttachment.clearDepth = params.clearDepth;
}
const bool depthAutomaticResolve = samples > 1 && depthAttachment.getSampleCount() == 1;
if (depthAutomaticResolve) {
if (depthAttachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
// We should not be attempting to load anything into the MSAA texture.
assert_invariant(descriptor.depthAttachment.loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
// which is not supported. In that case, ignore the load action.
if (descriptor.depthAttachment.loadAction == MTLLoadActionLoad) {
descriptor.depthAttachment.loadAction = MTLLoadActionDontCare;
}
id<MTLTexture> sidecar = depthAttachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.depthAttachment.texture = sidecar;
descriptor.depthAttachment.texture = depthAttachment.getMsaaTexture();
descriptor.depthAttachment.level = 0;
descriptor.depthAttachment.slice = 0;
const bool discard = any(discardFlags & TargetBufferFlags::DEPTH);
if (!discard) {
assert_invariant(context->supportsAutoDepthResolve);
descriptor.depthAttachment.resolveTexture = depthAttachment.getTexture();
descriptor.depthAttachment.resolveLevel = depthAttachment.level;
descriptor.depthAttachment.resolveSlice = depthAttachment.layer;
descriptor.depthAttachment.resolveLevel = depthAttachment.getLevel();
descriptor.depthAttachment.resolveSlice = depthAttachment.getLayer();
descriptor.depthAttachment.storeAction = MTLStoreActionMultisampleResolve;
}
}
Attachment stencilAttachment = getStencilAttachment();
MetalAttachment stencilAttachment = getStencilAttachment();
if (stencilAttachment) {
descriptor.stencilAttachment.texture = stencilAttachment.getTexture();
descriptor.stencilAttachment.level = stencilAttachment.level;
descriptor.stencilAttachment.slice = stencilAttachment.layer;
descriptor.stencilAttachment.level = stencilAttachment.getLevel();
descriptor.stencilAttachment.slice = stencilAttachment.getLayer();
descriptor.stencilAttachment.loadAction = getLoadAction(params, TargetBufferFlags::STENCIL);
descriptor.stencilAttachment.storeAction = getStoreAction(params, TargetBufferFlags::STENCIL);
descriptor.stencilAttachment.clearStencil = params.clearStencil;
}
const bool stencilAutomaticResolve = samples > 1 && stencilAttachment.getSampleCount() == 1;
if (stencilAutomaticResolve) {
if (stencilAttachment.getMsaaTexture()) {
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
// We should not be attempting to load anything into the MSAA texture.
assert_invariant(descriptor.stencilAttachment.loadAction != MTLLoadActionLoad);
assert_invariant(!defaultRenderTarget);
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
// which is not supported. In that case, ignore the load action.
if (descriptor.stencilAttachment.loadAction == MTLLoadActionLoad) {
descriptor.stencilAttachment.loadAction = MTLLoadActionDontCare;
}
id<MTLTexture> sidecar = stencilAttachment.getMSAASidecarTexture();
assert_invariant(sidecar);
descriptor.stencilAttachment.texture = sidecar;
descriptor.stencilAttachment.texture = stencilAttachment.getMsaaTexture();
descriptor.stencilAttachment.level = 0;
descriptor.stencilAttachment.slice = 0;
const bool discard = any(discardFlags & TargetBufferFlags::STENCIL);
if (!discard) {
assert_invariant(context->supportsAutoDepthResolve);
descriptor.stencilAttachment.resolveTexture = stencilAttachment.getTexture();
descriptor.stencilAttachment.resolveLevel = stencilAttachment.level;
descriptor.stencilAttachment.resolveSlice = stencilAttachment.layer;
descriptor.stencilAttachment.resolveLevel = stencilAttachment.getLevel();
descriptor.stencilAttachment.resolveSlice = stencilAttachment.getLayer();
descriptor.stencilAttachment.storeAction = MTLStoreActionMultisampleResolve;
if (@available(iOS 12.0, *)) {
descriptor.stencilAttachment.stencilResolveFilter = MTLMultisampleStencilResolveFilterSample0;
@@ -1189,44 +1272,46 @@ bool MetalRenderTarget::involvesAbandonedSwapChain() const noexcept {
return (draw && draw->isAbandoned()) || (read && read->isAbandoned());
}
MetalRenderTarget::Attachment MetalRenderTarget::getDrawColorAttachment(size_t index) {
MetalAttachment MetalRenderTarget::getDrawColorAttachment(size_t index) {
assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
Attachment result = color[index];
if (index == 0 && defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
result.texture = context->currentDrawSwapChain->acquireDrawable();
// acquireDrawable may fail to acquire the drawable, in which case result.texture will be
// nil, and an invalid attachment
return context->currentDrawSwapChain->acquireDrawable();
}
return result;
return color[index];
}
MetalRenderTarget::Attachment MetalRenderTarget::getReadColorAttachment(size_t index) {
MetalAttachment MetalRenderTarget::getReadColorAttachment(size_t index) {
assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
Attachment result = color[index];
if (index == 0 && defaultRenderTarget) {
assert_invariant(context->currentReadSwapChain);
result.texture = context->currentReadSwapChain->acquireDrawable();
// acquireDrawable may fail to acquire the drawable, in which case result.texture will be
// nil, and an invalid attachment
return context->currentReadSwapChain->acquireDrawable();
}
return result;
return color[index];
}
MetalRenderTarget::Attachment MetalRenderTarget::getDepthAttachment() {
Attachment result = depth;
MetalAttachment MetalRenderTarget::getDepthAttachment() {
if (defaultRenderTarget) {
result.texture = context->currentDrawSwapChain->acquireDepthTexture();
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->acquireDepthTexture();
}
return result;
return depth;
}
MetalRenderTarget::Attachment MetalRenderTarget::getStencilAttachment() {
Attachment result = stencil;
MetalAttachment MetalRenderTarget::getStencilAttachment() {
if (defaultRenderTarget) {
result.texture = context->currentDrawSwapChain->acquireStencilTexture();
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->acquireStencilTexture();
}
return result;
return stencil;
}
NSUInteger MetalRenderTarget::getSampleCount() const {
if (defaultRenderTarget) {
assert_invariant(context->currentDrawSwapChain);
return context->currentDrawSwapChain->getSampleCount();
}
return samples;
}
MTLLoadAction MetalRenderTarget::getLoadAction(const RenderPassParams& params,
@@ -1310,20 +1395,30 @@ void MetalFence::onSignal(MetalFenceSignalBlock block) {
FenceStatus MetalFence::wait(uint64_t timeoutNs) {
if (@available(iOS 12, *)) {
using ns = std::chrono::nanoseconds;
// keep a reference on the stack so our state cannot be destroyed while we wait
auto state = this->state;
std::unique_lock<std::mutex> guard(state->mutex);
while (state->status == FenceStatus::TIMEOUT_EXPIRED) {
if (timeoutNs == FENCE_WAIT_FOR_EVER) {
state->cv.wait(guard);
} else if (timeoutNs == 0 ||
state->cv.wait_for(guard, ns(timeoutNs)) == std::cv_status::timeout) {
return FenceStatus::TIMEOUT_EXPIRED;
return state->status;
}
}
return FenceStatus::CONDITION_SATISFIED;
return state->status;
}
return FenceStatus::ERROR;
}
void MetalFence::cancel() {
std::unique_lock guard(state->mutex);
state->status = FenceStatus::ERROR;
state->cv.notify_all();
}
MetalDescriptorSetLayout::MetalDescriptorSetLayout(DescriptorSetLayout&& l) noexcept
: mLayout(std::move(l)) {
size_t dynamicBindings = 0;

View File

@@ -303,13 +303,10 @@ void GLDescriptorSet::bind(
offset += offsets[dynamicOffsetIndex++];
}
if (arg.bo) {
auto buffer = static_cast<char const*>(arg.bo->gl.buffer) + offset;
p.updateUniforms(bindingPoint, arg.bo->gl.id, buffer, arg.bo->age);
p.updateUniforms(bindingPoint, arg.bo->gl.id, arg.bo->gl.buffer, arg.bo->age, offset);
}
} else if constexpr (std::is_same_v<T, Sampler>) {
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.handle) {
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);

View File

@@ -2287,9 +2287,14 @@ mat3f OpenGLDriver::getStreamTransformMatrix(Handle<HwStream> sh) {
void OpenGLDriver::destroyFence(Handle<HwFence> fh) {
if (fh) {
GLFence const* f = handle_cast<GLFence*>(fh);
GLFence const* const f = handle_cast<GLFence*>(fh);
if (mPlatform.canCreateFence() || mContext.isES2()) {
mPlatform.destroyFence(f->fence);
} else {
// signal waiters it's time to give-up
std::unique_lock const lock(f->state->lock);
f->state->status = FenceStatus::ERROR;
f->state->cond.notify_all();
}
destruct(fh, f);
}

View File

@@ -262,20 +262,25 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
}
void OpenGLProgram::updateUniforms(
uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept {
uint32_t const index, GLuint const id, void const* buffer,
uint16_t const age, uint32_t const offset) const noexcept {
assert_invariant(mUniformsRecords);
assert_invariant(buffer);
// only update the uniforms if the UBO has changed since last time we updated
UniformsRecord const& records = mUniformsRecords[index];
if (records.id == id && records.age == age) {
if (records.id == id && records.age == age && records.offset == offset) {
return;
}
records.id = id;
records.age = age;
records.offset = offset;
assert_invariant(records.uniforms.size() == records.locations.size());
// apply the offset to the buffer
buffer = static_cast<char const*>(buffer) + offset;
for (size_t i = 0, c = records.uniforms.size(); i < c; i++) {
Program::Uniform const& u = records.uniforms[i];
GLint const loc = records.locations[i];

View File

@@ -88,7 +88,7 @@ public:
}
// For ES2 only
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept;
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age, uint32_t offset) const noexcept;
void setRec709ColorSpace(bool rec709) const noexcept;
PushConstantBundle getPushConstants() {
@@ -123,6 +123,7 @@ private:
LocationInfo locations;
mutable GLuint id = 0;
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
mutable uint32_t offset = 0;
};
UniformsRecord const* mUniformsRecords = nullptr;
GLint mRec709Location : 24; // 4 bytes

View File

@@ -567,7 +567,15 @@ void PlatformEGLAndroid::destroyStream(Stream* stream) noexcept {
}
Platform::Sync* PlatformEGLAndroid::createSync() noexcept {
auto const sync = eglCreateSyncKHR(getEglDisplay(), EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
EGLSyncKHR sync = EGL_NO_SYNC_KHR;
if (UTILS_LIKELY(ext.egl.ANDROID_native_fence_sync)) {
sync = eglCreateSyncKHR(getEglDisplay(), EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
if (sync == EGL_NO_SYNC_KHR) {
LOG(ERROR) << "Failed to create sync: " << eglGetError();
}
} else {
LOG(WARNING) << "Native fences not supported on this device.";
}
return new(std::nothrow) SyncEGLAndroid{ .sync = sync };
}
@@ -575,10 +583,16 @@ bool PlatformEGLAndroid::convertSyncToFd(Sync* sync, int* fd) noexcept {
assert_invariant(sync && fd);
if (UTILS_UNLIKELY(!ext.egl.ANDROID_native_fence_sync)) {
LOG(WARNING) << "Native fences not supported, cannot convert to fd.";
return false;
}
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
if (eglSync.sync == EGL_NO_SYNC_KHR) {
LOG(ERROR) << "Invalid fence, cannot convert to fd.";
return false;
}
*fd = eglDupNativeFenceFDANDROID(getEglDisplay(), eglSync.sync);
// In the case where there was no native FD, -1 is returned. Return false
// to indicate there was an error in this case.
@@ -591,8 +605,12 @@ bool PlatformEGLAndroid::convertSyncToFd(Sync* sync, int* fd) noexcept {
void PlatformEGLAndroid::destroySync(Sync* sync) noexcept {
assert_invariant(sync);
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
eglDestroySyncKHR(getEglDisplay(), eglSync.sync);
if (UTILS_LIKELY(ext.egl.ANDROID_native_fence_sync)) {
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
if (eglSync.sync != EGL_NO_SYNC_KHR) {
eglDestroySyncKHR(getEglDisplay(), eglSync.sync);
}
}
delete sync;
}
@@ -702,6 +720,10 @@ void PlatformEGLAndroid::SwapChainEGLAndroid::terminate(PlatformEGLAndroid& plat
}
bool PlatformEGLAndroid::SwapChainEGLAndroid::setPresentFrameId(uint64_t frameId) const noexcept {
if (!nativeWindow) {
// nativeWindow is null in the headless case
return false;
}
return mImpl.setPresentFrameId(nativeWindow, frameId);
}

View File

@@ -26,6 +26,8 @@
#include <dlfcn.h>
#include <mutex>
#define LIBRARY_GLX "libGL.so.1"
#define LIBRARY_X11 "libX11.so.6"

View File

@@ -32,27 +32,31 @@ namespace filament::backend {
FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
std::chrono::steady_clock::time_point const until) {
{
std::shared_lock l(mLock);
// this lock MUST be held for READ when calling vkWaitForFences()
std::shared_lock rl(mLock);
// If the vulkan fence has not been submitted yet, we need to wait for that before we
// can use vkWaitForFences()
if (mStatus == VK_INCOMPLETE) {
bool const success = mCond.wait_until(l, until, [this] {
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted".
// When this fence gets submitted, its status changes to VK_NOT_READY.
return mStatus != VK_INCOMPLETE;
});
if (!success) {
// !success indicates a timeout
return FenceStatus::TIMEOUT_EXPIRED;
}
// If the vulkan fence has not been submitted yet, we need to wait for that before we
// can use vkWaitForFences()
if (mStatus == VK_INCOMPLETE) {
bool const success = mCond.wait_until(rl, until, [this] {
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted".
// When this fence gets submitted, its status changes to VK_NOT_READY.
return mStatus != VK_INCOMPLETE || mCanceled;
});
if (!success) {
// !success indicates a timeout or cancel
return mCanceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
}
}
// The fence could have already signaled, avoid calling into vkWaitForFences()
if (mStatus == VK_SUCCESS) {
return FenceStatus::CONDITION_SATISFIED;
}
// The fence could have already signaled, avoid calling into vkWaitForFences()
if (mStatus == VK_SUCCESS) {
return FenceStatus::CONDITION_SATISFIED;
}
// Or it could have been canceled, return immediately
if (mCanceled) {
return FenceStatus::ERROR;
}
// If we're here, we know that vkQueueSubmit has been called (because it sets the status
@@ -62,13 +66,14 @@ FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
// place simultaneously. vkResetFence is only called once it knows the fence has signaled,
// which guaranties that vkResetFence won't have to wait too long, just enough for
// all the vkWaitForFences() to return.
VkResult status = vkWaitForFences(device, 1, &mFence, VK_TRUE, timeout);
VkResult const status = vkWaitForFences(device, 1, &mFence, VK_TRUE, timeout);
if (status == VK_TIMEOUT) {
return FenceStatus::TIMEOUT_EXPIRED;
}
if (status == VK_SUCCESS) {
std::lock_guard const l(mLock);
rl.unlock();
std::lock_guard const wl(mLock);
mStatus = status;
return FenceStatus::CONDITION_SATISFIED;
}

View File

@@ -56,9 +56,16 @@ struct VulkanCmdFence {
FenceStatus wait(VkDevice device, uint64_t timeout,
std::chrono::steady_clock::time_point until);
void cancel() {
std::lock_guard const l(mLock);
mCanceled = true;
mCond.notify_all();
}
private:
std::shared_mutex mLock; // NOLINT(*-include-cleaner)
std::condition_variable_any mCond;
bool mCanceled = false;
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
// gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
// finishes executing the command buffer, the status changes to VK_SUCCESS.
@@ -70,28 +77,43 @@ struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource {
VulkanFence() {}
void setFence(std::shared_ptr<VulkanCmdFence> fence) {
std::lock_guard lock(mState->lock);
std::lock_guard const lock(mState->lock);
mState->sharedFence = std::move(fence);
mState->cond.notify_all();
}
std::shared_ptr<VulkanCmdFence>& getSharedFence() {
std::lock_guard lock(mState->lock);
std::lock_guard const lock(mState->lock);
return mState->sharedFence;
}
std::shared_ptr<VulkanCmdFence> wait(std::chrono::steady_clock::time_point const until) {
std::pair<std::shared_ptr<VulkanCmdFence>, bool>
wait(std::chrono::steady_clock::time_point const until) {
// hold a reference so that our state doesn't disappear while we wait
std::shared_ptr state{ mState };
std::unique_lock lock(state->lock);
state->cond.wait_until(lock, until, [&state] { return bool(state->sharedFence); });
state->cond.wait_until(lock, until, [&state] {
return bool(state->sharedFence) || state->canceled;
});
// here mSharedFence will be null if we timed out
return state->sharedFence;
return { state->sharedFence, state->canceled };
}
void cancel() const {
std::shared_ptr const state{ mState };
std::unique_lock const lock(state->lock);
if (state->sharedFence) {
state->sharedFence->cancel();
}
state->canceled = true;
state->cond.notify_all();
}
private:
struct State {
std::mutex lock;
std::condition_variable cond;
bool canceled = false;
std::shared_ptr<VulkanCmdFence> sharedFence;
};
std::shared_ptr<State> mState{ std::make_shared<State>() };

View File

@@ -1156,6 +1156,7 @@ void VulkanDriver::updateStreams(CommandStream* driver) {
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
fence->cancel();
fence.dec();
}
@@ -1164,6 +1165,10 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> const fh) {
}
FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout) {
if (!fh) {
return FenceStatus::ERROR;
}
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
// we have to take into account that the STL's wait_for() actually works with
@@ -1180,9 +1185,9 @@ FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout
until = now + nanoseconds(timeout);
}
std::shared_ptr const cmdfence = fence->wait(until);
if (!cmdfence) {
return FenceStatus::TIMEOUT_EXPIRED;
auto const [cmdfence, canceled] = fence->wait(until);
if (!cmdfence || canceled) {
return canceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
}
// now we are holding a reference to our VulkanCmdFence, so we know it can't

View File

@@ -111,12 +111,12 @@ void BackendTest::flushAndWait() {
getDriver().purge();
}
Handle<HwSwapChain> BackendTest::createSwapChain() {
Handle<HwSwapChain> BackendTest::createSwapChain(uint64_t flags) {
const NativeView& view = getNativeView();
if (!view.ptr) {
return getDriverApi().createSwapChainHeadless(view.width, view.height, 0);
return getDriverApi().createSwapChainHeadless(view.width, view.height, flags);
}
return getDriverApi().createSwapChain(view.ptr, 0);
return getDriverApi().createSwapChain(view.ptr, flags);
}
PipelineState BackendTest::getColorWritePipelineState() {

View File

@@ -60,7 +60,7 @@ protected:
void executeCommands();
void flushAndWait();
filament::backend::Handle<filament::backend::HwSwapChain> createSwapChain();
filament::backend::Handle<filament::backend::HwSwapChain> createSwapChain(uint64_t flags = 0);
static filament::backend::PipelineState getColorWritePipelineState();

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BackendTest.h"
#include "ImageExpectations.h"
#include "Lifetimes.h"
#include "Shader.h"
#include "Skip.h"
#include "SharedShaders.h"
#include "TrianglePrimitive.h"
namespace test {
using namespace filament;
using namespace filament::backend;
using namespace filament::math;
// Copied from filament/SwapChain.h
static constexpr uint64_t CONFIG_MSAA_4_SAMPLES = backend::SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES;
// Tests are parameterized by headless versus native SwapChain.
struct MsaaSwapChainTest : public BackendTest, public testing::WithParamInterface<bool> {};
TEST_P(MsaaSwapChainTest, Basic) {
SKIP_IF(Backend::OPENGL, "OpenGL does not support MSAA SwapChain on all platforms.");
SKIP_IF(Backend::VULKAN, "Vulkan does not support MSAA SwapChain on all platforms.");
constexpr int kRenderTargetSize = 512;
auto useHeadlessSwapChain = GetParam();
auto& api = getDriverApi();
api.startCapture(0);
Cleanup cleanup(api);
auto swapChain = [&]() {
auto flags = CONFIG_MSAA_4_SAMPLES;
if (useHeadlessSwapChain) {
return cleanup.add(
api.createSwapChainHeadless(kRenderTargetSize, kRenderTargetSize, flags));
}
return cleanup.add(createSwapChain(flags));
}();
api.makeCurrent(swapChain, swapChain);
RenderTargetHandle renderTarget = cleanup.add(api.createDefaultRenderTarget());
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{
.mVertexType = VertexShaderType::Noop,
.mFragmentType = FragmentShaderType::White,
});
TrianglePrimitive triangle(api);
RenderPassParams params = getClearColorRenderPass();
params.viewport = getFullViewport();
PipelineState ps = getColorWritePipelineState();
shader.addProgramToPipelineState(ps);
{
RenderFrame frame(api);
api.beginRenderPass(renderTarget, params);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(ps);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(renderTarget,
ScreenshotParams(kRenderTargetSize, kRenderTargetSize, "MsaaSwapChain", 0));
api.commit(swapChain);
}
api.stopCapture(0);
}
// Helper to give names to the tests.
static std::string testNameGenerator(const testing::TestParamInfo<bool>& info) {
const bool useHeadlessSwapChain = info.param;
return useHeadlessSwapChain ? "HeadlessSwapChain" : "NativeSwapChain";
}
INSTANTIATE_TEST_SUITE_P(MsaaSwapChainTests, MsaaSwapChainTest, testing::Values(true, false),
testNameGenerator);
} // namespace test

View File

@@ -238,8 +238,11 @@ public:
* given sample points within each pixel. Only supported when isMSAASwapChainSupported(4) is
* true.
*
* This is only supported by EGL(Android). Other GL platforms (GLX, WGL, etc) don't support it
* because the swapchain MSAA settings must be configured before window creation.
* This is supported by EGL(Android) and Metal. Other GL platforms (GLX, WGL, etc) don't support
* it because the swapchain MSAA settings must be configured before window creation.
*
* With Metal, this flag should only be used when rendering a single View into a SwapChain. This
* flag is not supported when rendering multiple Filament Views into this SwapChain.
*
* @see isMSAASwapChainSupported(4)
*/

View File

@@ -39,6 +39,7 @@
#include <stdint.h>
#include <stddef.h>
#include <details/Engine.h>
namespace filament {
@@ -57,23 +58,33 @@ FrameInfoManager::FrameInfoManager(DriverApi& driver) noexcept
FrameInfoManager::~FrameInfoManager() noexcept = default;
void FrameInfoManager::terminate(DriverApi& driver) noexcept {
void FrameInfoManager::terminate(FEngine& engine) noexcept {
DriverApi& driver = engine.getDriverApi();
if (mHasTimerQueries) {
for (auto const& query : mQueries) {
driver.destroyTimerQuery(query.handle);
}
}
// wait for all pending callbacks to be called & terminate the thread
mJobQueue.drainAndExit();
// destroy the fences that are still alive
// Destroy the fences that are still alive, they will error out.
for (size_t i = 0, c = mFrameTimeHistory.size(); i < c; i++) {
auto& info = mFrameTimeHistory[i];
if (info.fence) {
driver.destroyFence(std::move(info.fence));
}
}
// for extra safety submit the current command buffer (because nothing else will while we
// wait in drainAndExit()), this is in case the backend is already waiting on a h/w fence
// e.g. vkWaitForFences().
driver.flush();
// make sure the driver commands above will be processed
engine.flush();
// wait for all pending callbacks to be called & terminate the thread
mJobQueue.drainAndExit();
}
void FrameInfoManager::beginFrame(DriverApi& driver, Config const& config,

View File

@@ -234,7 +234,9 @@ public:
explicit FrameInfoManager(backend::DriverApi& driver) noexcept;
~FrameInfoManager() noexcept;
void terminate(backend::DriverApi& driver) noexcept;
// The command queue must be empty before calling terminate()
void terminate(FEngine& engine) noexcept;
// call this immediately after "make current"
void beginFrame(backend::DriverApi& driver, Config const& config,

View File

@@ -682,7 +682,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::transparentPicking(FrameGrap
data.picking = builder.createTexture("Picking Buffer", {
.width = width, .height = height,
.format = isFL0 ? TextureFormat::RGBA8 : TextureFormat::RG32F });
.format = isFL0 ? TextureFormat::RGBA8 : TextureFormat::RG32UI });
data.picking = builder.write(data.picking,
FrameGraphTexture::Usage::COLOR_ATTACHMENT);

View File

@@ -184,7 +184,8 @@ void FRenderer::terminate(FEngine& engine) {
// to initialize themselves, otherwise the engine tries to destroy invalid handles.
engine.execute();
}
mFrameInfoManager.terminate(driver);
mFrameInfoManager.terminate(engine);
mFrameSkipper.terminate(driver);
mResourceAllocator->terminate();
}
@@ -220,6 +221,9 @@ std::pair<Handle<HwRenderTarget>, TargetBufferFlags>
if (!outTarget) {
outTarget = mRenderTargetHandle;
outAttachmentMask = TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH;
if (mSwapChain->hasStencilBuffer()) {
outAttachmentMask |= TargetBufferFlags::STENCIL;
}
}
return { outTarget, outAttachmentMask };
}

View File

@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.66.2"
spec.version = "1.67.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.66.2/filament-v1.66.2-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.67.0/filament-v1.67.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {

View File

@@ -28,7 +28,7 @@
namespace filament {
// update this when a new version of filament wouldn't work with older materials
static constexpr size_t MATERIAL_VERSION = 66;
static constexpr size_t MATERIAL_VERSION = 67;
/**
* Supported shading models

View File

@@ -834,8 +834,8 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
// Write back the active feature level.
config.featureLevel = mFilamentApp->mEngine->getActiveFeatureLevel();
mSwapChain = mFilamentApp->mEngine->createSwapChain(
nativeSwapChain, filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
mSwapChain = mFilamentApp->mEngine->createSwapChain(nativeSwapChain,
filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
}
mRenderer = mFilamentApp->mEngine->createRenderer();

View File

@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.66.2",
"version": "1.67.0",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",