diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 01d998de3c5..4a1a9c7fa7e 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -7,5 +7,3 @@ for next branch cut* header. appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut - -- Add a `name` API to Filament objects for debugging handle use-after-free assertions diff --git a/README.md b/README.md index f0e9ca349de..c3f9a954aa2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.54.1' + implementation 'com.google.android.filament:filament-android:1.54.2' } ``` @@ -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.54.1' +pod 'Filament', '~> 1.54.2' ``` ### Snapshots diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c0680a55057..0bad42c9327 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,10 @@ A new header is inserted each time a *tag* is created. Instead, if you are authoring a PR for the main branch, add your release note to [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). +## v1.54.2 + +- Add a `name` API to Filament objects for debugging handle use-after-free assertions + ## v1.54.1 diff --git a/android/gradle.properties b/android/gradle.properties index 994ce214046..510e34eb0a4 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.54.1 +VERSION_NAME=1.54.2 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/docs/Materials.md.html b/docs/Materials.md.html index 85428dbba9c..753e3a6b2f5 100644 --- a/docs/Materials.md.html +++ b/docs/Materials.md.html @@ -1308,7 +1308,12 @@ declare a variable called `eyeDirection` you can access it in the fragment shader using `variable_eyeDirection`. In the vertex shader, the interpolant name is simply a member of the `MaterialVertexInputs` structure (`material.eyeDirection` in your example). Each - interpolant is of type `float4` (`vec4`) in the shaders. + interpolant is of type `float4` (`vec4`) in the shaders. By default the precision of the + interpolant is `highp` in *both* the vertex and fragment shaders. + An alternate syntax can be used to specify both the name and precision of the interpolant. + In this case the specified precision is used as-is in both fragment and vertex stages, in + particular if `default` is specified the default precision is used is the fragment shader + (`mediump`) and in the vertex shader (`highp`). ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON material { @@ -1320,7 +1325,11 @@ } ], variables : [ - eyeDirection + eyeDirection, + { + name : eyeColor, + precision : medium + } ], vertexDomain : device, depthWrite : false, diff --git a/filament/backend/include/private/backend/HandleAllocator.h b/filament/backend/include/private/backend/HandleAllocator.h index b61f8e376f0..6a13b75e43b 100644 --- a/filament/backend/include/private/backend/HandleAllocator.h +++ b/filament/backend/include/private/backend/HandleAllocator.h @@ -208,14 +208,8 @@ class HandleAllocator { // TODO: for now, only pool handles check for use-after-free, so we only keep tags for // those if (isPoolHandle(id)) { - // Truncate the tag's age to N bits. - constexpr uint8_t N = 2; // support a history of 4 tags - constexpr uint8_t mask = (1 << N) - 1; - - uint8_t const age = (id & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT; - uint8_t const newAge = age & mask; - uint32_t const key = (id & ~HANDLE_AGE_MASK) | (newAge << HANDLE_AGE_SHIFT); - + // Truncate the age to get the debug tag + uint32_t const key = id & ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK); // This line is the costly part. In the future, we could potentially use a custom // allocator. mDebugTags[key] = std::move(tag); @@ -226,7 +220,8 @@ class HandleAllocator { if (!isPoolHandle(id)) { return "(no tag)"; } - if (auto pos = mDebugTags.find(id); pos != mDebugTags.end()) { + uint32_t const key = id & ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK); + if (auto pos = mDebugTags.find(key); pos != mDebugTags.end()) { return pos->second; } return "(no tag)"; @@ -349,12 +344,24 @@ class HandleAllocator { } } - // we handle a 4 bits age per address - static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u; // pool vs heap handle - static constexpr uint32_t HANDLE_AGE_MASK = 0x78000000u; // handle's age - static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu; // handle index - static constexpr uint32_t HANDLE_TAG_MASK = HANDLE_AGE_MASK; - static constexpr uint32_t HANDLE_AGE_SHIFT = 27; + // number if bits allotted to the handle's age (currently 4 max) + static constexpr uint32_t HANDLE_AGE_BIT_COUNT = 4; + // number if bits allotted to the handle's debug tag (HANDLE_AGE_BIT_COUNT max) + static constexpr uint32_t HANDLE_DEBUG_TAG_BIT_COUNT = 2; + // bit shift for both the age and debug tag + static constexpr uint32_t HANDLE_AGE_SHIFT = 27; + // mask for the heap (vs pool) flag + static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u; + // mask for the age + static constexpr uint32_t HANDLE_AGE_MASK = + ((1 << HANDLE_AGE_BIT_COUNT) - 1) << HANDLE_AGE_SHIFT; + // mask for the debug tag + static constexpr uint32_t HANDLE_DEBUG_TAG_MASK = + ((1 << HANDLE_DEBUG_TAG_BIT_COUNT) - 1) << HANDLE_AGE_SHIFT; + // mask for the index + static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu; + + static_assert(HANDLE_DEBUG_TAG_BIT_COUNT <= HANDLE_AGE_BIT_COUNT); static bool isPoolHandle(HandleBase::HandleId id) noexcept { return (id & HANDLE_HEAP_FLAG) == 0u; @@ -369,7 +376,7 @@ class HandleAllocator { // a non-pool handle. if (UTILS_LIKELY(isPoolHandle(id))) { char* const base = (char*)mHandleArena.getArea().begin(); - uint32_t const tag = id & HANDLE_TAG_MASK; + uint32_t const tag = id & HANDLE_AGE_MASK; size_t const offset = (id & HANDLE_INDEX_MASK) * Allocator::getAlignment(); return { static_cast(base + offset), tag }; } @@ -384,7 +391,7 @@ class HandleAllocator { size_t const offset = (char*)p - base; assert_invariant((offset % Allocator::getAlignment()) == 0); auto id = HandleBase::HandleId(offset / Allocator::getAlignment()); - id |= tag & HANDLE_TAG_MASK; + id |= tag & HANDLE_AGE_MASK; assert_invariant((id & HANDLE_HEAP_FLAG) == 0); return id; } diff --git a/filament/backend/src/metal/MetalBuffer.h b/filament/backend/src/metal/MetalBuffer.h index bb3de6d27c9..265c2896238 100644 --- a/filament/backend/src/metal/MetalBuffer.h +++ b/filament/backend/src/metal/MetalBuffer.h @@ -160,6 +160,8 @@ class MetalBuffer { size_t size, bool forceGpuBuffer = false); ~MetalBuffer(); + [[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; } + MetalBuffer(const MetalBuffer& rhs) = delete; MetalBuffer& operator=(const MetalBuffer& rhs) = delete; diff --git a/filament/backend/src/metal/MetalBuffer.mm b/filament/backend/src/metal/MetalBuffer.mm index 6070cf47fb3..a3eb80fa12d 100644 --- a/filament/backend/src/metal/MetalBuffer.mm +++ b/filament/backend/src/metal/MetalBuffer.mm @@ -53,8 +53,8 @@ mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate], TrackedMetalBuffer::Type::GENERIC }; } - FILAMENT_CHECK_POSTCONDITION(mBuffer) - << "Could not allocate Metal buffer of size " << size << "."; + // mBuffer might fail to be allocated. Clients can check for this by calling + // wasAllocationSuccessful(). } MetalBuffer::~MetalBuffer() { diff --git a/filament/backend/src/metal/MetalDriver.h b/filament/backend/src/metal/MetalDriver.h index e38b65b3755..6b9eac013e9 100644 --- a/filament/backend/src/metal/MetalDriver.h +++ b/filament/backend/src/metal/MetalDriver.h @@ -57,7 +57,6 @@ class MetalDriver final : public DriverBase { public: static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig); - void runAtNextTick(const std::function& fn) noexcept; private: @@ -73,10 +72,12 @@ class MetalDriver final : public DriverBase { /* * Tasks run regularly on the driver thread. + * Not thread-safe; tasks are run from the driver thead and must be enqueued from the driver + * thread. */ + void runAtNextTick(const std::function& fn) noexcept; void executeTickOps() noexcept; std::vector> mTickOps; - std::mutex mTickOpsLock; /* * Driver interface diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index b7d0a31deaa..b85d616c773 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -320,17 +320,40 @@ uint32_t vertexCount, Handle vbih) { MetalVertexBufferInfo const* const vbi = handle_cast(vbih); construct_handle(vbh, *mContext, vertexCount, vbi->bufferCount, vbih); + // No actual GPU memory is allocated here, so no need to check for allocation success. } void MetalDriver::createIndexBufferR(Handle ibh, ElementType elementType, uint32_t indexCount, BufferUsage usage) { - auto elementSize = (uint8_t) getElementTypeSize(elementType); - construct_handle(ibh, *mContext, usage, elementSize, indexCount); + auto elementSize = (uint8_t)getElementTypeSize(elementType); + auto* indexBuffer = + construct_handle(ibh, *mContext, usage, elementSize, indexCount); + auto& buffer = indexBuffer->buffer; + // If the allocation was not successful, postpone the error message until the next tick, to give + // Filament a chance to call setDebugTag on the handle; this way we get a nicer error message. + if (UTILS_UNLIKELY(!buffer.wasAllocationSuccessful())) { + const size_t byteCount = buffer.getSize(); + runAtNextTick([byteCount, this, ibh]() { + FILAMENT_CHECK_POSTCONDITION(false) + << "Could not allocate Metal index buffer of size " << byteCount + << ", tag=" << mHandleAllocator.getHandleTag(ibh.getId()).c_str_safe(); + }); + } } void MetalDriver::createBufferObjectR(Handle boh, uint32_t byteCount, BufferObjectBinding bindingType, BufferUsage usage) { - construct_handle(boh, *mContext, bindingType, usage, byteCount); + auto* bufferObject = + construct_handle(boh, *mContext, bindingType, usage, byteCount); + // If the allocation was not successful, postpone the error message until the next tick, to give + // Filament a chance to call setDebugTag on the handle; this way we get a nicer error message. + if (UTILS_UNLIKELY(!bufferObject->getBuffer()->wasAllocationSuccessful())) { + runAtNextTick([byteCount, this, boh]() { + FILAMENT_CHECK_POSTCONDITION(false) + << "Could not allocate Metal buffer of size " << byteCount + << ", tag=" << mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); + }); + } } void MetalDriver::createTextureR(Handle th, SamplerType target, uint8_t levels, @@ -2066,15 +2089,12 @@ } void MetalDriver::runAtNextTick(const std::function& fn) noexcept { - std::lock_guard const lock(mTickOpsLock); mTickOps.push_back(fn); } void MetalDriver::executeTickOps() noexcept { std::vector> ops; - mTickOpsLock.lock(); std::swap(ops, mTickOps); - mTickOpsLock.unlock(); for (const auto& f : ops) { f(); } diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 9f3e8a502e8..938d4569d36 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -257,10 +257,6 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { } } -#ifndef FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD -#define FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD 1 -#endif - class PresentDrawableData { public: PresentDrawableData() = delete; @@ -279,14 +275,10 @@ static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPr [that->mDrawable present]; } -#if FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD == 1 // mDrawable is acquired on the driver thread. Typically, we would release this object on // the same thread, but after receiving consistent crash reports from within // [CAMetalDrawable dealloc], we suspect this object requires releasing on the main thread. dispatch_async(dispatch_get_main_queue(), ^{ cleanupAndDestroy(that); }); -#else - that->mDriver->runAtNextTick([that]() { cleanupAndDestroy(that); }); -#endif } private: diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 423f8865cc7..66327633f17 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -541,9 +541,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts, bugs->delay_fbo_destruction = true; // PowerVR seems to have no problem with this (which is good for us) bugs->allow_read_only_ancillary_feedback_loop = true; - // PowerVR doesn't respect lengths passed to glShaderSource, so concatenate them into a - // single string. - bugs->concatenate_shader_strings = true; } else if (strstr(renderer, "Apple")) { // Apple GPU } else if (strstr(renderer, "Tegra") || diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index a4a43e8fe0a..902fcc9f094 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -315,12 +315,6 @@ class OpenGLContext final : public TimerQueryFactoryInterface { // bugs or performance issues. bool force_feature_level0; - // Some drivers don't respect the length argument of glShaderSource() and (apparently) - // require each shader source string to be null-terminated. This works around the issue by - // concatenating the strings into a single null-terminated string before passing it to - // glShaderSource(). - bool concatenate_shader_strings; - } bugs = {}; // state getters -- as needed. @@ -567,9 +561,6 @@ class OpenGLContext final : public TimerQueryFactoryInterface { { bugs.force_feature_level0, "force_feature_level0", ""}, - { bugs.concatenate_shader_strings, - "concatenate_shader_strings", - ""}, }}; // this is chosen to minimize code size diff --git a/filament/backend/src/opengl/ShaderCompilerService.cpp b/filament/backend/src/opengl/ShaderCompilerService.cpp index 0c01d383b35..eb6876859be 100644 --- a/filament/backend/src/opengl/ShaderCompilerService.cpp +++ b/filament/backend/src/opengl/ShaderCompilerService.cpp @@ -597,40 +597,30 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context, version = "#version 310 es\n"; } - const std::array sources = { - version.data(), - prolog.data(), - specializationConstantString.c_str(), - packingFunctions.data(), - body.data() + std::array sources = { + version, + prolog, + specializationConstantString, + packingFunctions, + { body.data(), body.size() - 1 } // null-terminated }; - const std::array lengths = { - (GLint)version.length(), - (GLint)prolog.length(), - (GLint)specializationConstantString.length(), - (GLint)packingFunctions.length(), - (GLint)body.length() - 1 // null terminated - }; + // Some of the sources may be zero-length. Remove them as to avoid passing lengths of + // zero to glShaderSource(). glShaderSource should work with lengths of zero, but some + // drivers instead interpret zero as a sentinel for a null-terminated string. + auto partitionPoint = std::stable_partition( + sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); }); + size_t count = std::distance(sources.begin(), partitionPoint); + + std::array shaderStrings; + std::array lengths; + for (size_t i = 0; i < count; i++) { + shaderStrings[i] = sources[i].data(); + lengths[i] = sources[i].size(); + } GLuint const shaderId = glCreateShader(glShaderType); - - if (UTILS_UNLIKELY(context.bugs.concatenate_shader_strings)) { - size_t totalSize = 0; - for (size_t i = 0; i < sources.size(); i++) { - totalSize += lengths[i]; - } - std::string concatenatedShaderSource; - concatenatedShaderSource.reserve(totalSize); - for (size_t i = 0; i < sources.size(); i++) { - concatenatedShaderSource.append(sources[i], lengths[i]); - } - const GLchar* ptr = concatenatedShaderSource.c_str(); - GLint length = concatenatedShaderSource.length(); - glShaderSource(shaderId, 1, &ptr, &length); - } else { - glShaderSource(shaderId, sources.size(), sources.data(), lengths.data()); - } + glShaderSource(shaderId, count, shaderStrings.data(), lengths.data()); glCompileShader(shaderId); diff --git a/filament/include/filament/FilamentAPI.h b/filament/include/filament/FilamentAPI.h index 7a6f16d6307..7fa19433847 100644 --- a/filament/include/filament/FilamentAPI.h +++ b/filament/include/filament/FilamentAPI.h @@ -55,7 +55,8 @@ class UTILS_PUBLIC FilamentAPI { template using BuilderBase = utils::PrivateImplementation; -void builderMakeName(utils::CString& outName, const char* name, size_t len) noexcept; +// This needs to be public because it is used in the following template. +UTILS_PUBLIC void builderMakeName(utils::CString& outName, const char* name, size_t len) noexcept; template class UTILS_PUBLIC BuilderNameMixin { diff --git a/filament/src/FrameSkipper.cpp b/filament/src/FrameSkipper.cpp index 7942ecfa5ad..ef14f5a0f82 100644 --- a/filament/src/FrameSkipper.cpp +++ b/filament/src/FrameSkipper.cpp @@ -16,16 +16,22 @@ #include "FrameSkipper.h" -#include +#include + +#include #include +#include + +#include + namespace filament { using namespace utils; using namespace backend; FrameSkipper::FrameSkipper(size_t latency) noexcept - : mLast(latency - 1) { + : mLast(std::max(latency, MAX_FRAME_LATENCY) - 1) { assert_invariant(latency <= MAX_FRAME_LATENCY); } @@ -41,31 +47,32 @@ void FrameSkipper::terminate(DriverApi& driver) noexcept { bool FrameSkipper::beginFrame(DriverApi& driver) noexcept { auto& fences = mDelayedFences; - auto fence = fences.front(); - if (fence) { - auto status = driver.getFenceStatus(fence); - if (status == FenceStatus::TIMEOUT_EXPIRED) { - // Sync not ready, skip frame + if (fences.front()) { + // Do we have a latency old fence? + auto status = driver.getFenceStatus(fences.front()); + if (UTILS_UNLIKELY(status == FenceStatus::TIMEOUT_EXPIRED)) { + // The fence hasn't signaled yet, skip this frame return false; } assert_invariant(status == FenceStatus::CONDITION_SATISFIED); - driver.destroyFence(fence); } - // shift all fences down by 1 - std::move(fences.begin() + 1, fences.end(), fences.begin()); - fences.back() = {}; return true; } void FrameSkipper::endFrame(DriverApi& driver) noexcept { - // If the user produced a new frame despite the fact that the previous one wasn't finished - // (i.e. FrameSkipper::beginFrame() returned false), we need to make sure to replace - // a fence that might be here already) - auto& fence = mDelayedFences[mLast]; - if (fence) { - driver.destroyFence(fence); + auto& fences = mDelayedFences; + size_t const last = mLast; + + // pop the oldest fence and advance the other ones + if (fences.front()) { + driver.destroyFence(fences.front()); } - fence = driver.createFence(); + std::move(fences.begin() + 1, fences.end(), fences.begin()); + + // add a new fence to the end + assert_invariant(!fences[last]); + + fences[last] = driver.createFence(); } } // namespace filament diff --git a/filament/src/FrameSkipper.h b/filament/src/FrameSkipper.h index 3b7cedbc25c..61bc4049735 100644 --- a/filament/src/FrameSkipper.h +++ b/filament/src/FrameSkipper.h @@ -22,6 +22,9 @@ #include +#include +#include + namespace filament { /* @@ -60,7 +63,7 @@ class FrameSkipper { private: using Container = std::array, MAX_FRAME_LATENCY>; mutable Container mDelayedFences{}; - size_t mLast; + uint8_t const mLast; }; } // namespace filament diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index cc15f8b8952..f0ee30c682b 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -2945,6 +2945,10 @@ FrameGraphId PostProcessManager::upscale(FrameGraph& fg, bool 1.0f / outputDesc.height}); } + if (blitterNames[index] == "blitLow") { + mi->setParameter("levelOfDetail", 0.0f); + } + mi->setParameter("viewport", float4{ (float)vp.left / inputDesc.width, (float)vp.bottom / inputDesc.height, @@ -3003,9 +3007,8 @@ FrameGraphId PostProcessManager::blit(FrameGraph& fg, bool tr SamplerMagFilter filterMag, SamplerMinFilter filterMin) noexcept { - // TODO: add support for sub-resources - assert_invariant(fg.getSubResourceDescriptor(input).layer == 0); - assert_invariant(fg.getSubResourceDescriptor(input).level == 0); + uint32_t const layer = fg.getSubResourceDescriptor(input).layer; + float const levelOfDetail = fg.getSubResourceDescriptor(input).level; struct QuadBlitData { FrameGraphId input; @@ -3031,7 +3034,8 @@ FrameGraphId PostProcessManager::blit(FrameGraph& fg, bool tr // -------------------------------------------------------------------------------- // set uniforms - PostProcessMaterial const& material = getPostProcessMaterial("blitLow"); + PostProcessMaterial const& material = + getPostProcessMaterial(layer ? "blitArray" : "blitLow"); auto* mi = material.getMaterialInstance(mEngine); mi->setParameter("color", color, { .filterMag = filterMag, @@ -3043,6 +3047,10 @@ FrameGraphId PostProcessManager::blit(FrameGraph& fg, bool tr float(vp.width) / inputDesc.width, float(vp.height) / inputDesc.height }); + mi->setParameter("levelOfDetail", levelOfDetail); + if (layer) { + mi->setParameter("layerIndex", layer); + } mi->commit(driver); mi->use(driver); @@ -3185,6 +3193,7 @@ FrameGraphId PostProcessManager::resolve(FrameGraph& fg, assert_invariant(dst); assert_invariant(srcDesc.format == dstDesc.format); assert_invariant(srcDesc.width == dstDesc.width && srcDesc.height == dstDesc.height); + assert_invariant(srcDesc.samples > 1 && dstDesc.samples <= 1); driver.resolve( dst, dstSubDesc.level, dstSubDesc.layer, src, srcSubDesc.level, srcSubDesc.layer); diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 3dc95fa4f88..b931419775c 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -610,7 +610,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla // Here, objects close to the camera (but behind) will be drawn first. // An alternative that keeps the mathematical ordering is given here: // distanceBits ^= ((int32_t(distanceBits) >> 31) | 0x80000000u); - float const distance = -dot(soaWorldAABBCenter[i], cameraForward) - cameraPositionDotCameraForward; + float const distance = -(dot(soaWorldAABBCenter[i], cameraForward) - cameraPositionDotCameraForward); uint32_t const distanceBits = reinterpret_cast(distance); // calculate the per-primitive face winding order inversion diff --git a/filament/src/RendererUtils.cpp b/filament/src/RendererUtils.cpp index d465a03288b..c25530f8811 100644 --- a/filament/src/RendererUtils.cpp +++ b/filament/src/RendererUtils.cpp @@ -40,6 +40,7 @@ #include #include +#include #include #include @@ -50,8 +51,9 @@ namespace filament { using namespace backend; using namespace math; -FrameGraphId RendererUtils::colorPass( +RendererUtils::ColorPassOutput RendererUtils::colorPass( FrameGraph& fg, const char* name, FEngine& engine, FView const& view, + ColorPassInput const& colorPassInput, FrameGraphTexture::Descriptor const& colorBufferDesc, ColorPassConfig const& config, PostProcessManager::ColorGradingConfig colorGradingConfig, RenderPass::Executor passExecutor) noexcept { @@ -67,31 +69,28 @@ FrameGraphId RendererUtils::colorPass( FrameGraphId structure; }; - Blackboard& blackboard = fg.getBlackboard(); - auto& colorPass = fg.addPass(name, [&](FrameGraph::Builder& builder, ColorPassData& data) { TargetBufferFlags const clearColorFlags = config.clearFlags & TargetBufferFlags::COLOR; TargetBufferFlags clearDepthFlags = config.clearFlags & TargetBufferFlags::DEPTH; TargetBufferFlags clearStencilFlags = config.clearFlags & TargetBufferFlags::STENCIL; - uint8_t layerCount = 1; - data.shadows = blackboard.get("shadows"); - data.ssao = blackboard.get("ssao"); - data.color = blackboard.get("color"); - data.depth = blackboard.get("depth"); + data.color = colorPassInput.linearColor; + data.depth = colorPassInput.depth; + data.shadows = colorPassInput.shadows; + data.ssao = colorPassInput.ssao; // Screen-space reflection or refractions if (config.hasScreenSpaceReflectionsOrRefractions) { - data.ssr = blackboard.get("ssr"); + data.ssr = colorPassInput.ssr; if (data.ssr) { data.ssr = builder.sample(data.ssr); } } if (config.hasContactShadows) { - data.structure = blackboard.get("structure"); + data.structure = colorPassInput.structure; assert_invariant(data.structure); data.structure = builder.sample(data.structure); } @@ -154,6 +153,8 @@ FrameGraphId RendererUtils::colorPass( } if (colorGradingConfig.asSubpass) { + assert_invariant(config.msaa <= 1); + assert_invariant(colorBufferDesc.samples <= 1); data.output = builder.createTexture("Tonemapped Buffer", { .width = colorBufferDesc.width, .height = colorBufferDesc.height, @@ -172,9 +173,6 @@ FrameGraphId RendererUtils::colorPass( data.color = builder.write(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT); data.depth = builder.write(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT); - if (view.hasStereo() && engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) { - layerCount = engine.getConfig().stereoscopicEyeCount; - } /* * There is a bit of magic happening here regarding the viewport used. @@ -196,9 +194,8 @@ FrameGraphId RendererUtils::colorPass( .stencil = data.stencil }, .clearColor = config.clearColor, .samples = config.msaa, - .layerCount = layerCount, + .layerCount = static_cast(colorBufferDesc.depth), .clearFlags = clearColorFlags | clearDepthFlags | clearStencilFlags}); - blackboard["depth"] = data.depth; }, [=, passExecutor = std::move(passExecutor), &view, &engine](FrameGraphResources const& resources, ColorPassData const& data, DriverApi& driver) { @@ -264,24 +261,21 @@ FrameGraphId RendererUtils::colorPass( } ); - // when color grading is done as a subpass, the output of the color-pass is the ldr buffer - auto output = colorGradingConfig.asSubpass ? colorPass->output : colorPass->color; - - blackboard["color"] = output; - return output; + return { + .linearColor = colorPass->color, + .tonemappedColor = colorPass->output, // can be null + .depth = colorPass->depth + }; } -std::pair, bool> RendererUtils::refractionPass( +std::optional RendererUtils::refractionPass( FrameGraph& fg, FEngine& engine, FView const& view, + ColorPassInput colorPassInput, ColorPassConfig config, PostProcessManager::ScreenSpaceRefConfig const& ssrConfig, PostProcessManager::ColorGradingConfig colorGradingConfig, RenderPass const& pass) noexcept { - auto& blackboard = fg.getBlackboard(); - auto input = blackboard.get("color"); - FrameGraphId output; - // find the first refractive object in channel 2 RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(), [](auto const& command) { @@ -296,34 +290,35 @@ std::pair, bool> RendererUtils::refractionPass( // if there wasn't any refractive object, just skip everything below. if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) { - PostProcessManager& ppm = engine.getPostProcessManager(); - - // clear the color/depth buffers, which will orphan (and cull) the color pass - input.clear(); - blackboard.remove("color"); - blackboard.remove("depth"); - + assert_invariant(!colorPassInput.linearColor); + assert_invariant(!colorPassInput.depth); config.hasScreenSpaceReflectionsOrRefractions = true; - input = RendererUtils::colorPass(fg, "Color Pass (opaque)", engine, view, { + PostProcessManager& ppm = engine.getPostProcessManager(); + auto const opaquePassOutput = RendererUtils::colorPass(fg, + "Color Pass (opaque)", engine, view, colorPassInput, { // When rendering the opaques, we need to conserve the sample buffer, // so create a config that specifies the sample count. .width = config.physicalViewport.width, .height = config.physicalViewport.height, .samples = config.msaa, .format = config.hdrFormat - }, config, { .asSubpass = false }, + }, + config, { .asSubpass = false, .customResolve = false }, pass.getExecutor(pass.begin(), refraction)); // generate the mipmap chain PostProcessManager::generateMipmapSSR(ppm, fg, - input, ssrConfig.refraction, true, ssrConfig); + opaquePassOutput.linearColor, + ssrConfig.refraction, + true, ssrConfig); // Now we're doing the refraction pass proper. - // This uses the same framebuffer (color and depth) used by the opaque pass. This happens - // automatically because these are set in the Blackboard (they were set by the opaque - // pass). For this reason, `desc` below is only used in colorPass() for the width and - // height. + // This uses the same framebuffer (color and depth) used by the opaque pass. + // For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for + // the width and height. + colorPassInput.linearColor = opaquePassOutput.linearColor; + colorPassInput.depth = opaquePassOutput.depth; // Since we're reusing the existing target we don't want to clear any of its buffer. // Important: if this target ended up being an imported target, then the clearFlags @@ -331,22 +326,25 @@ std::pair, bool> RendererUtils::refractionPass( // and we'd end up clearing the opaque pass. This scenario never happens because it is // prevented in Renderer.cpp's final blit. config.clearFlags = TargetBufferFlags::NONE; - output = RendererUtils::colorPass(fg, "Color Pass (transparent)", engine, view, { + auto transparentPassOutput = RendererUtils::colorPass(fg, "Color Pass (transparent)", + engine, view, colorPassInput, { .width = config.physicalViewport.width, .height = config.physicalViewport.height }, - config, colorGradingConfig, pass.getExecutor(refraction, pass.end())); + config, colorGradingConfig, + pass.getExecutor(refraction, pass.end())); if (config.msaa > 1 && !colorGradingConfig.asSubpass) { // We need to do a resolve here because later passes (such as color grading or DoF) will // need to sample from 'output'. However, because we have MSAA, we know we're not // sampleable. And this is because in the SSR case, we had to use a renderbuffer to // conserve the multi-sample buffer. - output = ppm.resolve(fg, "Resolved Color Buffer", output, { .levels = 1 }); + transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer", + transparentPassOutput.linearColor, { .levels = 1 }); } - } else { - output = input; + return transparentPassOutput; } - return { output, hasScreenSpaceRefraction }; + + return std::nullopt; } UTILS_NOINLINE diff --git a/filament/src/RendererUtils.h b/filament/src/RendererUtils.h index 9b7e93cbc60..34ea7b0eda9 100644 --- a/filament/src/RendererUtils.h +++ b/filament/src/RendererUtils.h @@ -21,14 +21,19 @@ #include "RenderPass.h" #include "fg/FrameGraphId.h" +#include "fg/FrameGraphTexture.h" + +#include #include #include +#include #include #include +#include #include namespace filament { @@ -71,15 +76,32 @@ class RendererUtils { bool screenSpaceReflectionHistoryNotReady; }; - static FrameGraphId colorPass( + struct ColorPassInput { + FrameGraphId linearColor; + FrameGraphId tonemappedColor; + FrameGraphId depth; + FrameGraphId shadows; + FrameGraphId ssao; + FrameGraphId ssr; + FrameGraphId structure; + }; + struct ColorPassOutput { + FrameGraphId linearColor; + FrameGraphId tonemappedColor; + FrameGraphId depth; + }; + + static ColorPassOutput colorPass( FrameGraph& fg, const char* name, FEngine& engine, FView const& view, + ColorPassInput const& colorPassInput, FrameGraphTexture::Descriptor const& colorBufferDesc, ColorPassConfig const& config, PostProcessManager::ColorGradingConfig colorGradingConfig, RenderPass::Executor passExecutor) noexcept; - static std::pair, bool> refractionPass( + static std::optional refractionPass( FrameGraph& fg, FEngine& engine, FView const& view, + ColorPassInput colorPassInput, ColorPassConfig config, PostProcessManager::ScreenSpaceRefConfig const& ssrConfig, PostProcessManager::ColorGradingConfig colorGradingConfig, diff --git a/filament/src/details/DebugRegistry.cpp b/filament/src/details/DebugRegistry.cpp index c2f5d3d3fb6..8915cf312e1 100644 --- a/filament/src/details/DebugRegistry.cpp +++ b/filament/src/details/DebugRegistry.cpp @@ -28,12 +28,6 @@ #include #include -#ifndef NDEBUG -# define DEBUG_PROPERTIES_WRITABLE true -#else -# define DEBUG_PROPERTIES_WRITABLE false -#endif - using namespace filament::math; using namespace utils; @@ -79,17 +73,15 @@ bool FDebugRegistry::hasProperty(const char* name) const noexcept { template bool FDebugRegistry::setProperty(const char* name, T v) noexcept { - if constexpr (DEBUG_PROPERTIES_WRITABLE) { - auto info = getPropertyInfo(name); - T* const addr = static_cast(info.first); - if (addr) { - auto old = *addr; - *addr = v; - if (info.second && old != v) { - info.second(); - } - return true; + auto info = getPropertyInfo(name); + T* const addr = static_cast(info.first); + if (addr) { + auto old = *addr; + *addr = v; + if (info.second && old != v) { + info.second(); } + return true; } return false; } diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 4fe186cd299..b27c6608731 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -192,19 +192,27 @@ Material* Material::Builder::build(Engine& engine) { return nullptr; } + // Print a warning if the material's stereo type doesn't align with the engine's setting. MaterialDomain materialDomain; + UserVariantFilterMask variantFilterMask; materialParser->getMaterialDomain(&materialDomain); - if (materialDomain == MaterialDomain::SURFACE) { + materialParser->getMaterialVariantFilterMask(&variantFilterMask); + bool const hasStereoVariants = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::STE)); + if (materialDomain == MaterialDomain::SURFACE && hasStereoVariants) { StereoscopicType const engineStereoscopicType = engine.getConfig().stereoscopicType; - StereoscopicType materialStereoscopicType = StereoscopicType::NONE; - materialParser->getStereoscopicType(&materialStereoscopicType); - if (materialStereoscopicType != engineStereoscopicType) { - CString name; - materialParser->getName(&name); - slog.w << "The stereoscopic type in the compiled material '" << name.c_str_safe() - << "' is " << (int)materialStereoscopicType - << ", which is not compatiable with the engine's setting " - << (int)engineStereoscopicType << "." << io::endl; + // Default materials are always compiled with either 'instanced' or 'multiview'. + // So, we only verify compatibility if the engine is set up for stereo. + if (engineStereoscopicType != StereoscopicType::NONE) { + StereoscopicType materialStereoscopicType = StereoscopicType::NONE; + materialParser->getStereoscopicType(&materialStereoscopicType); + if (materialStereoscopicType != engineStereoscopicType) { + CString name; + materialParser->getName(&name); + slog.w << "The stereoscopic type in the compiled material '" << name.c_str_safe() + << "' is " << (int)materialStereoscopicType + << ", which is not compatiable with the engine's setting " + << (int)engineStereoscopicType << "." << io::endl; + } } } diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index 1b8ffdd3596..d91a9c42d36 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -655,16 +655,16 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { // on qualcomm hardware -- we might need a backend dependent toggle at some point const PostProcessManager::ColorGradingConfig colorGradingConfig{ .asSubpass = - hasColorGrading && msaaSampleCount <= 1 && - !bloomOptions.enabled && !dofOptions.enabled && !taaOptions.enabled && driver.isFrameBufferFetchSupported() && + hasColorGrading && + !bloomOptions.enabled && !dofOptions.enabled && !taaOptions.enabled && !engine.debug.renderer.disable_subpasses, .customResolve = - msaaOptions.customResolve && msaaSampleCount > 1 && - hasColorGrading && driver.isFrameBufferFetchMultiSampleSupported() && + msaaOptions.customResolve && + hasColorGrading && !engine.debug.renderer.disable_subpasses, .translucent = needsAlphaChannel, .fxaa = hasFXAA, @@ -673,6 +673,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { TextureFormat::RGBA8 : getLdrFormat(needsAlphaChannel) }; + // by construction (msaaSampleCount) both asSubpass and customResolve can't be true + assert_invariant(colorGradingConfig.asSubpass + colorGradingConfig.customResolve < 2); + // whether we're scaled at all bool scaled = any(notEqual(scale, float2(1.0f))); @@ -695,13 +698,12 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { CameraInfo cameraInfo = view.computeCameraInfo(engine); - // when colorgrading-as-subpass is active, we know that many other effects are disabled - // such as dof, bloom. Moreover, if fxaa and scaling are not enabled, we're essentially in - // a very fast rendering path -- in this case, we would need an extra blit to "resolve" the - // buffer padding (because there are no other pass that can do it as a side effect). - // In this case, it is better to skip the padding, which won't be helping much. - const bool noBufferPadding = (colorGradingConfig.asSubpass && !hasFXAA && !scaled) - || engine.debug.renderer.disable_buffer_padding; + // If fxaa and scaling are not enabled, we're essentially in a very fast rendering path -- in + // this case, we would need an extra blit to "resolve" the buffer padding (because there are no + // other pass that can do it as a side effect). In this case, it is better to skip the padding, + // which won't be helping much. + const bool noBufferPadding + = (!hasFXAA && !scaled) || engine.debug.renderer.disable_buffer_padding; // guardBand must be a multiple of 16 to guarantee the same exact rendering up to 4 mip levels. float const guardBand = guardBandOptions.enabled ? 16.0f : 0.0f; @@ -952,7 +954,6 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { .scale = aoOptions.resolution, .picking = view.hasPicking() }); - blackboard["structure"] = structure; const auto picking = picking_; @@ -1000,7 +1001,6 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { ssReflectionsOptions.enabled ? TextureFormat::RGBA16F : TextureFormat::R11F_G11F_B10F, view.getCameraUser().getFieldOfView(Camera::Fov::VERTICAL), config.scale); config.ssrLodOffset = ssrConfig.lodOffset; - blackboard["ssr"] = ssrConfig.ssr; // -------------------------------------------------------------------------------------------- // screen-space reflections pass @@ -1104,24 +1104,39 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { ); // the color pass itself + color-grading as subpass if needed - auto colorPassOutput = RendererUtils::colorPass(fg, "Color Pass", mEngine, view, + auto colorPassOutput = RendererUtils::colorPass(fg, "Color Pass", mEngine, view, { + .shadows = blackboard.get("shadows"), + .ssao = blackboard.get("ssao"), + .ssr = ssrConfig.ssr, + .structure = structure + }, colorBufferDesc, config, colorGradingConfigForColor, pass.getExecutor()); if (view.isScreenSpaceRefractionEnabled() && !pass.empty()) { - // this cancels the colorPass() call above if refraction is active. - // the color pass + refraction + color-grading as subpass if needed - const auto [output, enabled] = RendererUtils::refractionPass(fg, mEngine, view, + // This cancels the colorPass() call above if refraction is active. + // The color pass + refraction + color-grading as subpass if needed + auto const output = RendererUtils::refractionPass(fg, mEngine, view, { + .shadows = blackboard.get("shadows"), + .ssao = blackboard.get("ssao"), + .ssr = ssrConfig.ssr, + .structure = structure + }, config, ssrConfig, colorGradingConfigForColor, pass); - colorPassOutput = output; - hasScreenSpaceRefraction = enabled; + + hasScreenSpaceRefraction = output.has_value(); + if (hasScreenSpaceRefraction) { + colorPassOutput = output.value(); + } } if (colorGradingConfig.customResolve) { + assert_invariant(fg.getDescriptor(colorPassOutput.linearColor).samples <= 1); // TODO: we have to "uncompress" (i.e. detonemap) the color buffer here because it's used // by many other passes (Bloom, TAA, DoF, etc...). We could make this more // efficient by using ARM_shader_framebuffer_fetch. We use a load/store (i.e. // subpass) here because it's more convenient. - colorPassOutput = ppm.customResolveUncompressPass(fg, colorPassOutput); + colorPassOutput.linearColor = + ppm.customResolveUncompressPass(fg, colorPassOutput.linearColor); } // export the color buffer if screen-space reflections are enabled @@ -1137,7 +1152,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { // The "output" of this pass is going to be used during the next frame as // an "import". builder.sideEffect(); - data.history = builder.sample(colorPassOutput); // FIXME: an access must be declared for detach(), why? + + // we can't use colorPassOutput here because it could be tonemapped + data.history = builder.sample(colorPassOutput.linearColor); // FIXME: an access must be declared for detach(), why? }, [&view, projection](FrameGraphResources const& resources, auto const& data, backend::DriverApi&) { auto& history = view.getFrameHistory(); @@ -1147,7 +1164,14 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { }); } - FrameGraphId input = colorPassOutput; + // this is the output of the color pass / input to post processing, + // this is only used later for comparing it with the output after post-processing + FrameGraphId const postProcessInput = colorGradingConfig.asSubpass ? + colorPassOutput.tonemappedColor : + colorPassOutput.linearColor; + + // input can change below + FrameGraphId input = postProcessInput; fg.addTrivialSideEffectPass("Finish Color Passes", [&view](DriverApi& driver) { // Unbind SSAO sampler, b/c the FrameGraph will delete the texture at the end of the pass. view.cleanupRenderPasses(); @@ -1158,8 +1182,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { // if the depth is not used below or if the depth is not MS (e.g. it could have been // auto-resolved). // In practice, this is used on Vulkan and older Metal devices. - auto depth = blackboard.get("depth"); - depth = ppm.resolve(fg, "Resolved Depth Buffer", depth, { .levels = 1 }); + auto depth = ppm.resolve(fg, "Resolved Depth Buffer", colorPassOutput.depth, { .levels = 1 }); // Debug: CSM visualisation if (UTILS_UNLIKELY(engine.debug.shadowmap.visualize_cascades && @@ -1275,7 +1298,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { // The intermediate buffer is accomplished with a "fake" opaqueBlit (i.e. blit) operation. const bool outputIsSwapChain = - (input == colorPassOutput) && (viewRenderTarget == mRenderTargetHandle); + (input == postProcessInput) && (viewRenderTarget == mRenderTargetHandle); if (mightNeedFinalBlit) { if (blendModeTranslucent || xvp != svp || diff --git a/filament/src/fg/PassNode.cpp b/filament/src/fg/PassNode.cpp index cf76ea35b76..51eb06645d5 100644 --- a/filament/src/fg/PassNode.cpp +++ b/filament/src/fg/PassNode.cpp @@ -190,10 +190,10 @@ void RenderPassNode::resolve() noexcept { minHeight = std::min(minHeight, h); maxHeight = std::max(maxHeight, h); } - // additionally, clear implies discardStart - rt.backend.params.flags.discardStart |= ( - rt.descriptor.clearFlags & rt.targetBufferFlags); } + // additionally, clear implies discardStart + rt.backend.params.flags.discardStart |= ( + rt.descriptor.clearFlags & rt.targetBufferFlags); assert_invariant(minWidth == maxWidth); assert_invariant(minHeight == maxHeight); diff --git a/filament/src/materials/blitArray.mat b/filament/src/materials/blitArray.mat index 2d7cfc4da3b..39d3a18e534 100644 --- a/filament/src/materials/blitArray.mat +++ b/filament/src/materials/blitArray.mat @@ -10,6 +10,10 @@ material { type : int, name : layerIndex }, + { + type : float, + name : levelOfDetail, + }, { type : float4, name : viewport, @@ -33,6 +37,6 @@ vertex { fragment { void postProcess(inout PostProcessInputs postProcess) { - postProcess.color = textureLod(materialParams_color, vec3(variable_vertex.xy, materialParams.layerIndex), 0.0); + postProcess.color = textureLod(materialParams_color, vec3(variable_vertex.xy, materialParams.layerIndex), materialParams.levelOfDetail); } } diff --git a/filament/src/materials/blitLow.mat b/filament/src/materials/blitLow.mat index 3ed03983c9d..16148577655 100644 --- a/filament/src/materials/blitLow.mat +++ b/filament/src/materials/blitLow.mat @@ -10,6 +10,10 @@ material { type : float4, name : viewport, precision: high + }, + { + type : float, + name : levelOfDetail, } ], variables : [ @@ -33,7 +37,7 @@ fragment { #if FILAMENT_EFFECTIVE_VERSION == 100 postProcess.color = texture2D(materialParams_color, variable_vertex.xy); #else - postProcess.color = textureLod(materialParams_color, variable_vertex.xy, 0.0); + postProcess.color = textureLod(materialParams_color, variable_vertex.xy, materialParams.levelOfDetail); #endif } } diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 1e87d19b7ee..73fe0a78f81 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.54.1" + spec.version = "1.54.2" 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.54.1/filament-v1.54.1-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.54.2/filament-v1.54.2-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index 2587adba659..767bdecc6fd 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -323,6 +323,9 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { //! Custom variables (all float4). MaterialBuilder& variable(Variable v, const char* name) noexcept; + MaterialBuilder& variable(Variable v, const char* name, + ParameterPrecision precision) noexcept; + /** * Require a specified attribute. * @@ -708,11 +711,17 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { ShaderStage stage; }; + struct CustomVariable { + utils::CString name; + Precision precision = Precision::DEFAULT; + bool hasPrecision = false; + }; + static constexpr size_t MATERIAL_PROPERTIES_COUNT = filament::MATERIAL_PROPERTIES_COUNT; using Property = filament::Property; using PropertyList = bool[MATERIAL_PROPERTIES_COUNT]; - using VariableList = utils::CString[MATERIAL_VARIABLES_COUNT]; + using VariableList = CustomVariable[MATERIAL_VARIABLES_COUNT]; using OutputList = std::vector; static constexpr size_t MAX_COLOR_OUTPUT = filament::backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index 3a4a4824e0e..bd879b1ee0c 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -671,13 +671,15 @@ void GLSLPostProcessor::fixupClipDistance( // - triggers a crash on some Adreno drivers (b/291140208, b/289401984, b/289393290) // However Metal requires this pass in order to correctly generate half-precision MSL // -// CreateSimplificationPass() creates a lot of problems: +// Note: CreateSimplificationPass() used to creates a lot of problems: // - Adreno GPU show artifacts after running simplification passes (Vulkan) // - spirv-cross fails generating working glsl // (https://github.com/KhronosGroup/SPIRV-Cross/issues/2162) -// - generally it makes the code more complicated, e.g.: replacing for loops with -// while-if-break, unclear if it helps for anything. -// However, the simplification passes below are necessary when targeting Metal, otherwise the +// +// However this problem was addressed in spirv-cross here: +// https://github.com/KhronosGroup/SPIRV-Cross/pull/2163 +// +// The simplification passes below are necessary when targeting Metal, otherwise the // result is mismatched half / float assignments in MSL. @@ -710,11 +712,11 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c RegisterPass(CreateAggressiveDCEPass()); RegisterPass(CreateRedundancyEliminationPass()); RegisterPass(CreateCombineAccessChainsPass()); - RegisterPass(CreateSimplificationPass(), MaterialBuilder::TargetApi::METAL); + RegisterPass(CreateSimplificationPass()); RegisterPass(CreateVectorDCEPass()); RegisterPass(CreateDeadInsertElimPass()); RegisterPass(CreateDeadBranchElimPass()); - RegisterPass(CreateSimplificationPass(), MaterialBuilder::TargetApi::METAL); + RegisterPass(CreateSimplificationPass()); RegisterPass(CreateIfConversionPass()); RegisterPass(CreateCopyPropagateArraysPass()); RegisterPass(CreateReduceLoadSizePass()); @@ -723,7 +725,7 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c RegisterPass(CreateRedundancyEliminationPass()); RegisterPass(CreateDeadBranchElimPass()); RegisterPass(CreateBlockMergePass()); - RegisterPass(CreateSimplificationPass(), MaterialBuilder::TargetApi::METAL); + RegisterPass(CreateSimplificationPass()); } void GLSLPostProcessor::registerSizePasses(Optimizer& optimizer, Config const& config) { diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 35add4f503f..1905c86cc00 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -234,7 +234,21 @@ MaterialBuilder& MaterialBuilder::variable(Variable v, const char* name) noexcep case Variable::CUSTOM2: case Variable::CUSTOM3: assert(size_t(v) < MATERIAL_VARIABLES_COUNT); - mVariables[size_t(v)] = CString(name); + mVariables[size_t(v)] = { CString(name), Precision::DEFAULT, false }; + break; + } + return *this; +} + +MaterialBuilder& MaterialBuilder::variable(Variable v, + const char* name, ParameterPrecision precision) noexcept { + switch (v) { + case Variable::CUSTOM0: + case Variable::CUSTOM1: + case Variable::CUSTOM2: + case Variable::CUSTOM3: + assert(size_t(v) < MATERIAL_VARIABLES_COUNT); + mVariables[size_t(v)] = { CString(name), precision, true }; break; } return *this; @@ -1383,7 +1397,7 @@ bool MaterialBuilder::checkMaterialLevelFeatures(MaterialInfo const& info) const bool MaterialBuilder::hasCustomVaryings() const noexcept { for (const auto& variable : mVariables) { - if (!variable.empty()) { + if (!variable.name.empty()) { return true; } } diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index 1c74cbd7797..2b90c299410 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -451,15 +451,20 @@ io::sstream& CodeGenerator::generatePostProcessMain(io::sstream& out, ShaderStag } io::sstream& CodeGenerator::generateVariable(io::sstream& out, ShaderStage stage, - const CString& name, size_t index) { - + const MaterialBuilder::CustomVariable& variable, size_t index) { + auto const& name = variable.name; + const char* precisionString = getPrecisionQualifier(variable.precision); if (!name.empty()) { if (stage == ShaderStage::VERTEX) { out << "\n#define VARIABLE_CUSTOM" << index << " " << name.c_str() << "\n"; out << "\n#define VARIABLE_CUSTOM_AT" << index << " variable_" << name.c_str() << "\n"; - out << "LAYOUT_LOCATION(" << index << ") VARYING vec4 variable_" << name.c_str() << ";\n"; + out << "LAYOUT_LOCATION(" << index << ") VARYING " << precisionString << " vec4 variable_" << name.c_str() << ";\n"; } else if (stage == ShaderStage::FRAGMENT) { - out << "\nLAYOUT_LOCATION(" << index << ") VARYING highp vec4 variable_" << name.c_str() << ";\n"; + if (!variable.hasPrecision && variable.precision == Precision::DEFAULT) { + // for backward compatibility + precisionString = "highp"; + } + out << "\nLAYOUT_LOCATION(" << index << ") VARYING " << precisionString << " vec4 variable_" << name.c_str() << ";\n"; } } return out; diff --git a/libs/filamat/src/shaders/CodeGenerator.h b/libs/filamat/src/shaders/CodeGenerator.h index 233243f348c..f7bb4af5ebb 100644 --- a/libs/filamat/src/shaders/CodeGenerator.h +++ b/libs/filamat/src/shaders/CodeGenerator.h @@ -103,7 +103,7 @@ class UTILS_PRIVATE CodeGenerator { // generate declarations for custom interpolants static utils::io::sstream& generateVariable(utils::io::sstream& out, ShaderStage stage, - const utils::CString& name, size_t index); + const MaterialBuilder::CustomVariable& variable, size_t index); // generate declarations for non-custom "in" variables utils::io::sstream& generateShaderInputs(utils::io::sstream& out, ShaderStage type, diff --git a/libs/gltfio/src/MaterialProvider.cpp b/libs/gltfio/src/MaterialProvider.cpp index 7e7fa6d947e..7de92341524 100644 --- a/libs/gltfio/src/MaterialProvider.cpp +++ b/libs/gltfio/src/MaterialProvider.cpp @@ -56,7 +56,13 @@ bool operator==(const MaterialKey& k1, const MaterialKey& k2) { (k1.volumeThicknessUV == k2.volumeThicknessUV) && (k1.hasSheen == k2.hasSheen) && (k1.hasIOR == k2.hasIOR) && - (k1.hasVolume == k2.hasVolume); + (k1.hasVolume == k2.hasVolume) && + (k1.hasSpecular == k2.hasSpecular) && + (k1.hasSpecularTexture == k2.hasSpecularTexture) && + (k1.hasSpecularColorTexture == k2.hasSpecularColorTexture) && + (k1.specularTextureUV == k2.specularTextureUV) && + (k1.specularColorTextureUV == k2.specularColorTextureUV) + ; } // Filament supports up to 2 UV sets. glTF has arbitrary texcoord set indices, but it allows diff --git a/libs/gltfio/src/UbershaderProvider.cpp b/libs/gltfio/src/UbershaderProvider.cpp index 64451aa363c..d70c5ab6955 100644 --- a/libs/gltfio/src/UbershaderProvider.cpp +++ b/libs/gltfio/src/UbershaderProvider.cpp @@ -259,6 +259,24 @@ MaterialInstance* UbershaderProvider::createMaterialInstance(MaterialKey* config mi->setParameter("occlusionUvMatrix", identity); mi->setParameter("emissiveUvMatrix", identity); + // set to -1 all possibly optional parameters + for (auto const* param : { + "clearCoatIndex", + "clearCoatRoughnessIndex", + "clearCoatNormalIndex", + "sheenColorIndex", + "sheenRoughnessIndex", + "volumeThicknessUvMatrix", + "volumeThicknessIndex", + "transmissionIndex", + "specularIndex", + "specularColorIndex", + }) { + if (material->hasParameter(param)) { + mi->setParameter(param, -1); + } + } + if (config->hasClearCoat) { mi->setParameter("clearCoatIndex", getUvIndex(config->clearCoatUV, config->hasClearCoatTexture)); @@ -340,10 +358,10 @@ MaterialInstance* UbershaderProvider::createMaterialInstance(MaterialKey* config mi->setParameter("sheenRoughnessMap", mDummyTexture, sampler); } - if (mi->getMaterial()->hasParameter("ior")) { + if (material->hasParameter("ior")) { mi->setParameter("ior", 1.5f); } - if (mi->getMaterial()->hasParameter("reflectance")) { + if (material->hasParameter("reflectance")) { mi->setParameter("reflectance", 0.5f); } @@ -355,6 +373,14 @@ MaterialInstance* UbershaderProvider::createMaterialInstance(MaterialKey* config mi->setParameter("specularColorMap", mDummyTexture, sampler); } + if (material->hasParameter("specularColorFactor")) { + mi->setParameter("specularColorFactor", float3(1.0f)); + } + + if (material->hasParameter("specularStrength")) { + mi->setParameter("specularStrength", 1.0f); + } + return mi; } diff --git a/libs/gltfio/src/extended/AssetLoaderExtended.cpp b/libs/gltfio/src/extended/AssetLoaderExtended.cpp index 19ee1eed77a..37434783911 100644 --- a/libs/gltfio/src/extended/AssetLoaderExtended.cpp +++ b/libs/gltfio/src/extended/AssetLoaderExtended.cpp @@ -527,11 +527,13 @@ bool AssetLoaderExtended::createPrimitive(Input* input, Output* out, if (!mCgltfBuffersLoaded) { mCgltfBuffersLoaded = utility::loadCgltfBuffers(gltf, mGltfPath.c_str(), mUriDataCache); - if (!mCgltfBuffersLoaded) return false; + if (!mCgltfBuffersLoaded) { + return false; + } + utility::decodeMeshoptCompression(gltf); } utility::decodeDracoMeshes(gltf, prim, input->dracoCache); - utility::decodeMeshoptCompression(gltf); auto slots = computeGeometries(prim, jobType, attributesMap, morphTargets, out->uvmap, mEngine); diff --git a/libs/viewer/include/viewer/AutomationEngine.h b/libs/viewer/include/viewer/AutomationEngine.h index 8747f59d8da..f0d907a0f7a 100644 --- a/libs/viewer/include/viewer/AutomationEngine.h +++ b/libs/viewer/include/viewer/AutomationEngine.h @@ -224,6 +224,9 @@ class UTILS_PUBLIC AutomationEngine { */ static void exportSettings(const Settings& settings, const char* filename); + static void exportScreenshot(View* view, Renderer* renderer, std::string filename, + bool autoclose, AutomationEngine* automationEngine); + Options getOptions() const { return mOptions; } bool isRunning() const { return mIsRunning; } size_t currentTest() const { return mCurrentTest; } diff --git a/libs/viewer/src/AutomationEngine.cpp b/libs/viewer/src/AutomationEngine.cpp index 19d23ef1681..810df99dbb8 100644 --- a/libs/viewer/src/AutomationEngine.cpp +++ b/libs/viewer/src/AutomationEngine.cpp @@ -56,7 +56,7 @@ static void convertRGBAtoRGB(void* buffer, uint32_t width, uint32_t height) { } } -static void exportScreenshot(View* view, Renderer* renderer, std::string filename, +void AutomationEngine::exportScreenshot(View* view, Renderer* renderer, std::string filename, bool autoclose, AutomationEngine* automationEngine) { const Viewport& vp = view->getViewport(); const size_t byteCount = vp.width * vp.height * 4; @@ -244,7 +244,8 @@ void AutomationEngine::tick(Engine* engine, const ViewerContent& content, float } if (mOptions.exportScreenshots) { - exportScreenshot(content.view, content.renderer, prefix + ".ppm", isLastTest, this); + AutomationEngine::exportScreenshot( + content.view, content.renderer, prefix + ".ppm", isLastTest, this); } if (isLastTest) { diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index 47d8c843698..6206c0c9a46 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -61,8 +61,10 @@ #include #include #include +#include #include #include +#include #include #include "generated/resources/gltf_demo.h" @@ -137,6 +139,8 @@ struct App { AutomationSpec* automationSpec = nullptr; AutomationEngine* automationEngine = nullptr; + bool screenshot = false; + uint8_t screenshotSeq = 0; }; static const char* DEFAULT_IBL = "assets/ibl/lightroom_14b"; @@ -877,10 +881,15 @@ int main(int argc, char** argv) { if (ImGui::CollapsingHeader("Debug")) { auto& debug = engine->getDebugRegistry(); - if (ImGui::Button("Capture frame")) { - bool* captureFrame = - debug.getPropertyAddress("d.renderer.doFrameCapture"); - *captureFrame = true; + if (engine->getBackend() == Engine::Backend::METAL) { + if (ImGui::Button("Capture frame")) { + bool* captureFrame = + debug.getPropertyAddress("d.renderer.doFrameCapture"); + *captureFrame = true; + } + } + if (ImGui::Button("Screenshot")) { + app.screenshot = true; } ImGui::Checkbox("Disable buffer padding", debug.getPropertyAddress("d.renderer.disable_buffer_padding")); @@ -1136,6 +1145,14 @@ int main(int argc, char** argv) { }; auto postRender = [&app](Engine* engine, View* view, Scene*, Renderer* renderer) { + if (app.screenshot) { + std::ostringstream stringStream; + stringStream << "screenshot" << std::setfill('0') << std::setw(2) << +app.screenshotSeq; + AutomationEngine::exportScreenshot( + view, renderer, stringStream.str() + ".ppm", false, app.automationEngine); + ++app.screenshotSeq; + app.screenshot = false; + } if (app.automationEngine->shouldClose()) { FilamentApp::get().close(); return; diff --git a/tools/matc/src/matc/ParametersProcessor.cpp b/tools/matc/src/matc/ParametersProcessor.cpp index 828597e51a6..34bbc2d72a3 100644 --- a/tools/matc/src/matc/ParametersProcessor.cpp +++ b/tools/matc/src/matc/ParametersProcessor.cpp @@ -615,14 +615,51 @@ static bool processVariables(MaterialBuilder& builder, const JsonishValue& value } for (size_t i = 0; i < elements.size(); i++) { - auto elementValue = elements[i]; + ParameterPrecision precision = ParameterPrecision::DEFAULT; MaterialBuilder::Variable v = intToVariable(i); - if (elementValue->getType() != JsonishValue::Type::STRING) { + std::string nameString; + + auto elementValue = elements[i]; + if (elementValue->getType() == JsonishValue::Type::OBJECT) { + + JsonishObject const& jsonObject = *elementValue->toJsonObject(); + + const JsonishValue* nameValue = jsonObject.getValue("name"); + if (!nameValue) { + std::cerr << "variables: entry without 'name' key." << std::endl; + return false; + } + if (nameValue->getType() != JsonishValue::STRING) { + std::cerr << "variables: name value must be STRING." << std::endl; + return false; + } + + const JsonishValue* precisionValue = jsonObject.getValue("precision"); + if (precisionValue) { + if (precisionValue->getType() != JsonishValue::STRING) { + std::cerr << "variables: precision must be a STRING." << std::endl; + return false; + } + auto precisionString = precisionValue->toJsonString(); + if (!Enums::isValid(precisionString->getString())){ + return logEnumIssue("variables", *precisionString, Enums::map()); + } + } + + nameString = nameValue->toJsonString()->getString(); + if (precisionValue) { + precision = Enums::toEnum( + precisionValue->toJsonString()->getString()); + } + builder.variable(v, nameString.c_str(), precision); + } else if (elementValue->getType() == JsonishValue::Type::STRING) { + nameString = elementValue->toJsonString()->getString(); + builder.variable(v, nameString.c_str()); + } else { std::cerr << "variables: array index " << i << " is not a STRING. found:" << JsonishValue::typeToString(elementValue->getType()) << std::endl; return false; } - builder.variable(v, elementValue->toJsonString()->getString().c_str()); } return true; diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 21ca5f2f834..7b502cf79ab 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.54.1", + "version": "1.54.2", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js",