Compare commits
12 Commits
ImmediateG
...
rc/1.67.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c3cdbb7fe | ||
|
|
85d0543eb1 | ||
|
|
b03180650c | ||
|
|
d9fd0823ae | ||
|
|
d1c331529e | ||
|
|
8b97948149 | ||
|
|
6e3da09ec2 | ||
|
|
a27cb66257 | ||
|
|
230a5d8a93 | ||
|
|
66186ba396 | ||
|
|
40a66263ed | ||
|
|
65200838b9 |
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ...);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#define LIBRARY_GLX "libGL.so.1"
|
||||
#define LIBRARY_X11 "libX11.so.6"
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>() };
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
BIN
filament/backend/test/expected_images/MsaaSwapChain.png
Normal file
BIN
filament/backend/test/expected_images/MsaaSwapChain.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
103
filament/backend/test/test_MsaaSwapChain.cpp
Normal file
103
filament/backend/test/test_MsaaSwapChain.cpp
Normal 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
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user