From 543b93939ae03fd0d9d1373c5160a231e061df77 Mon Sep 17 00:00:00 2001 From: Matthew Hoffman Date: Mon, 5 May 2025 12:35:21 -0500 Subject: [PATCH 01/34] Optional CMake flag for enabling ASAN for backend and its tests. (#8696) --- filament/backend/CMakeLists.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 6669898637..9d8dfae47d 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -5,6 +5,18 @@ set(TARGET backend) set(PUBLIC_HDR_DIR include) set(GENERATION_ROOT ${CMAKE_CURRENT_BINARY_DIR}) +# ================================================================================================== +# Compilation options +# ================================================================================================== +# +set(BACKEND_SANITIZATION "" CACHE STRING "Sanitization option") +set_property(CACHE BACKEND_SANITIZATION PROPERTY STRINGS ";ASAN") + +set(BACKEND_SANITIZERS) +if (BACKEND_SANITIZATION STREQUAL "ASAN") + set(BACKEND_SANITIZERS -fsanitize=address) +endif() + # ================================================================================================== # Sources and headers # ================================================================================================== @@ -472,6 +484,7 @@ target_compile_options(${TARGET} PRIVATE ${OSMESA_COMPILE_FLAGS} $<$:${OPTIMIZATION_FLAGS}> $<$,$>:${DARWIN_OPTIMIZATION_FLAGS}> + ${BACKEND_SANITIZERS} ) if (FILAMENT_SUPPORTS_METAL) @@ -482,6 +495,8 @@ if (FILAMENT_SUPPORTS_WEBGPU) target_compile_definitions(${TARGET} PRIVATE $<$:FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING>) endif() +target_link_options(${TARGET} PRIVATE ${BACKEND_SANITIZERS}) + target_link_libraries(${TARGET} PRIVATE ${OSMESA_LINKER_FLAGS} $<$,$>:${LINUX_LINKER_OPTIMIZATION_FLAGS}> @@ -551,6 +566,8 @@ if (APPLE AND NOT IOS) test/test_RenderExternalImage.cpp) add_library(backend_test STATIC ${BACKEND_TEST_SRC}) target_link_libraries(backend_test PUBLIC ${BACKEND_TEST_LIBS}) + target_compile_options(backend_test PRIVATE ${BACKEND_SANITIZERS}) + target_link_options(backend_test PRIVATE ${BACKEND_SANITIZERS}) set(BACKEND_TEST_DEPS OSDependent @@ -589,6 +606,7 @@ if (APPLE AND NOT IOS) # linker from removing "unused" symbols. target_link_libraries(backend_test_mac PRIVATE -force_load backend_test) set_target_properties(backend_test_mac PROPERTIES FOLDER Tests) + target_link_options(backend_test_mac PRIVATE ${BACKEND_SANITIZERS}) # This is needed after XCode 15.3 set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) @@ -598,6 +616,8 @@ endif() if (LINUX) add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC}) + target_compile_options(backend_test_linux PRIVATE ${BACKEND_SANITIZERS}) + target_link_options(backend_test_linux PRIVATE ${BACKEND_SANITIZERS}) target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS}) set_target_properties(backend_test_linux PROPERTIES FOLDER Tests) endif() From 4759237e66658d21654283468301adaa404b6af6 Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Wed, 30 Apr 2025 15:51:11 -0400 Subject: [PATCH 02/34] Separate out required and optional features for webgpu. Panic if any required features are not available --- .../src/webgpu/platform/WebGPUPlatform.cpp | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp b/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp index 6cc264d055..f41d71a30b 100644 --- a/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp +++ b/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp @@ -99,22 +99,45 @@ wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) { wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) { // TODO consider passing limits - constexpr std::array desiredFeatures = { - wgpu::FeatureName::DepthClipControl, - wgpu::FeatureName::Depth32FloatStencil8, - wgpu::FeatureName::CoreFeaturesAndLimits }; - std::vector requiredFeatures; - requiredFeatures.reserve(desiredFeatures.size()); + constexpr std::array optionalFeatures = { wgpu::FeatureName::DepthClipControl, + wgpu::FeatureName::Depth32FloatStencil8, wgpu::FeatureName::CoreFeaturesAndLimits }; + + constexpr std::array requiredFeatures = { wgpu::FeatureName::TransientAttachments }; + wgpu::SupportedFeatures supportedFeatures; adapter.GetFeatures(&supportedFeatures); + + std::vector enabledFeatures; + enabledFeatures.reserve(requiredFeatures.size() + optionalFeatures.size()); + std::set_intersection(supportedFeatures.features, - supportedFeatures.features + supportedFeatures.featureCount, desiredFeatures.begin(), - desiredFeatures.end(), std::back_inserter(requiredFeatures)); + supportedFeatures.features + supportedFeatures.featureCount, requiredFeatures.begin(), + requiredFeatures.end(), std::back_inserter(enabledFeatures)); + + if (enabledFeatures.size() != requiredFeatures.size()) { + std::vector missingFeatures; + std::set_difference(requiredFeatures.begin(), requiredFeatures.end(), + supportedFeatures.features, + supportedFeatures.features + supportedFeatures.featureCount, + std::back_inserter(missingFeatures)); + + std::stringstream missingFeaturesStream{}; + for (const auto& entry: missingFeatures) { + missingFeaturesStream << std::to_string(static_cast(entry)) << " "; + } + PANIC_POSTCONDITION("Some required features are not available %s/n", + missingFeaturesStream.str().c_str()); + } + + std::set_intersection(supportedFeatures.features, + supportedFeatures.features + supportedFeatures.featureCount, optionalFeatures.begin(), + optionalFeatures.end(), std::back_inserter(enabledFeatures)); + wgpu::DeviceDescriptor deviceDescriptor{}; deviceDescriptor.label = "graphics_device"; deviceDescriptor.defaultQueue.label = "default_queue"; - deviceDescriptor.requiredFeatureCount = requiredFeatures.size(); - deviceDescriptor.requiredFeatures = requiredFeatures.data(); + deviceDescriptor.requiredFeatureCount = enabledFeatures.size(); + deviceDescriptor.requiredFeatures = enabledFeatures.data(); deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, [](wgpu::Device const&, wgpu::DeviceLostReason const& reason, wgpu::StringView message) { From 4570c2e710ad669e59a5a3f7c2c05dc321316c2e Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Tue, 6 May 2025 11:26:07 -0400 Subject: [PATCH 03/34] WebGPU: Sampler, Texture and Descriptor Set setup Co-authored-by: Andy Hovingh <6198728+AndyHovingh@users.noreply.github.com> --- filament/backend/src/webgpu/WebGPUDriver.cpp | 179 +++++- filament/backend/src/webgpu/WebGPUDriver.h | 3 +- filament/backend/src/webgpu/WebGPUHandles.cpp | 527 +++++++++++++++++- filament/backend/src/webgpu/WebGPUHandles.h | 72 ++- 4 files changed, 714 insertions(+), 67 deletions(-) diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index a6157947f9..e26b69dbe4 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -389,9 +389,7 @@ Handle WebGPUDriver::createTextureS() noexcept { return allocHandle(); } -Handle WebGPUDriver::importTextureS() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); -} +Handle WebGPUDriver::importTextureS() noexcept { return allocHandle(); } Handle WebGPUDriver::createProgramS() noexcept { return allocHandle(); @@ -410,7 +408,7 @@ Handle WebGPUDriver::createIndexBufferS() noexcept { } Handle WebGPUDriver::createTextureViewS() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); + return allocHandle(); } Handle WebGPUDriver::createBufferObjectS() noexcept { @@ -438,7 +436,7 @@ Handle WebGPUDriver::createVertexBufferInfoS() noexcept { } Handle WebGPUDriver::createTextureViewSwizzleS() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); + return allocHandle(); } Handle WebGPUDriver::createDefaultRenderTargetS() noexcept { @@ -450,15 +448,15 @@ Handle WebGPUDriver::createDescriptorSetLayoutS() noexcep } Handle WebGPUDriver::createTextureExternalImageS() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); + return allocHandle(); } Handle WebGPUDriver::createTextureExternalImage2S() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); + return allocHandle(); } Handle WebGPUDriver::createTextureExternalImagePlaneS() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); + return allocHandle(); } void WebGPUDriver::createSwapChainR(Handle sch, void* nativeWindow, uint64_t flags) { @@ -470,6 +468,8 @@ void WebGPUDriver::createSwapChainR(Handle sch, void* nativeWindow, mSwapChain = constructHandle(sch, std::move(surface), surfaceSize, mAdapter, mDevice, flags); assert_invariant(mSwapChain); + WebGPUDescriptorSet::initializeDummyResourcesIfNotAlready(mDevice, + mSwapChain->getColorFormat()); FWGPU_LOGW << "WebGPU support is still essentially a no-op at this point in development (only " "background components have been instantiated/selected, such as surface/screen, " "graphics device/GPU, etc.), thus nothing is being drawn to the screen." @@ -512,30 +512,46 @@ void WebGPUDriver::createBufferObjectR(Handle boh, uint32_t byte void WebGPUDriver::createTextureR(Handle th, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, - TextureUsage usage) {} + TextureUsage usage) { + constructHandle(th, target, levels, format, samples, w, h, depth, usage, mDevice); +} void WebGPUDriver::createTextureViewR(Handle th, Handle srch, - uint8_t baseLevel, uint8_t levelCount) {} + uint8_t baseLevel, uint8_t levelCount) { + auto source = handleCast(srch); + + constructHandle(th, source, baseLevel, levelCount); +} void WebGPUDriver::createTextureViewSwizzleR(Handle th, Handle srch, backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b, - backend::TextureSwizzle a) {} + backend::TextureSwizzle a) { + PANIC_POSTCONDITION("Swizzle WebGPU Texture is not supported"); +} void WebGPUDriver::createTextureExternalImage2R(Handle th, backend::SamplerType target, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, - Platform::ExternalImageHandleRef externalImage) {} + Platform::ExternalImageHandleRef externalImage) { + PANIC_POSTCONDITION("External WebGPU Texture is not supported"); +} void WebGPUDriver::createTextureExternalImageR(Handle th, backend::SamplerType target, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, - void* externalImage) {} + void* externalImage) { + PANIC_POSTCONDITION("External WebGPU Texture is not supported"); +} void WebGPUDriver::createTextureExternalImagePlaneR(Handle th, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, - void* image, uint32_t plane) {} + void* image, uint32_t plane) { + PANIC_POSTCONDITION("External WebGPU Texture is not supported"); +} void WebGPUDriver::importTextureR(Handle th, intptr_t id, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, - uint32_t depth, TextureUsage usage) {} + uint32_t depth, TextureUsage usage) { + PANIC_POSTCONDITION("Import WebGPU Texture is not supported"); +} void WebGPUDriver::createRenderPrimitiveR(Handle rph, Handle vbh, Handle ibh, PrimitiveType pt) { @@ -575,7 +591,7 @@ void WebGPUDriver::createDescriptorSetLayoutR(Handle dslh void WebGPUDriver::createDescriptorSetR(Handle dsh, Handle dslh) { auto layout = handleCast(dslh); - constructHandle(dsh, layout->getLayout(), layout->getLayoutSize()); + constructHandle(dsh, layout->getLayout(), layout->getBindGroupEntries()); } Handle WebGPUDriver::createStreamNative(void* nativeStream) { @@ -612,7 +628,7 @@ FenceStatus WebGPUDriver::getFenceStatus(Handle fh) { // We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that // the GPU supports the given texture format with non-zero optimal tiling features. bool WebGPUDriver::isTextureFormatSupported(TextureFormat format) { - return true; + return WGPUTexture::fToWGPUTextureFormat(format) != wgpu::TextureFormat::Undefined; } bool WebGPUDriver::isTextureSwizzleSupported() { @@ -973,24 +989,22 @@ void WebGPUDriver::updateDescriptorSetBuffer(Handle dsh, void WebGPUDriver::updateDescriptorSetTexture(Handle dsh, backend::descriptor_binding_t binding, Handle th, SamplerParams params) { - /* auto bindGroup = handleCast(dsh); auto texture = handleCast(th); - // TODO very high odds badd assumptions are in here about handling HwTexture. Revisit with more - // understanding. Right now assuming there is a wgpu::TextureView filled in if (!bindGroup->getIsLocked()) { + // Dawn will cache duplicate samplers, so we don't strictly need to maintain a cache. + // Making a cache might save us minor perf by reducing param translation + auto sampler = makeSampler(params); // TODO making assumptions that size and offset mean the same thing here. wgpu::BindGroupEntry tEntry{ .binding = static_cast(binding * 2), - .textureView = texture->texView }; + .textureView = texture->getTexView() }; bindGroup->addEntry(tEntry.binding, std::move(tEntry)); wgpu::BindGroupEntry sEntry{ .binding = static_cast(binding * 2 + 1), - .sampler = texture->sampler }; + .sampler = sampler }; bindGroup->addEntry(sEntry.binding, std::move(sEntry)); } - //TODO Just the setup, this function stilll needs the rest of logic implemented - */ } void WebGPUDriver::bindDescriptorSet(Handle dsh, backend::descriptor_set_t set, @@ -1002,5 +1016,122 @@ void WebGPUDriver::bindDescriptorSet(Handle dsh, backend::descr void WebGPUDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) { } +wgpu::Sampler WebGPUDriver::makeSampler(SamplerParams const& params) { + wgpu::SamplerDescriptor desc; + + desc.label = "TODO"; + desc.addressModeU = fWrapModeToWAddressMode(params.wrapS); + desc.addressModeV = fWrapModeToWAddressMode(params.wrapR); + desc.addressModeW = fWrapModeToWAddressMode(params.wrapT); + switch (params.filterMag) { + case SamplerMagFilter::NEAREST: { + desc.magFilter = wgpu::FilterMode::Nearest; + break; + } + case SamplerMagFilter::LINEAR: { + desc.magFilter = wgpu::FilterMode::Linear; + break; + } + } + switch (params.filterMin) { + case SamplerMinFilter::NEAREST: { + desc.minFilter = wgpu::FilterMode::Nearest; + // Metal Driver uses an explicit not-mipmapped value webgpu lacks. Nearest should + // suffice + desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest; + break; + } + case SamplerMinFilter::LINEAR: { + desc.minFilter = wgpu::FilterMode::Linear; + // Metal Driver uses an explicit not-mipmapped value webgpu lacks. Nearest should + // suffice + + desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest; + break; + } + case SamplerMinFilter::NEAREST_MIPMAP_NEAREST: { + desc.minFilter = wgpu::FilterMode::Nearest; + desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest; + break; + } + case SamplerMinFilter::LINEAR_MIPMAP_NEAREST: { + desc.minFilter = wgpu::FilterMode::Linear; + desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest; + + break; + } + case SamplerMinFilter::NEAREST_MIPMAP_LINEAR: { + desc.minFilter = wgpu::FilterMode::Nearest; + desc.mipmapFilter = wgpu::MipmapFilterMode::Linear; + + break; + } + case SamplerMinFilter::LINEAR_MIPMAP_LINEAR: { + desc.minFilter = wgpu::FilterMode::Linear; + desc.mipmapFilter = wgpu::MipmapFilterMode::Linear; + break; + } + } + switch (params.compareFunc) { + case SamplerCompareFunc::LE: { + desc.compare = wgpu::CompareFunction::LessEqual; + break; + } + case SamplerCompareFunc::GE: { + desc.compare = wgpu::CompareFunction::GreaterEqual; + break; + } + case SamplerCompareFunc::L: { + desc.compare = wgpu::CompareFunction::Less; + break; + } + case SamplerCompareFunc::G: { + desc.compare = wgpu::CompareFunction::Greater; + break; + } + case SamplerCompareFunc::E: { + desc.compare = wgpu::CompareFunction::Equal; + break; + } + case SamplerCompareFunc::NE: { + desc.compare = wgpu::CompareFunction::NotEqual; + break; + } + case SamplerCompareFunc::A: { + desc.compare = wgpu::CompareFunction::Always; + break; + } + case SamplerCompareFunc::N: { + desc.compare = wgpu::CompareFunction::Never; + break; + } + } + + desc.maxAnisotropy = 1u << params.anisotropyLog2; + + + // Unused: Filament's compareMode, WGPU lodMinClamp/lodMaxClamp + + //TODO Once we can properly map to descriptorsetlayout use the sampler. + return mDevice.CreateSampler(/*&desc*/); +} +wgpu::AddressMode WebGPUDriver::fWrapModeToWAddressMode(const SamplerWrapMode& fWrapMode) { + switch (fWrapMode) { + case SamplerWrapMode::CLAMP_TO_EDGE: { + return wgpu::AddressMode::ClampToEdge; + break; + } + case SamplerWrapMode::REPEAT: { + return wgpu::AddressMode::Repeat; + break; + } + case SamplerWrapMode::MIRRORED_REPEAT: { + return wgpu::AddressMode::MirrorRepeat; + break; + } + } + return wgpu::AddressMode::Undefined; +} + } // namespace filament diff --git a/filament/backend/src/webgpu/WebGPUDriver.h b/filament/backend/src/webgpu/WebGPUDriver.h index df85e64971..2f8386d552 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.h +++ b/filament/backend/src/webgpu/WebGPUDriver.h @@ -56,7 +56,8 @@ private: explicit WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept; [[nodiscard]] ShaderModel getShaderModel() const noexcept final; [[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final; - + [[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params); + [[nodiscard]] static wgpu::AddressMode fWrapModeToWAddressMode(const filament::backend::SamplerWrapMode& fUsage); template void updateGPUBuffer(GPUBufferObject* gpuBufferObject, BufferDescriptor&& bufferDescriptor, uint32_t byteOffset) { diff --git a/filament/backend/src/webgpu/WebGPUHandles.cpp b/filament/backend/src/webgpu/WebGPUHandles.cpp index 9e8f951ae7..b1cedb701f 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.cpp +++ b/filament/backend/src/webgpu/WebGPUHandles.cpp @@ -16,7 +16,14 @@ #include "WebGPUHandles.h" +#include + +#include + +#include +#include #include +#include namespace { constexpr wgpu::BufferUsage getBufferObjectUsage( @@ -205,23 +212,28 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& std::vector wEntries; wEntries.reserve(layout.bindings.size() + samplerCount); + mBindGroupEntries.reserve(wEntries.capacity()); for (auto fEntry: layout.bindings) { auto& wEntry = wEntries.emplace_back(); + auto& entryInfo = mBindGroupEntries.emplace_back(); wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags); wEntry.binding = fEntry.binding * 2; + entryInfo.binding = wEntry.binding; switch (fEntry.type) { - // TODO Metal treats these the same. Is this fine? case DescriptorType::SAMPLER_EXTERNAL: case DescriptorType::SAMPLER: { - // Sampler binding is 2n+1 due to split. auto& samplerEntry = wEntries.emplace_back(); + auto& samplerEntryInfo = mBindGroupEntries.emplace_back(); samplerEntry.binding = fEntry.binding * 2 + 1; + samplerEntryInfo.binding = samplerEntry.binding; + samplerEntryInfo.type = WebGPUDescriptorSetLayout::BindGroupEntryType::SAMPLER; samplerEntry.visibility = wEntry.visibility; // We are simply hoping that undefined and defaults suffices here. - samplerEntry.sampler.type = wgpu::SamplerBindingType::Undefined; - wEntry.texture.sampleType = wgpu::TextureSampleType::Undefined; + samplerEntry.sampler.type = wgpu::SamplerBindingType::NonFiltering; // Example default + wEntry.texture.sampleType = wgpu::TextureSampleType::Float; // Example default + entryInfo.type = WebGPUDescriptorSetLayout::BindGroupEntryType::TEXTURE_VIEW; break; } case DescriptorType::UNIFORM_BUFFER: { @@ -233,54 +245,151 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& } case DescriptorType::INPUT_ATTACHMENT: { - // TODO: support INPUT_ATTACHMENT. Metal does not currently. PANIC_POSTCONDITION("Input Attachment is not supported"); break; } - case DescriptorType::SHADER_STORAGE_BUFFER: { - // TODO: Vulkan does not support this, can we? PANIC_POSTCONDITION("Shader storage is not supported"); break; } } - - // Currently flags are only used to specify dynamic offset. - - // UNUSED - // fEntry.count + // fEntry.count is unused currently } - + std::string label = "layout_"+ std::to_string(++layoutNum) ; wgpu::BindGroupLayoutDescriptor layoutDescriptor{ - // TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for - // debugging. For now, hack an incrementing value. - .label{ "layout_" + std::to_string(++layoutNum) }, + .label{label.c_str()}, // Use .c_str() if label needs to be const char* .entryCount = wEntries.size(), .entries = wEntries.data() }; - // TODO Do we need to defer this until we have more info on textures and samplers?? - mLayoutSize = wEntries.size(); mLayout = device.CreateBindGroupLayout(&layoutDescriptor); } WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {} -WebGPUDescriptorSet::WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, uint layoutSize) +wgpu::Buffer WebGPUDescriptorSet::sDummyUniformBuffer = nullptr; +wgpu::Texture WebGPUDescriptorSet::sDummyTexture = nullptr; +wgpu::TextureView WebGPUDescriptorSet::sDummyTextureView = nullptr; +wgpu::Sampler WebGPUDescriptorSet::sDummySampler = nullptr; + +void WebGPUDescriptorSet::initializeDummyResourcesIfNotAlready(wgpu::Device const& device, + wgpu::TextureFormat aColorFormat) { + if (!sDummyUniformBuffer) { + wgpu::BufferDescriptor bufferDescriptor{ + .label = "dummy_uniform_not_to_be_used", + .usage = wgpu::BufferUsage::Uniform, + .size = 4 + }; + sDummyUniformBuffer = device.CreateBuffer(&bufferDescriptor); + FILAMENT_CHECK_POSTCONDITION(sDummyUniformBuffer) + << "Failed to create dummy uniform buffer?"; + } + if (!sDummyTexture || !sDummyTextureView) { + wgpu::TextureDescriptor textureDescriptor{ + .label = "dummy_texture_not_to_be_used", + .usage = wgpu::TextureUsage::TextureBinding, + .dimension = wgpu::TextureDimension::e2D, + .size = wgpu::Extent3D{ .width = 4, .height = 4, .depthOrArrayLayers = 1 }, + .format = aColorFormat, + }; + if (!sDummyTexture) { + sDummyTexture = device.CreateTexture(&textureDescriptor); + FILAMENT_CHECK_POSTCONDITION(sDummyUniformBuffer) << "Failed to create dummy texture?"; + } + if (!sDummyTextureView) { + wgpu::TextureViewDescriptor textureViewDescriptor{ + .label = "dummy_texture_view_not_to_be_used" + }; + sDummyTextureView = sDummyTexture.CreateView(&textureViewDescriptor); + FILAMENT_CHECK_POSTCONDITION(sDummyUniformBuffer) + << "Failed to create dummy texture view?"; + } + } + if (!sDummySampler) { + wgpu::SamplerDescriptor samplerDescriptor{ + .label = "dummy_sampler_not_to_be_used" + }; + sDummySampler = device.CreateSampler(&samplerDescriptor); + FILAMENT_CHECK_POSTCONDITION(sDummyUniformBuffer) << "Failed to create dummy sampler?"; + } +} + +std::vector WebGPUDescriptorSet::createDummyEntriesSortedByBinding( + std::vector const& + bindGroupEntries) { + assert_invariant(WebGPUDescriptorSet::sDummyUniformBuffer && + "Dummy uniform buffer must have been created before " + "creating dummy bind group entries."); + assert_invariant( + WebGPUDescriptorSet::sDummyTexture && + "Dummy texture must have been created before creating dummy bind group entries."); + assert_invariant( + WebGPUDescriptorSet::sDummyTextureView && + "Dummy texture view must have been created before creating dummy bind group entries."); + assert_invariant( + WebGPUDescriptorSet::sDummySampler && + "Dummy sampler must have been created before creating dummy bind group entries."); + using filament::backend::WebGPUDescriptorSetLayout; + std::vector entries; + entries.reserve(bindGroupEntries.size()); + for (auto const& entryInfo: bindGroupEntries) { + auto& entry = entries.emplace_back(); + entry.binding = entryInfo.binding; + switch (entryInfo.type) { + case WebGPUDescriptorSetLayout::BindGroupEntryType::UNIFORM_BUFFER: + entry.buffer = WebGPUDescriptorSet::sDummyUniformBuffer; + break; + case WebGPUDescriptorSetLayout::BindGroupEntryType::TEXTURE_VIEW: + entry.textureView = WebGPUDescriptorSet::sDummyTextureView; + break; + case WebGPUDescriptorSetLayout::BindGroupEntryType::SAMPLER: + entry.sampler = WebGPUDescriptorSet::sDummySampler; + break; + } + } + std::sort(entries.begin(), entries.end(), + [](wgpu::BindGroupEntry const& a, wgpu::BindGroupEntry const& b) { + return a.binding < b.binding; + }); + return entries; +} + +WebGPUDescriptorSet::WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, + std::vector const& bindGroupEntries) : mLayout(layout), - entries(layoutSize, wgpu::BindGroupEntry{}) { + mEntriesSortedByBinding(createDummyEntriesSortedByBinding(bindGroupEntries)) { // Establish the size of entries based on the layout. This should be reliable and efficient. + assert_invariant(INVALID_INDEX > mEntryIndexByBinding.size()); + for (size_t i = 0; i < mEntryIndexByBinding.size(); i++) { + mEntryIndexByBinding[i] = INVALID_INDEX; + } + for (size_t index = 0; index < mEntriesSortedByBinding.size(); index++) { + wgpu::BindGroupEntry const& entry = mEntriesSortedByBinding[index]; + assert_invariant(entry.binding < mEntryIndexByBinding.size()); + mEntryIndexByBinding[entry.binding] = static_cast(index); + } +} + +WebGPUDescriptorSet::~WebGPUDescriptorSet() { + mBindGroup = nullptr; + mLayout = nullptr; } -WebGPUDescriptorSet::~WebGPUDescriptorSet() {} wgpu::BindGroup WebGPUDescriptorSet::lockAndReturn(const wgpu::Device& device) { if (mBindGroup) { return mBindGroup; } // TODO label? Should we just copy layout label? - wgpu::BindGroupDescriptor desc{ .layout = mLayout, - .entryCount = entries.size(), - .entries = entries.data() }; + wgpu::BindGroupDescriptor desc{ + .layout = mLayout, + .entryCount = mEntriesSortedByBinding.size(), + .entries = mEntriesSortedByBinding.data() + }; mBindGroup = device.CreateBindGroup(&desc); + FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group?"; + // once we have created the bind group itself we should no longer need any other state + mLayout = nullptr; + mEntriesSortedByBinding.clear(); + mEntriesSortedByBinding.shrink_to_fit(); return mBindGroup; } @@ -292,6 +401,374 @@ void WebGPUDescriptorSet::addEntry(uint index, wgpu::BindGroupEntry&& entry) { } // TODO: Putting some level of trust that Filament is not going to reuse indexes or go past the // layout index for efficiency. Add guards if wrong. - entries[index] = std::move(entry); + FILAMENT_CHECK_POSTCONDITION(index < mEntryIndexByBinding.size()) + << "impossible/invalid index for a descriptor/binding (our of range or >= " + "MAX_DESCRIPTOR_COUNT) " + << index; + uint8_t entryIndex = mEntryIndexByBinding[index]; + FILAMENT_CHECK_POSTCONDITION( + entryIndex != INVALID_INDEX && entryIndex < mEntriesSortedByBinding.size()) + << "Invalid binding " << index; + entry.binding = index; + mEntriesSortedByBinding[entryIndex] = std::move(entry); } + +// From createTextureR +WGPUTexture::WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, + uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, + wgpu::Device device) noexcept { + + // First the texture aspect + wgpu::TextureDescriptor desc; + + switch (target) { + case SamplerType::SAMPLER_CUBEMAP: + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + case SamplerType::SAMPLER_2D: + case SamplerType::SAMPLER_2D_ARRAY: + // Should be safe to assume external is 2d + case SamplerType::SAMPLER_EXTERNAL: { + desc.dimension = wgpu::TextureDimension::e2D; + break; + } + case SamplerType::SAMPLER_3D: { + desc.dimension = wgpu::TextureDimension::e3D; + break; + } + } + desc.size = { .width = width, .height = height, .depthOrArrayLayers = depth }; + desc.format = fToWGPUTextureFormat(format); + assert_invariant(desc.format != wgpu::TextureFormat::Undefined); + + // WGPU requires this to be true. Filament should comply + assert(samples == 1 || samples || 4); + + desc.sampleCount = samples; + desc.usage = fToWGPUTextureUsage(usage); + desc.mipLevelCount = levels; + + // TODO Is this fine? Could do all-the-things, a naive mapping or get something from Filament + desc.viewFormats = nullptr; + + texture = device.CreateTexture(&desc); + + // TODO should a default levelCount be something other than 0? Sample count? + texView = makeTextureView(0, 1); +} +// From createTextureViewR +WGPUTexture::WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept { + texture = src->texture; + texView = makeTextureView(baseLevel, levelCount); +} + +wgpu::TextureUsage WGPUTexture::fToWGPUTextureUsage(const TextureUsage& fUsage) { + wgpu::TextureUsage retUsage = wgpu::TextureUsage::None; + + // Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini + // TODO Validate assumptions, revisit if issues. + if (any(TextureUsage::BLIT_SRC & fUsage)) { + retUsage |= wgpu::TextureUsage::CopySrc; + } + if (any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) { + retUsage |= wgpu::TextureUsage::CopyDst; + } + if (any(TextureUsage::SAMPLEABLE & fUsage)) { + retUsage |= wgpu::TextureUsage::TextureBinding; + } + // WGPU Render attachment covers either color or stencil situation dependant + // NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If + // issues, investigate here. + if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT | + TextureUsage::DEPTH_ATTACHMENT) & + fUsage)) { + retUsage |= wgpu::TextureUsage::RenderAttachment; + } + + // This is from Vulkan logic- if there are any issues try disabling this first, allows perf + // benefit though + const bool useTransientAttachment = + // Usage consists of attachment flags only. + none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) && + // Usage contains at least one attachment flag. + any(fUsage & TextureUsage::ALL_ATTACHMENTS) && + // Depth resolve cannot use transient attachment because it uses a custom shader. + // TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this + // restriction. + // Note that the custom shader does not resolve stencil. We do need to move to vk 1.2 + // and above to be able to support stencil resolve (along with depth). + !(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1); + if (useTransientAttachment) { + retUsage |= wgpu::TextureUsage::TransientAttachment; + } + // NOTE: Unused wgpu flags: + // StorageBinding + // StorageAttachment + + // NOTE: Unused Filament flags: + // SUBPASS_INPUT VK goes to input attachment which we don't support right now + // PROTECTED + return retUsage; +} +wgpu::TextureFormat WGPUTexture::fToWGPUTextureFormat(const TextureFormat& fUsage) { + switch (fUsage) { + case filament::backend::TextureFormat::R8: + return wgpu::TextureFormat::R8Unorm; + case filament::backend::TextureFormat::R8_SNORM: + return wgpu::TextureFormat::R8Snorm; + case filament::backend::TextureFormat::R8UI: + return wgpu::TextureFormat::R8Uint; + case filament::backend::TextureFormat::R8I: + return wgpu::TextureFormat::R8Sint; + case filament::backend::TextureFormat::STENCIL8: + return wgpu::TextureFormat::Stencil8; + case filament::backend::TextureFormat::R16F: + return wgpu::TextureFormat::R16Float; + case filament::backend::TextureFormat::R16UI: + return wgpu::TextureFormat::R16Uint; + case filament::backend::TextureFormat::R16I: + return wgpu::TextureFormat::R16Sint; + case filament::backend::TextureFormat::RG8: + return wgpu::TextureFormat::RG8Unorm; + case filament::backend::TextureFormat::RG8_SNORM: + return wgpu::TextureFormat::RG8Snorm; + case filament::backend::TextureFormat::RG8UI: + return wgpu::TextureFormat::RG8Uint; + case filament::backend::TextureFormat::RG8I: + return wgpu::TextureFormat::RG8Sint; + case filament::backend::TextureFormat::R32F: + return wgpu::TextureFormat::R32Float; + case filament::backend::TextureFormat::R32UI: + return wgpu::TextureFormat::R32Uint; + case filament::backend::TextureFormat::R32I: + return wgpu::TextureFormat::R32Sint; + case filament::backend::TextureFormat::RG16F: + return wgpu::TextureFormat::RG16Float; + case filament::backend::TextureFormat::RG16UI: + return wgpu::TextureFormat::RG16Uint; + case filament::backend::TextureFormat::RG16I: + return wgpu::TextureFormat::RG16Sint; + case filament::backend::TextureFormat::RGBA8: + return wgpu::TextureFormat::RGBA8Unorm; + case filament::backend::TextureFormat::SRGB8_A8: + return wgpu::TextureFormat::RGBA8UnormSrgb; + case filament::backend::TextureFormat::RGBA8_SNORM: + return wgpu::TextureFormat::RGBA8Snorm; + case filament::backend::TextureFormat::RGBA8UI: + return wgpu::TextureFormat::RGBA8Uint; + case filament::backend::TextureFormat::RGBA8I: + return wgpu::TextureFormat::RGBA8Sint; + case filament::backend::TextureFormat::DEPTH16: + return wgpu::TextureFormat::Depth16Unorm; + case filament::backend::TextureFormat::DEPTH24: + return wgpu::TextureFormat::Depth24Plus; + case filament::backend::TextureFormat::DEPTH32F: + return wgpu::TextureFormat::Depth32Float; + case filament::backend::TextureFormat::DEPTH24_STENCIL8: + return wgpu::TextureFormat::Depth24PlusStencil8; + case filament::backend::TextureFormat::DEPTH32F_STENCIL8: + return wgpu::TextureFormat::Depth32FloatStencil8; + case filament::backend::TextureFormat::RG32F: + return wgpu::TextureFormat::RG32Float; + case filament::backend::TextureFormat::RG32UI: + return wgpu::TextureFormat::RG32Uint; + case filament::backend::TextureFormat::RG32I: + return wgpu::TextureFormat::RG32Sint; + case filament::backend::TextureFormat::RGBA16F: + return wgpu::TextureFormat::RGBA16Float; + case filament::backend::TextureFormat::RGBA16UI: + return wgpu::TextureFormat::RGBA16Uint; + case filament::backend::TextureFormat::RGBA16I: + return wgpu::TextureFormat::RGBA16Sint; + case filament::backend::TextureFormat::RGBA32F: + return wgpu::TextureFormat::RGBA32Float; + case filament::backend::TextureFormat::RGBA32UI: + return wgpu::TextureFormat::RGBA32Uint; + case filament::backend::TextureFormat::RGBA32I: + return wgpu::TextureFormat::RGBA32Sint; + case filament::backend::TextureFormat::EAC_R11: + return wgpu::TextureFormat::EACR11Unorm; + case filament::backend::TextureFormat::EAC_R11_SIGNED: + return wgpu::TextureFormat::EACR11Snorm; + case filament::backend::TextureFormat::EAC_RG11: + return wgpu::TextureFormat::EACRG11Unorm; + case filament::backend::TextureFormat::EAC_RG11_SIGNED: + return wgpu::TextureFormat::EACRG11Snorm; + case filament::backend::TextureFormat::ETC2_RGB8: + return wgpu::TextureFormat::ETC2RGB8Unorm; + case filament::backend::TextureFormat::ETC2_SRGB8: + return wgpu::TextureFormat::ETC2RGB8UnormSrgb; + case filament::backend::TextureFormat::ETC2_RGB8_A1: + return wgpu::TextureFormat::ETC2RGB8A1Unorm; + case filament::backend::TextureFormat::ETC2_SRGB8_A1: + return wgpu::TextureFormat::ETC2RGB8A1UnormSrgb; + case filament::backend::TextureFormat::ETC2_EAC_RGBA8: + return wgpu::TextureFormat::ETC2RGBA8Unorm; + case filament::backend::TextureFormat::ETC2_EAC_SRGBA8: + return wgpu::TextureFormat::ETC2RGBA8UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_4x4: + return wgpu::TextureFormat::ASTC4x4Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_4x4: + return wgpu::TextureFormat::ASTC4x4UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_5x4: + return wgpu::TextureFormat::ASTC5x4Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_5x4: + return wgpu::TextureFormat::ASTC5x4UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_5x5: + return wgpu::TextureFormat::ASTC5x5Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_5x5: + return wgpu::TextureFormat::ASTC5x5UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_6x5: + return wgpu::TextureFormat::ASTC6x5Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_6x5: + return wgpu::TextureFormat::ASTC6x5UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_6x6: + return wgpu::TextureFormat::ASTC6x6Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_6x6: + return wgpu::TextureFormat::ASTC6x6UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_8x5: + return wgpu::TextureFormat::ASTC8x5Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_8x5: + return wgpu::TextureFormat::ASTC8x5UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_8x6: + return wgpu::TextureFormat::ASTC8x6Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_8x6: + return wgpu::TextureFormat::ASTC8x6UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_8x8: + return wgpu::TextureFormat::ASTC8x8Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_8x8: + return wgpu::TextureFormat::ASTC8x8UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_10x5: + return wgpu::TextureFormat::ASTC10x5Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x5: + return wgpu::TextureFormat::ASTC10x5UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_10x6: + return wgpu::TextureFormat::ASTC10x6Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x6: + return wgpu::TextureFormat::ASTC10x6UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_10x8: + return wgpu::TextureFormat::ASTC10x8Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x8: + return wgpu::TextureFormat::ASTC10x8UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_10x10: + return wgpu::TextureFormat::ASTC10x10Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x10: + return wgpu::TextureFormat::ASTC10x10UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_12x10: + return wgpu::TextureFormat::ASTC12x10Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_12x10: + return wgpu::TextureFormat::ASTC12x10UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_12x12: + return wgpu::TextureFormat::ASTC12x12Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_12x12: + return wgpu::TextureFormat::ASTC12x12UnormSrgb; + case filament::backend::TextureFormat::RED_RGTC1: + return wgpu::TextureFormat::BC4RUnorm; + case filament::backend::TextureFormat::SIGNED_RED_RGTC1: + return wgpu::TextureFormat::BC4RSnorm; + case filament::backend::TextureFormat::RED_GREEN_RGTC2: + return wgpu::TextureFormat::BC5RGUnorm; + case filament::backend::TextureFormat::SIGNED_RED_GREEN_RGTC2: + return wgpu::TextureFormat::BC5RGSnorm; + case filament::backend::TextureFormat::RGB_BPTC_UNSIGNED_FLOAT: + return wgpu::TextureFormat::BC6HRGBUfloat; + case filament::backend::TextureFormat::RGB_BPTC_SIGNED_FLOAT: + return wgpu::TextureFormat::BC6HRGBFloat; + case filament::backend::TextureFormat::RGBA_BPTC_UNORM: + return wgpu::TextureFormat::BC7RGBAUnorm; + case filament::backend::TextureFormat::SRGB_ALPHA_BPTC_UNORM: + return wgpu::TextureFormat::BC7RGBAUnormSrgb; + case filament::backend::TextureFormat::RGB565: + // No direct mapping in wgpu. Could potentially map to RGBA8Unorm + // and discard the alpha and lower precision. + return wgpu::TextureFormat::Undefined; + case filament::backend::TextureFormat::RGB9_E5: + return wgpu::TextureFormat::RGB9E5Ufloat; + case filament::backend::TextureFormat::RGB5_A1: + // No direct mapping in wgpu. Could potentially map to RGBA8Unorm + // and handle the packing/unpacking in shaders. + return wgpu::TextureFormat::Undefined; + case filament::backend::TextureFormat::RGBA4: + // No direct mapping in wgpu. Could potentially map to RGBA8Unorm + // and handle the packing/unpacking in shaders. + return wgpu::TextureFormat::Undefined; + case filament::backend::TextureFormat::RGB8: + // No direct sRGB equivalent in wgpu without alpha. + return wgpu::TextureFormat::RGBA8Unorm; + case filament::backend::TextureFormat::SRGB8: + // No direct sRGB equivalent in wgpu without alpha. + return wgpu::TextureFormat::RGBA8UnormSrgb; + case filament::backend::TextureFormat::RGB8_SNORM: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA8Snorm; + case filament::backend::TextureFormat::RGB8UI: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA8Uint; + case filament::backend::TextureFormat::RGB8I: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA8Sint; + case filament::backend::TextureFormat::R11F_G11F_B10F: + return wgpu::TextureFormat::RG11B10Ufloat; + case filament::backend::TextureFormat::UNUSED: + return wgpu::TextureFormat::Undefined; + case filament::backend::TextureFormat::RGB10_A2: + return wgpu::TextureFormat::RGB10A2Unorm; + case filament::backend::TextureFormat::RGB16F: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA16Float; + case filament::backend::TextureFormat::RGB16UI: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA16Uint; + case filament::backend::TextureFormat::RGB16I: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA16Sint; + case filament::backend::TextureFormat::RGB32F: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA32Float; + case filament::backend::TextureFormat::RGB32UI: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA32Uint; + case filament::backend::TextureFormat::RGB32I: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA32Sint; + case filament::backend::TextureFormat::DXT1_RGB: + return wgpu::TextureFormat::BC1RGBAUnorm; + case filament::backend::TextureFormat::DXT1_RGBA: + return wgpu::TextureFormat::BC1RGBAUnorm; + case filament::backend::TextureFormat::DXT3_RGBA: + return wgpu::TextureFormat::BC2RGBAUnorm; + case filament::backend::TextureFormat::DXT5_RGBA: + return wgpu::TextureFormat::BC3RGBAUnorm; + case filament::backend::TextureFormat::DXT1_SRGB: + return wgpu::TextureFormat::BC1RGBAUnormSrgb; + case filament::backend::TextureFormat::DXT1_SRGBA: + return wgpu::TextureFormat::BC1RGBAUnormSrgb; + case filament::backend::TextureFormat::DXT3_SRGBA: + return wgpu::TextureFormat::BC2RGBAUnormSrgb; + case filament::backend::TextureFormat::DXT5_SRGBA: + return wgpu::TextureFormat::BC3RGBAUnormSrgb; + } +} + +wgpu::TextureView WGPUTexture::makeTextureView(const uint8_t& baseLevel, + const uint8_t& levelCount) { + wgpu::TextureViewDescriptor desc; + desc.baseMipLevel = baseLevel; + desc.mipLevelCount = levelCount; + + // baseArrayLayer is required, making a guess + desc.baseArrayLayer = 0; + // Have not found an analouge to aspect in other drivers, but ALL should be unrestrictive. + // TODO Can we make this better? + desc.aspect = wgpu::TextureAspect::All; + + // The rest of the properties should be fine to leave as default, using the texture params. + desc.label = "TODO"; + + desc.format = wgpu::TextureFormat::Undefined; + desc.dimension = wgpu::TextureViewDimension::Undefined; + desc.usage = wgpu::TextureUsage::None; + + return texture.CreateView(&desc); +} + }// namespace filament::backend diff --git a/filament/backend/src/webgpu/WebGPUHandles.h b/filament/backend/src/webgpu/WebGPUHandles.h index 3c936e904f..4b21ffb5f9 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.h +++ b/filament/backend/src/webgpu/WebGPUHandles.h @@ -27,6 +27,7 @@ #include +#include #include #include @@ -98,55 +99,92 @@ struct WGPUBufferObject : HwBufferObject { wgpu::Buffer buffer = nullptr; const BufferObjectBinding bufferObjectBinding; }; + class WebGPUDescriptorSetLayout final : public HwDescriptorSetLayout { public: + + enum class BindGroupEntryType : uint8_t { + UNIFORM_BUFFER, + TEXTURE_VIEW, + SAMPLER + }; + + struct BindGroupEntryInfo final { + uint8_t binding = 0; + BindGroupEntryType type = BindGroupEntryType::UNIFORM_BUFFER; + }; + WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const& device); ~WebGPUDescriptorSetLayout(); [[nodiscard]] const wgpu::BindGroupLayout& getLayout() const { return mLayout; } - [[nodiscard]] uint getLayoutSize() const { return mLayoutSize; } + [[nodiscard]] std::vector const& getBindGroupEntries() const { + return mBindGroupEntries; + } private: // TODO: If this is useful elsewhere, remove it from this class // Convert Filament Shader Stage Flags bitmask to webgpu equivilant static wgpu::ShaderStage filamentStageToWGPUStage(ShaderStageFlags fFlags); - uint mLayoutSize; + std::vector mBindGroupEntries; wgpu::BindGroupLayout mLayout; }; class WebGPUDescriptorSet final : public HwDescriptorSet { public: - WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, uint layoutSize); + static void initializeDummyResourcesIfNotAlready(wgpu::Device const&, + wgpu::TextureFormat aColorFormat); + + WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, + std::vector const& bindGroupEntries); ~WebGPUDescriptorSet(); - wgpu::BindGroup lockAndReturn(wgpu::Device const& device); + wgpu::BindGroup lockAndReturn(wgpu::Device const&); void addEntry(uint index, wgpu::BindGroupEntry&& entry); [[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; } private: + + static wgpu::Buffer sDummyUniformBuffer; + static wgpu::Texture sDummyTexture; + static wgpu::TextureView sDummyTextureView; + static wgpu::Sampler sDummySampler; + + static std::vector createDummyEntriesSortedByBinding( + std::vector const&); + // TODO: Consider storing what we used to make the layout. However we need to essentially // Recreate some of the info (Sampler in slot X with the actual sampler) so letting Dawn confirm // there isn't a mismatch may be easiest. // Also storing the wgpu ObjectBase takes care of ownership challenges in theory - wgpu::BindGroupLayout mLayout; - std::vector entries; - wgpu::BindGroup mBindGroup; + wgpu::BindGroupLayout mLayout = nullptr; + static constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1; + std::array mEntryIndexByBinding {}; + std::vector mEntriesSortedByBinding; + wgpu::BindGroup mBindGroup = nullptr; }; -// TODO: Currently WGPUTexture is not used by WebGPU for useful task. -// Update the struct when used by WebGPU driver. -struct WGPUTexture : public HwTexture { +class WGPUTexture : public HwTexture { +public: WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, - uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage) noexcept; + uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, + wgpu::Device device) noexcept; - // constructors for creating texture views - WGPUTexture(WGPUTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept; + WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept; + const wgpu::Texture& getTexture() const { return texture; } + const wgpu::TextureView& getTexView() const { return texView; } + + // Public to allow checking for support of a texture format + static wgpu::TextureFormat fToWGPUTextureFormat(const filament::backend::TextureFormat& fUsage); + +private: + wgpu::TextureView makeTextureView(const uint8_t& baseLevel, const uint8_t& levelCount); + // CreateTextureR has info for a texture and sampler. Texture Views are needed for binding, + // along with a sampler Current plan: Inherit the sampler and Texture to always exist (It is a + // ref counted pointer) when making views. View is optional wgpu::Texture texture = nullptr; - // TODO: Adding this but not yet setting it up. Filament "Textures" are combined image samplers, - // rep both. - wgpu::Sampler sampler = nullptr; - //TODO: Not sure all the ways HwTexture is used. Overloading like this might be entirely wrong. wgpu::TextureView texView = nullptr; + wgpu::TextureUsage fToWGPUTextureUsage(const filament::backend::TextureUsage& fUsage); }; struct WGPURenderPrimitive : public HwRenderPrimitive { From e97f7ef628db3c50e04a9f636fa9928bde4275dc Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Tue, 6 May 2025 11:34:44 -0400 Subject: [PATCH 04/34] WebGPU: Render Target Implementation Co-authored-by: Andy Hovingh <6198728+AndyHovingh@users.noreply.github.com> Co-authored-by: Syed Idris Shah --- filament/backend/src/webgpu/WebGPUDriver.cpp | 17 +++++- filament/backend/src/webgpu/WebGPUHandles.cpp | 57 ++++++++++++++++++- filament/backend/src/webgpu/WebGPUHandles.h | 23 ++++---- .../src/webgpu/WebGPUPipelineCreation.cpp | 1 + 4 files changed, 82 insertions(+), 16 deletions(-) diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index e26b69dbe4..283d3703f6 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -776,8 +776,19 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority, } } -void WebGPUDriver::beginRenderPass(Handle rth, const RenderPassParams& params) { +void WebGPUDriver::beginRenderPass(Handle rth, RenderPassParams const& params) { assert_invariant(mCommandEncoder); + + auto* renderTarget = handleCast(rth); + // if (renderTarget == mDefaultRenderTarget) { + // FWGPU_LOGW << "Default render target" + // << utils::io::endl; + // } else { + // FWGPU_LOGW << "Non Default render target" + // << utils::io::endl; + // } + wgpu::RenderPassDescriptor renderPassDescriptor2; + renderTarget->setUpRenderPassAttachments(&renderPassDescriptor2, mTextureView, params); // TODO: Remove this code once WebGPU pipeline is implemented static float red = 1.0f; if (red - 0.01 > 0) { @@ -802,7 +813,7 @@ void WebGPUDriver::beginRenderPass(Handle rth, const RenderPassP .timestampWrites = nullptr, }; - mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor); + mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor2); mRenderPassEncoder.SetViewport(params.viewport.left, params.viewport.bottom, params.viewport.width, params.viewport.height, params.depthRange.near, params.depthRange.far); } @@ -930,7 +941,7 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) { pipelineState.polygonOffset, pipelineState.primitiveType, mSwapChain->getColorFormat(), mSwapChain->getDepthFormat()); // TODO: uncomment once we have a valid pipeline to set - // mRenderPassEncoder.SetPipeline(pipeline); + mRenderPassEncoder.SetPipeline(pipeline); } void WebGPUDriver::bindRenderPrimitive(Handle rph) { diff --git a/filament/backend/src/webgpu/WebGPUHandles.cpp b/filament/backend/src/webgpu/WebGPUHandles.cpp index b1cedb701f..c7e7a6ef4c 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.cpp +++ b/filament/backend/src/webgpu/WebGPUHandles.cpp @@ -144,7 +144,7 @@ WGPUVertexBufferInfo::WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attribut mAttributes[attrib.buffer].push_back({ .format = vertexFormat, .offset = attrib.offset, - .shaderLocation = static_cast(mAttributes[attrib.buffer].size()), + .shaderLocation = attribIndex, }); mVertexBufferLayout[attrib.buffer].stepMode = wgpu::VertexStepMode::Vertex; @@ -771,4 +771,59 @@ wgpu::TextureView WGPUTexture::makeTextureView(const uint8_t& baseLevel, return texture.CreateView(&desc); } +WGPURenderTarget::Attachment WGPURenderTarget::getDrawColorAttachment(size_t index) { + assert_invariant( index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT); + auto result = color[index]; + if (index == 0 && defaultRenderTarget) { + + } + + return result; +} + +wgpu::LoadOp WGPURenderTarget::getLoadOperation(RenderPassParams const& params, + TargetBufferFlags buffer) { + auto clearFlags = params.flags.clear; + auto discardStartFlags = params.flags.discardStart; + if (any(clearFlags & buffer)) { + return wgpu::LoadOp::Clear; + } else if (any(discardStartFlags & buffer)) { + return wgpu::LoadOp::Clear; + } + return wgpu::LoadOp::Load; +} + +wgpu::StoreOp WGPURenderTarget::getStoreOperation(RenderPassParams const& params, + TargetBufferFlags buffer) { + const auto discardEndFlags = params.flags.discardEnd; + if (any(discardEndFlags & buffer)) { + return wgpu::StoreOp::Discard; + } + return wgpu::StoreOp::Store; +} +void WGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor* descriptor, + wgpu::TextureView const& textureView, const RenderPassParams& params) { + // auto discardFlags = params.flags.discardEnd; + // (void) discardFlags; + // std::vector colorAttachments; + colorAttachments.clear(); + for (size_t i = 0; i < 1/*MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT*/; i++) { + // auto attachment = getDrawColorAttachment(i); + // if (attachment) { + wgpu::RenderPassColorAttachment colorAttachment; + colorAttachment.view = textureView; + colorAttachment.loadOp = getLoadOperation(params, getTargetBufferFlagsAt(i)); + colorAttachment.storeOp = getStoreOperation(params, getTargetBufferFlagsAt(i)); + colorAttachment.clearValue = { params.clearColor.r, params.clearColor.g, params.clearColor.b, params.clearColor.a }; + colorAttachments.emplace_back(colorAttachment); + // } + } + descriptor->colorAttachments = colorAttachments.data(); + descriptor->colorAttachmentCount = colorAttachments.size(); + descriptor->depthStencilAttachment = nullptr; + descriptor->timestampWrites = nullptr; + +} + + }// namespace filament::backend diff --git a/filament/backend/src/webgpu/WebGPUHandles.h b/filament/backend/src/webgpu/WebGPUHandles.h index 4b21ffb5f9..7aa4e5e151 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.h +++ b/filament/backend/src/webgpu/WebGPUHandles.h @@ -188,28 +188,26 @@ private: }; struct WGPURenderPrimitive : public HwRenderPrimitive { - WGPURenderPrimitive() {} - - void setBuffers(WGPUVertexBufferInfo const* const vbi, - WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer); WGPUVertexBuffer* vertexBuffer = nullptr; WGPUIndexBuffer* indexBuffer = nullptr; }; -// TODO: Currently WGPURenderTarget is not used by WebGPU for useful task. -// Update the struct when used by WebGPU driver. -struct WGPURenderTarget : public HwRenderTarget { +class WGPURenderTarget : public HwRenderTarget { +public: class Attachment { public: - friend struct WGPURenderTarget; + friend class WGPURenderTarget; Attachment() = default; Attachment(WGPUTexture* gpuTexture, uint8_t level = 0, uint16_t layer = 0) : level(level), layer(layer), - texture(gpuTexture->texture), + texture(gpuTexture->getTexture()), mWGPUTexture(gpuTexture) {} + operator bool() const { + return mWGPUTexture != nullptr; + } uint8_t level = 0; uint16_t layer = 0; @@ -226,7 +224,7 @@ struct WGPURenderTarget : public HwRenderTarget { defaultRenderTarget(true) {} void setUpRenderPassAttachments(wgpu::RenderPassDescriptor* descriptor, - const RenderPassParams& params); + wgpu::TextureView const& textureView, const RenderPassParams& params); math::uint2 getAttachmentSize() noexcept; @@ -237,14 +235,15 @@ struct WGPURenderTarget : public HwRenderTarget { Attachment getReadColorAttachment(size_t index); private: - static wgpu::LoadOp getLoadAction(const RenderPassParams& params, TargetBufferFlags buffer); - static wgpu::LoadOp getStoreAction(const RenderPassParams& params, TargetBufferFlags buffer); + static wgpu::LoadOp getLoadOperation(const RenderPassParams& params, TargetBufferFlags buffer); + static wgpu::StoreOp getStoreOperation(const RenderPassParams& params, TargetBufferFlags buffer); bool defaultRenderTarget = false; uint8_t samples = 1; Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; math::uint2 attachmentSize = {}; + std::vector colorAttachments {}; }; }// namespace filament::backend diff --git a/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp b/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp index cb908f97ef..e435ca64bb 100644 --- a/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp +++ b/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp @@ -235,6 +235,7 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device, fragmentState.constantCount = program.constants.size(), fragmentState.constants = program.constants.data(), fragmentState.targetCount = 1; // TODO need to get this from the render target + fragmentState.targets = colorTargets.data(); assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT); for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) { auto& colorTarget = colorTargets[targetIndex]; From 56231db3264df868346d2d40f04c292d4605df98 Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Tue, 6 May 2025 11:35:46 -0400 Subject: [PATCH 05/34] WebGPU: Set spec constants via string mapping Co-authored-by: Andy Hovingh <6198728+AndyHovingh@users.noreply.github.com> Co-authored-by: Juan Caldas --- filament/backend/src/webgpu/WGPUProgram.cpp | 66 +++++++++++++++++---- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/filament/backend/src/webgpu/WGPUProgram.cpp b/filament/backend/src/webgpu/WGPUProgram.cpp index 42b9717a02..c81e4e8668 100644 --- a/filament/backend/src/webgpu/WGPUProgram.cpp +++ b/filament/backend/src/webgpu/WGPUProgram.cpp @@ -27,6 +27,7 @@ #include +#include #include #include #include @@ -125,21 +126,64 @@ namespace { return module; } +// This is a 1 to 1 mapping of the ReservedSpecializationConstants enum in EngineEnums.h +// The _hack is a workaround until https://issues.chromium.org/issues/42250586 is resolved +// This workaround is the same one being used on the generateSpecializationConstant() function +wgpu::StringView getSpecConstantStringId(uint32_t id) { + switch (id) { + case 0: + return "0";// BACKEND_FEATURE_LEVEL_hack + case 1: + return "1";// CONFIG_MAX_INSTANCES_hack + case 2: + return "2";// ONFIG_STATIC_TEXTURE_TARGET_WORKAROUND_hack + case 3: + return "3";// CONFIG_SRGB_SWAPCHAIN_EMULATION_hack + case 4: + return "4";// CONFIG_FROXEL_BUFFER_HEIGHT_hack + case 5: + return "5";// CONFIG_POWER_VR_SHADER_WORKAROUNDS_hack + case 6: + return "6";// CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP_hack + case 7: + return "7";// CONFIG_DEBUG_FROXEL_VISUALIZATION_hack + case 8: + return "8";// CONFIG_STEREO_EYE_COUNT_hack + case 9: + return "9";// CONFIG_SH_BANDS_COUNT_hack + case 10: + return "10";// CONFIG_SHADOW_SAMPLING_METHOD_hack + default: + PANIC_POSTCONDITION("Unknown/unhandled spec constant key/id: %d", id); + } +} + std::vector convertConstants( utils::FixedCapacityVector const& constantsInfo) { - std::vector constants(constantsInfo.size()); - for (size_t i = 0; i < constantsInfo.size(); i++) { - filament::backend::Program::SpecializationConstant const& specConstant = constantsInfo[i]; - wgpu::ConstantEntry& constantEntry = constants[i]; - constantEntry.key = wgpu::StringView(std::to_string(specConstant.id)); - if (auto* v = std::get_if(&specConstant.value)) { - constantEntry.value = static_cast(*v); - } else if (auto* f = std::get_if(&specConstant.value)) { - constantEntry.value = static_cast(*f); - } else if (auto* b = std::get_if(&specConstant.value)) { - constantEntry.value = *b ? 0.0 : 1.0; + std::vector constants; + constants.reserve(constantsInfo.size()); + for (filament::backend::Program::SpecializationConstant const& constant: constantsInfo) { + // CONFIG_MAX_INSTANCES (1) and CONFIG_FROXEL_BUFFER_HEIGHT (4) will not be present + // as constant overrides in the generated WGSL, because WGSL doesn't support specialization + // constants as an array length + // More information at https://github.com/gpuweb/gpuweb/issues/572#issuecomment-649760005 + // CONFIG_SRGB_SWAPCHAIN_EMULATION (3) is being skipped all together since it's only + // included for the case of mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0, which should + // not be possible for WebGPU + if (constant.id == 1 || constant.id == 3 || constant.id == 4) { + continue; } + double value = 0.0; + if (auto* v = std::get_if(&constant.value)) { + value = static_cast(*v); + } else if (auto* f = std::get_if(&constant.value)) { + value = static_cast(*f); + } else if (auto* b = std::get_if(&constant.value)) { + value = *b ? 0.0 : 1.0; + } + constants.push_back( + wgpu::ConstantEntry{ .key = getSpecConstantStringId(constant.id), .value = value }); } return constants; } From 705b3c39f4cff342ea4c5c93787cfbcae5bbef50 Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Tue, 6 May 2025 11:38:55 -0400 Subject: [PATCH 06/34] WebGPU: Add Draw call, with temporary hack to achieve spinning triangle Co-authored-by: Juan Caldas --- filament/backend/src/webgpu/WebGPUDriver.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index 283d3703f6..5192aff263 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -961,6 +961,13 @@ void WebGPUDriver::bindRenderPrimitive(Handle rph) { } void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { + // Calling DrawIndexed with "firstInstance = 0" results in a NON spinning triangle + // mRenderPassEncoder.DrawIndexed(indexCount, instanceCount, indexOffset, 0, 0); + // Calling DrawIndexed with "firstInstance = 1" results in a spinning triangle + mRenderPassEncoder.DrawIndexed(indexCount, instanceCount, indexOffset, 0, 1); + // Calling Draw with "firstInstance = 0" results in a NON spinning triangle + // Calling Draw with "firstInstance = 1" results in a spinning triangle + // mRenderPassEncoder.Draw(indexCount, instanceCount, 0, 1); } void WebGPUDriver::draw(PipelineState pipelineState, Handle rph, From 35ab5f768d1b05aad0487fc66e02d14d6a299ee8 Mon Sep 17 00:00:00 2001 From: Andy Hovingh <6198728+AndyHovingh@users.noreply.github.com> Date: Sat, 3 May 2025 11:11:59 -0500 Subject: [PATCH 07/34] webgpu: depth texture and attachment --- filament/backend/src/webgpu/WebGPUDriver.cpp | 14 +++- filament/backend/src/webgpu/WebGPUHandles.cpp | 13 ++-- filament/backend/src/webgpu/WebGPUHandles.h | 7 +- .../backend/src/webgpu/WebGPUSwapChain.cpp | 64 +++++++++++++++---- filament/backend/src/webgpu/WebGPUSwapChain.h | 6 ++ 5 files changed, 81 insertions(+), 23 deletions(-) diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index 5192aff263..4a3a1eb4e3 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -788,7 +788,19 @@ void WebGPUDriver::beginRenderPass(Handle rth, RenderPassParams // << utils::io::endl; // } wgpu::RenderPassDescriptor renderPassDescriptor2; - renderTarget->setUpRenderPassAttachments(&renderPassDescriptor2, mTextureView, params); + wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{ + .view = mSwapChain->getDepthTextureView(), + .depthLoadOp = WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH), + .depthStoreOp = WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH), + .depthClearValue = static_cast(params.clearDepth), + .depthReadOnly = (params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0, + .stencilLoadOp = WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL), + .stencilStoreOp = WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL), + .stencilClearValue = params.clearStencil, + .stencilReadOnly = (params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0 + }; + renderTarget->setUpRenderPassAttachments(renderPassDescriptor2, mTextureView, params); + renderPassDescriptor2.depthStencilAttachment = &depthStencilAttachment; // TODO: Remove this code once WebGPU pipeline is implemented static float red = 1.0f; if (red - 0.01 > 0) { diff --git a/filament/backend/src/webgpu/WebGPUHandles.cpp b/filament/backend/src/webgpu/WebGPUHandles.cpp index c7e7a6ef4c..20fdb4c039 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.cpp +++ b/filament/backend/src/webgpu/WebGPUHandles.cpp @@ -801,8 +801,8 @@ wgpu::StoreOp WGPURenderTarget::getStoreOperation(RenderPassParams const& params } return wgpu::StoreOp::Store; } -void WGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor* descriptor, - wgpu::TextureView const& textureView, const RenderPassParams& params) { +void WGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor& descriptor, + wgpu::TextureView const& textureView, RenderPassParams const& params) { // auto discardFlags = params.flags.discardEnd; // (void) discardFlags; // std::vector colorAttachments; @@ -818,11 +818,10 @@ void WGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor* de colorAttachments.emplace_back(colorAttachment); // } } - descriptor->colorAttachments = colorAttachments.data(); - descriptor->colorAttachmentCount = colorAttachments.size(); - descriptor->depthStencilAttachment = nullptr; - descriptor->timestampWrites = nullptr; - + descriptor.colorAttachments = colorAttachments.data(); + descriptor.colorAttachmentCount = colorAttachments.size(); + descriptor.depthStencilAttachment = nullptr; + descriptor.timestampWrites = nullptr; } diff --git a/filament/backend/src/webgpu/WebGPUHandles.h b/filament/backend/src/webgpu/WebGPUHandles.h index 7aa4e5e151..003bc52989 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.h +++ b/filament/backend/src/webgpu/WebGPUHandles.h @@ -223,8 +223,8 @@ public: : HwRenderTarget(0, 0), defaultRenderTarget(true) {} - void setUpRenderPassAttachments(wgpu::RenderPassDescriptor* descriptor, - wgpu::TextureView const& textureView, const RenderPassParams& params); + void setUpRenderPassAttachments(wgpu::RenderPassDescriptor& descriptor, + wgpu::TextureView const& textureView, RenderPassParams const& params); math::uint2 getAttachmentSize() noexcept; @@ -234,10 +234,9 @@ public: Attachment getDrawColorAttachment(size_t index); Attachment getReadColorAttachment(size_t index); -private: static wgpu::LoadOp getLoadOperation(const RenderPassParams& params, TargetBufferFlags buffer); static wgpu::StoreOp getStoreOperation(const RenderPassParams& params, TargetBufferFlags buffer); - +private: bool defaultRenderTarget = false; uint8_t samples = 1; diff --git a/filament/backend/src/webgpu/WebGPUSwapChain.cpp b/filament/backend/src/webgpu/WebGPUSwapChain.cpp index f9809ebe5e..d9a0444afe 100644 --- a/filament/backend/src/webgpu/WebGPUSwapChain.cpp +++ b/filament/backend/src/webgpu/WebGPUSwapChain.cpp @@ -105,7 +105,7 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config, } #endif -constexpr wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount, +[[nodiscard]] constexpr wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount, wgpu::TextureFormat const* availableFormats, bool useSRGBColorSpace) { const std::array expectedColorFormats = useSRGBColorSpace ? @@ -123,7 +123,7 @@ constexpr wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount, return *firstFoundColorFormat; } -constexpr wgpu::TextureFormat selectDepthFormat(bool depth32FloatStencil8Enabled, +[[nodiscard]] constexpr wgpu::TextureFormat selectDepthFormat(bool depth32FloatStencil8Enabled, bool needStencil) { if (needStencil) { if (depth32FloatStencil8Enabled) { @@ -137,7 +137,7 @@ constexpr wgpu::TextureFormat selectDepthFormat(bool depth32FloatStencil8Enabled } } -constexpr wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount, +[[nodiscard]] constexpr wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount, wgpu::PresentMode const* availablePresentModes) { // Verify that our chosen present mode is supported. In practice all devices support the FIFO // mode, but we check for it anyway for completeness. (and to avoid validation warnings) @@ -151,7 +151,7 @@ constexpr wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount, return desiredPresentMode; } -constexpr wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount, +[[nodiscard]] constexpr wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount, wgpu::CompositeAlphaMode const* availableAlphaModes) { bool autoAvailable = false; bool inheritAvailable = false; @@ -222,13 +222,58 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device, config.alphaMode = selectAlphaMode(capabilities.alphaModeCount, capabilities.alphaModes); } +[[nodiscard]] wgpu::Texture createDepthTexture(wgpu::Device const& device, + wgpu::Extent2D const& extent, wgpu::TextureFormat depthFormat) { + wgpu::TextureDescriptor descriptor{ .label = "depth_texture", + .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment, + .dimension = wgpu::TextureDimension::e2D, + .size = { .width = extent.width, .height = extent.height, .depthOrArrayLayers = 1 }, + .format = depthFormat, + .mipLevelCount = 1, + .sampleCount = 1, + .viewFormatCount = 1, + .viewFormats = &depthFormat + }; + wgpu::Texture depthTexture = device.CreateTexture(&descriptor); + FILAMENT_CHECK_POSTCONDITION(depthTexture) << "Failed to create depth texture with width " + << extent.width << " and height " << extent.height; + return depthTexture; +} + +[[nodiscard]] wgpu::TextureView createDepthTextureView(wgpu::Texture const& depthTexture, + wgpu::TextureFormat depthFormat, bool needStencil) { + wgpu::TextureViewDescriptor descriptor{ + .label = "depth_texture_view", + .format = depthFormat, + .dimension = wgpu::TextureViewDimension::e2D, + .baseMipLevel = 0, + .mipLevelCount = 1, + .baseArrayLayer = 0, + .arrayLayerCount = 1, + .aspect = wgpu::TextureAspect::DepthOnly, + .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment + }; + if (needStencil) { + descriptor.aspect = wgpu::TextureAspect::All; + } + wgpu::TextureView depthTextureView = depthTexture.CreateView(&descriptor); + FILAMENT_CHECK_POSTCONDITION(depthTextureView) << "Failed to create depth texture view"; + return depthTextureView; +} + }// namespace namespace filament::backend { WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize, wgpu::Adapter const& adapter, wgpu::Device const& device, uint64_t flags) - : mSurface(surface) { + : mDevice(device), + mSurface(surface), + mNeedStencil((flags & SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0), + mDepthFormat(selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8), + mNeedStencil)), + mDepthTexture(createDepthTexture(device, surfaceSize, mDepthFormat)), + mDepthTextureView(createDepthTextureView(mDepthTexture, mDepthFormat, mNeedStencil)) { wgpu::SurfaceCapabilities capabilities = {}; if (!mSurface.GetCapabilities(adapter, &capabilities)) { FWGPU_LOGW << "Failed to get WebGPU surface capabilities" << utils::io::endl; @@ -238,19 +283,14 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& #endif } const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0; - const bool needStencil = (flags & SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0; initConfig(mConfig, device, capabilities, surfaceSize, useSRGBColorSpace); - mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8), - needStencil); #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) printSurfaceConfiguration(mConfig, mDepthFormat); #endif mSurface.Configure(&mConfig); } -WebGPUSwapChain::~WebGPUSwapChain() { - mSurface.Unconfigure(); -} +WebGPUSwapChain::~WebGPUSwapChain() { mSurface.Unconfigure(); } void WebGPUSwapChain::setExtent(wgpu::Extent2D const& currentSurfaceSize) { FILAMENT_CHECK_POSTCONDITION(currentSurfaceSize.width > 0 || currentSurfaceSize.height > 0) @@ -267,6 +307,8 @@ void WebGPUSwapChain::setExtent(wgpu::Extent2D const& currentSurfaceSize) { // TODO we may need to ensure no surface texture is in flight when we do this. some // synchronization may be necessary mSurface.Configure(&mConfig); + mDepthTexture = createDepthTexture(mDevice, currentSurfaceSize, mDepthFormat); + mDepthTextureView = createDepthTextureView(mDepthTexture, mDepthFormat, mNeedStencil); } } diff --git a/filament/backend/src/webgpu/WebGPUSwapChain.h b/filament/backend/src/webgpu/WebGPUSwapChain.h index 47ed7313ed..9b3b796f3b 100644 --- a/filament/backend/src/webgpu/WebGPUSwapChain.h +++ b/filament/backend/src/webgpu/WebGPUSwapChain.h @@ -38,14 +38,20 @@ public: [[nodiscard]] wgpu::TextureView getCurrentSurfaceTextureView(wgpu::Extent2D const&); + [[nodiscard]] wgpu::TextureView getDepthTextureView() const { return mDepthTextureView; } + void present(); private: void setExtent(wgpu::Extent2D const&); + wgpu::Device mDevice = nullptr; wgpu::Surface mSurface = {}; wgpu::SurfaceConfiguration mConfig = {}; + bool mNeedStencil = false; wgpu::TextureFormat mDepthFormat = wgpu::TextureFormat::Undefined; + wgpu::Texture mDepthTexture = nullptr; + wgpu::TextureView mDepthTextureView = nullptr; }; } // namespace filament::backend From 2d98ac878e1796e74cc9a2f3386e8035f569f083 Mon Sep 17 00:00:00 2001 From: Andy Hovingh <6198728+AndyHovingh@users.noreply.github.com> Date: Sun, 4 May 2025 11:48:07 -0500 Subject: [PATCH 08/34] webgpu: sets bind groups in the render pass --- filament/backend/src/webgpu/WebGPUDriver.cpp | 17 ++++++--- filament/backend/src/webgpu/WebGPUHandles.cpp | 36 ++++++++++++++++++- filament/backend/src/webgpu/WebGPUHandles.h | 10 ++++-- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index 4a3a1eb4e3..5414607a44 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -1037,11 +1037,18 @@ void WebGPUDriver::updateDescriptorSetTexture(Handle dsh, } } -void WebGPUDriver::bindDescriptorSet(Handle dsh, backend::descriptor_set_t set, - backend::DescriptorSetOffsetArray&& offsets) { - auto bindGroup = handleCast(dsh); - // TODO: presume we need this, use it. Probably Encoder::SetBindGroup - auto wbg = bindGroup->lockAndReturn(mDevice); +void WebGPUDriver::bindDescriptorSet(Handle dsh, + backend::descriptor_set_t setIndex, backend::DescriptorSetOffsetArray&& offsets) { + const auto bindGroup = handleCast(dsh); + const auto wbg = bindGroup->lockAndReturn(mDevice); + assert_invariant(mRenderPassEncoder); + // TODO is this how we should be getting the dynamic offsets? + // should we add offsets for unused entries or is the input already have them? + // this implementation assumes unused entries are not provided, and adds dummy values. + // The count also includes unused entities, as not doing so produces errors + const size_t dynamicOffsetCount = bindGroup->countEntitiesWithDynamicOffsets(); + uint32_t const* const dynamicOffsetsWithUnused = bindGroup->setDynamicOffsets(offsets.data()); + mRenderPassEncoder.SetBindGroup(setIndex, wbg, dynamicOffsetCount, dynamicOffsetsWithUnused); } void WebGPUDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) { diff --git a/filament/backend/src/webgpu/WebGPUHandles.cpp b/filament/backend/src/webgpu/WebGPUHandles.cpp index 20fdb4c039..e65e48109b 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.cpp +++ b/filament/backend/src/webgpu/WebGPUHandles.cpp @@ -18,6 +18,8 @@ #include +#include + #include #include @@ -239,6 +241,7 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& case DescriptorType::UNIFORM_BUFFER: { wEntry.buffer.hasDynamicOffset = any(fEntry.flags & DescriptorFlags::DYNAMIC_OFFSET); + entryInfo.hasDynamicOffset = wEntry.buffer.hasDynamicOffset; wEntry.buffer.type = wgpu::BufferBindingType::Uniform; // TODO: Ideally we fill minBindingSize break; @@ -353,7 +356,7 @@ std::vector WebGPUDescriptorSet::createDummyEntriesSortedB return entries; } -WebGPUDescriptorSet::WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, +WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout, std::vector const& bindGroupEntries) : mLayout(layout), mEntriesSortedByBinding(createDummyEntriesSortedByBinding(bindGroupEntries)) { @@ -367,6 +370,13 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, assert_invariant(entry.binding < mEntryIndexByBinding.size()); mEntryIndexByBinding[entry.binding] = static_cast(index); } + for (auto const& entry : bindGroupEntries) { + if (entry.hasDynamicOffset) { + assert_invariant(entry.binding < mEntriesByBindingWithDynamicOffsets.size()); + mEntriesByBindingWithDynamicOffsets[entry.binding] = true; + } + } + mDynamicOffsets.reserve(mEntriesSortedByBinding.size()); } WebGPUDescriptorSet::~WebGPUDescriptorSet() { @@ -411,6 +421,30 @@ void WebGPUDescriptorSet::addEntry(uint index, wgpu::BindGroupEntry&& entry) { << "Invalid binding " << index; entry.binding = index; mEntriesSortedByBinding[entryIndex] = std::move(entry); + mEntriesByBindingAdded[index] = true; +} + +uint32_t const* WebGPUDescriptorSet::setDynamicOffsets(uint32_t const* offsets) { + // mDynamicOffsets already reserves enough memory for the number of entries in the set + mDynamicOffsets.clear(); + // this implementation copies the offsets to mDynamicOffsets, but also adds values for + // unused entries TODO: is this necessary? + size_t inputIndex = 0; + size_t outputIndex = 0; + for (auto const& entry : mEntriesSortedByBinding) { + if (mEntriesByBindingWithDynamicOffsets[entry.binding]) { + if (mEntriesByBindingAdded[entry.binding]) { + mDynamicOffsets[outputIndex++] = offsets[inputIndex++]; + } else { + mDynamicOffsets[outputIndex++] = 0; // dummy offset, as it was never added + } + } + } + return mDynamicOffsets.data(); +} + +size_t WebGPUDescriptorSet::countEntitiesWithDynamicOffsets() const { + return mEntriesByBindingWithDynamicOffsets.count(); } // From createTextureR diff --git a/filament/backend/src/webgpu/WebGPUHandles.h b/filament/backend/src/webgpu/WebGPUHandles.h index 003bc52989..22c2fba486 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.h +++ b/filament/backend/src/webgpu/WebGPUHandles.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -112,6 +113,7 @@ public: struct BindGroupEntryInfo final { uint8_t binding = 0; BindGroupEntryType type = BindGroupEntryType::UNIFORM_BUFFER; + bool hasDynamicOffset = false; }; WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const& device); @@ -134,14 +136,15 @@ public: static void initializeDummyResourcesIfNotAlready(wgpu::Device const&, wgpu::TextureFormat aColorFormat); - WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, + WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout, std::vector const& bindGroupEntries); ~WebGPUDescriptorSet(); wgpu::BindGroup lockAndReturn(wgpu::Device const&); void addEntry(uint index, wgpu::BindGroupEntry&& entry); + [[nodiscard]] uint32_t const* setDynamicOffsets(uint32_t const* offsets); [[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; } - + [[nodiscard]] size_t countEntitiesWithDynamicOffsets() const; private: static wgpu::Buffer sDummyUniformBuffer; @@ -160,6 +163,9 @@ private: static constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1; std::array mEntryIndexByBinding {}; std::vector mEntriesSortedByBinding; + std::bitset mEntriesByBindingWithDynamicOffsets {}; + std::bitset mEntriesByBindingAdded {}; + std::vector mDynamicOffsets; wgpu::BindGroup mBindGroup = nullptr; }; From c1a8d7338408021c2c8cd3ebddb732e85cc9a9ce Mon Sep 17 00:00:00 2001 From: Andy Hovingh <6198728+AndyHovingh@users.noreply.github.com> Date: Sun, 4 May 2025 12:04:15 -0500 Subject: [PATCH 09/34] webgpu: legacy draw call should call draw2 --- filament/backend/src/webgpu/WebGPUDriver.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index 5414607a44..8f8e5bc103 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -982,8 +982,9 @@ void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins // mRenderPassEncoder.Draw(indexCount, instanceCount, 0, 1); } -void WebGPUDriver::draw(PipelineState pipelineState, Handle rph, - uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { +void WebGPUDriver::draw(PipelineState, Handle, uint32_t indexOffset, + uint32_t indexCount, uint32_t instanceCount) { + draw2(indexOffset, indexCount, instanceCount); } void WebGPUDriver::dispatchCompute(Handle program, math::uint3 workGroupCount) { From 67b9c72442899eb5f2ad898cc5cb05a7156ad81f Mon Sep 17 00:00:00 2001 From: Konrad Piascik Date: Mon, 5 May 2025 18:46:52 -0400 Subject: [PATCH 10/34] Fix windows compile errors --- filament/backend/src/webgpu/WebGPUHandles.cpp | 4 ++-- filament/backend/src/webgpu/WebGPUHandles.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/filament/backend/src/webgpu/WebGPUHandles.cpp b/filament/backend/src/webgpu/WebGPUHandles.cpp index e65e48109b..e380af099c 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.cpp +++ b/filament/backend/src/webgpu/WebGPUHandles.cpp @@ -205,7 +205,7 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& // debugging. For now, hack an incrementing value. static int layoutNum = 0; - uint samplerCount = + unsigned int samplerCount = std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) { return fEntry.type == DescriptorType::SAMPLER || fEntry.type == DescriptorType::SAMPLER_EXTERNAL; @@ -403,7 +403,7 @@ wgpu::BindGroup WebGPUDescriptorSet::lockAndReturn(const wgpu::Device& device) { return mBindGroup; } -void WebGPUDescriptorSet::addEntry(uint index, wgpu::BindGroupEntry&& entry) { +void WebGPUDescriptorSet::addEntry(unsigned int index, wgpu::BindGroupEntry&& entry) { if (mBindGroup) { // We will keep getting hits from future updates, but shouldn't do anything // Filament guarantees this won't change after things have locked. diff --git a/filament/backend/src/webgpu/WebGPUHandles.h b/filament/backend/src/webgpu/WebGPUHandles.h index 22c2fba486..dddf4ab39a 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.h +++ b/filament/backend/src/webgpu/WebGPUHandles.h @@ -141,7 +141,7 @@ public: ~WebGPUDescriptorSet(); wgpu::BindGroup lockAndReturn(wgpu::Device const&); - void addEntry(uint index, wgpu::BindGroupEntry&& entry); + void addEntry(unsigned int index, wgpu::BindGroupEntry&& entry); [[nodiscard]] uint32_t const* setDynamicOffsets(uint32_t const* offsets); [[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; } [[nodiscard]] size_t countEntitiesWithDynamicOffsets() const; From af09c517b683f4ea1283e9ae073ba71294963544 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 6 May 2025 10:41:21 -0700 Subject: [PATCH 11/34] Revert "Use the Perfetto SDK instead of ATRACE" (#8701) This reverts commit ca3ff7e08ed15568c49b7239d87a6832abe7804a. --- filament/backend/src/CommandStream.cpp | 4 +- filament/backend/src/vulkan/VulkanConstants.h | 6 +- libs/gltfio/src/AssetLoader.cpp | 2 - libs/gltfio/src/ResourceLoader.cpp | 3 - libs/gltfio/src/Utility.cpp | 2 - libs/utils/include/utils/Systrace.h | 20 +- libs/utils/include/utils/android/Systrace.h | 259 ++++++++++++++---- libs/utils/include/utils/darwin/Systrace.h | 66 +++-- libs/utils/src/JobSystem.cpp | 4 +- libs/utils/src/android/Systrace.cpp | 210 +++++++++++++- libs/utils/src/darwin/Systrace.cpp | 25 +- 11 files changed, 458 insertions(+), 143 deletions(-) diff --git a/filament/backend/src/CommandStream.cpp b/filament/backend/src/CommandStream.cpp index 429cfa601b..bb2207be53 100644 --- a/filament/backend/src/CommandStream.cpp +++ b/filament/backend/src/CommandStream.cpp @@ -84,7 +84,7 @@ void CommandStream::execute(void* buffer) { Profiler profiler; - if constexpr (SYSTRACE_TAG) { + if (SYSTRACE_TAG) { if (UTILS_UNLIKELY(mUsePerformanceCounter)) { // we want to remove all this when tracing is completely disabled profiler.resetEvents(Profiler::EV_CPU_CYCLES | Profiler::EV_BPU_MISSES); @@ -100,7 +100,7 @@ void CommandStream::execute(void* buffer) { } }); - if constexpr (SYSTRACE_TAG) { + if (SYSTRACE_TAG) { if (UTILS_UNLIKELY(mUsePerformanceCounter)) { // we want to remove all this when tracing is completely disabled profiler.stop(); diff --git a/filament/backend/src/vulkan/VulkanConstants.h b/filament/backend/src/vulkan/VulkanConstants.h index c6524296ce..1a454d7069 100644 --- a/filament/backend/src/vulkan/VulkanConstants.h +++ b/filament/backend/src/vulkan/VulkanConstants.h @@ -152,12 +152,12 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION)); #elif FVK_ENABLED(FVK_DEBUG_SYSTRACE) #include - + #define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT() #define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker) #define FVK_SYSTRACE_END() SYSTRACE_NAME_END() - #define FVK_SYSTRACE_SCOPE() SYSTRACE_CALL() - #define FVK_PROFILE_MARKER(marker) SYSTRACE_CALL() + #define FVK_SYSTRACE_SCOPE() SYSTRACE_NAME(__func__) + #define FVK_PROFILE_MARKER(marker) FVK_SYSTRACE_SCOPE() #else #define FVK_SYSTRACE_CONTEXT() diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index e71d8fc897..ac9b6b502f 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -50,8 +50,6 @@ #include #include #include - -#define SYSTRACE_TAG SYSTRACE_TAG_GLTFIO #include #include diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index fb6b219294..b88a47b701 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -37,10 +37,7 @@ #include #include #include - -#define SYSTRACE_TAG SYSTRACE_TAG_GLTFIO #include - #include #include diff --git a/libs/gltfio/src/Utility.cpp b/libs/gltfio/src/Utility.cpp index 9273943c55..f875f71a4e 100644 --- a/libs/gltfio/src/Utility.cpp +++ b/libs/gltfio/src/Utility.cpp @@ -21,8 +21,6 @@ #include "GltfEnums.h" #include - -#define SYSTRACE_TAG SYSTRACE_TAG_GLTFIO #include #define CGLTF_IMPLEMENTATION diff --git a/libs/utils/include/utils/Systrace.h b/libs/utils/include/utils/Systrace.h index 02ff62217b..9f5a7f2a59 100644 --- a/libs/utils/include/utils/Systrace.h +++ b/libs/utils/include/utils/Systrace.h @@ -17,23 +17,24 @@ #ifndef TNT_UTILS_SYSTRACE_H #define TNT_UTILS_SYSTRACE_H -#define SYSTRACE_TAG_DISABLED (0) -#define SYSTRACE_TAG_FILAMENT (2) // don't change used in makefiles -#define SYSTRACE_TAG_JOBSYSTEM (3) -#define SYSTRACE_TAG_GLTFIO (4) + +#define SYSTRACE_TAG_NEVER (0) +#define SYSTRACE_TAG_ALWAYS (1<<0) +#define SYSTRACE_TAG_FILAMENT (1<<1) // don't change, used in makefiles +#define SYSTRACE_TAG_JOBSYSTEM (1<<2) /* - * The SYSTRACE_ macros use SYSTRACE_TAG as a category, which must be defined - * before this file is included. + * The SYSTRACE_ macros use SYSTRACE_TAG as a the TAG, which should be defined + * before this file is included. If not, the SYSTRACE_TAG_ALWAYS tag will be used. */ #ifndef SYSTRACE_TAG -# error SYSTRACE_TAG must be set to SYSTRACE_TAG_{DISABLED|FILAMENT|JOBSYSTEM} +#define SYSTRACE_TAG (SYSTRACE_TAG_ALWAYS) #endif // Systrace on Apple platforms is fragile and adds overhead, should only be enabled in dev builds. #ifndef FILAMENT_APPLE_SYSTRACE -# define FILAMENT_APPLE_SYSTRACE 0 +#define FILAMENT_APPLE_SYSTRACE 0 #endif #if defined(__ANDROID__) @@ -43,6 +44,7 @@ #else #define SYSTRACE_ENABLE() +#define SYSTRACE_DISABLE() #define SYSTRACE_CONTEXT() #define SYSTRACE_NAME(name) #define SYSTRACE_FRAME_ID(frame) @@ -54,6 +56,6 @@ #define SYSTRACE_VALUE32(name, val) #define SYSTRACE_VALUE64(name, val) -#endif +#endif // ANDROID #endif // TNT_UTILS_SYSTRACE_H diff --git a/libs/utils/include/utils/android/Systrace.h b/libs/utils/include/utils/android/Systrace.h index a2b74d76b1..760fe5be64 100644 --- a/libs/utils/include/utils/android/Systrace.h +++ b/libs/utils/include/utils/android/Systrace.h @@ -17,75 +17,226 @@ #ifndef TNT_UTILS_ANDROID_SYSTRACE_H #define TNT_UTILS_ANDROID_SYSTRACE_H -#include +#include #include +#include +#include -PERFETTO_DEFINE_CATEGORIES_IN_NAMESPACE(systrace, - perfetto::Category("filament"), - perfetto::Category("jobsystem"), - perfetto::Category("gltfio")); +#include -PERFETTO_USE_CATEGORIES_FROM_NAMESPACE(systrace); +// enable tracing +#define SYSTRACE_ENABLE() ::utils::details::Systrace::enable(SYSTRACE_TAG) -#if SYSTRACE_TAG == SYSTRACE_TAG_FILAMENT -# define UTILS_PERFETTO_CATEGORY "filament" -#elif SYSTRACE_TAG == SYSTRACE_TAG_JOBSYSTEM -# define UTILS_PERFETTO_CATEGORY "jobsystem" -#elif SYSTRACE_TAG == SYSTRACE_TAG_GLTFIO -# define UTILS_PERFETTO_CATEGORY "gltfio" -#endif +// disable tracing +#define SYSTRACE_DISABLE() ::utils::details::Systrace::disable(SYSTRACE_TAG) -#if SYSTRACE_TAG == SYSTRACE_TAG_DISABLED -#define SYSTRACE_ENABLE() -#define SYSTRACE_CONTEXT() -#define SYSTRACE_NAME(name) -#define SYSTRACE_FRAME_ID(frame) -#define SYSTRACE_NAME_BEGIN(name) -#define SYSTRACE_NAME_END() -#define SYSTRACE_CALL() -#define SYSTRACE_ASYNC_BEGIN(name, cookie) -#define SYSTRACE_ASYNC_END(name, cookie) -#define SYSTRACE_VALUE32(name, val) -#define SYSTRACE_VALUE64(name, val) +/** + * Creates a Systrace context in the current scope. needed for calling all other systrace + * commands below. + */ +#define SYSTRACE_CONTEXT() ::utils::details::Systrace ___trctx(SYSTRACE_TAG) -#else -#define SYSTRACE_ENABLE() -#define SYSTRACE_CONTEXT() - -#define SYSTRACE_CALL() \ - auto constexpr FILAMENT_SYSTRACE_FUNCTION = perfetto::StaticString(__FUNCTION__); \ - TRACE_EVENT(UTILS_PERFETTO_CATEGORY, FILAMENT_SYSTRACE_FUNCTION) - -#define SYSTRACE_NAME(name) TRACE_EVENT(UTILS_PERFETTO_CATEGORY, nullptr, \ - [&](perfetto::EventContext ctx) { \ - ctx.event()->set_name(name); \ - }) - -#define SYSTRACE_NAME_BEGIN(name) TRACE_EVENT_BEGIN(UTILS_PERFETTO_CATEGORY, nullptr, \ - [&](perfetto::EventContext ctx) { \ - ctx.event()->set_name(name); \ - }) - -#define SYSTRACE_NAME_END() TRACE_EVENT_END(UTILS_PERFETTO_CATEGORY) - -#define SYSTRACE_ASYNC_BEGIN(name, cookie) \ - TRACE_EVENT_BEGIN(UTILS_PERFETTO_CATEGORY, name, perfetto::Track(cookie)) - -#define SYSTRACE_ASYNC_END(name, cookie) \ - TRACE_EVENT_END(UTILS_PERFETTO_CATEGORY, perfetto::Track(cookie)) +// SYSTRACE_NAME traces the beginning and end of the current scope. To trace +// the correct start and end times this macro should be declared first in the +// scope body. +// It also automatically creates a Systrace context +#define SYSTRACE_NAME(name) ::utils::details::ScopedTrace ___tracer(SYSTRACE_TAG, name) +// Denotes that a new frame has started processing. #define SYSTRACE_FRAME_ID(frame) \ - TRACE_EVENT_INSTANT(UTILS_PERFETTO_CATEGORY, "frame", "id", frame) + { /* scope for frame id trace */ \ + char buf[64]; \ + snprintf(buf, 64, "frame %u", frame); \ + SYSTRACE_NAME(buf); \ + } +// SYSTRACE_CALL is an SYSTRACE_NAME that uses the current function name. +#define SYSTRACE_CALL() SYSTRACE_NAME(__FUNCTION__) + +#define SYSTRACE_NAME_BEGIN(name) \ + ___trctx.traceBegin(SYSTRACE_TAG, name) + +#define SYSTRACE_NAME_END() \ + ___trctx.traceEnd(SYSTRACE_TAG) + + +/** + * Trace the beginning of an asynchronous event. Unlike ATRACE_BEGIN/ATRACE_END + * contexts, asynchronous events do not need to be nested. The name describes + * the event, and the cookie provides a unique identifier for distinguishing + * simultaneous events. The name and cookie used to begin an event must be + * used to end it. + */ +#define SYSTRACE_ASYNC_BEGIN(name, cookie) \ + ___trctx.asyncBegin(SYSTRACE_TAG, name, cookie) + +/** + * Trace the end of an asynchronous event. + * This should have a corresponding SYSTRACE_ASYNC_BEGIN. + */ +#define SYSTRACE_ASYNC_END(name, cookie) \ + ___trctx.asyncEnd(SYSTRACE_TAG, name, cookie) + +/** + * Traces an integer counter value. name is used to identify the counter. + * This can be used to track how a value changes over time. + */ #define SYSTRACE_VALUE32(name, val) \ - TRACE_COUNTER(UTILS_PERFETTO_CATEGORY, name, val) + ___trctx.value(SYSTRACE_TAG, name, int32_t(val)) #define SYSTRACE_VALUE64(name, val) \ - TRACE_COUNTER(UTILS_PERFETTO_CATEGORY, name, val) + ___trctx.value(SYSTRACE_TAG, name, int64_t(val)) -#endif // SYSTRACE_TAG == SYSTRACE_TAG_DISABLED +// ------------------------------------------------------------------------------------------------ +// No user serviceable code below... +// ------------------------------------------------------------------------------------------------ + +namespace utils { +namespace details { + +class UTILS_PUBLIC Systrace { + public: + + enum tags { + NEVER = SYSTRACE_TAG_NEVER, + ALWAYS = SYSTRACE_TAG_ALWAYS, + FILAMENT = SYSTRACE_TAG_FILAMENT, + JOBSYSTEM = SYSTRACE_TAG_JOBSYSTEM + // we could define more TAGS here, as we need them. + }; + + explicit Systrace(uint32_t tag) noexcept { + if (tag) init(tag); + } + + static void enable(uint32_t tags) noexcept; + static void disable(uint32_t tags) noexcept; + + + inline void traceBegin(uint32_t tag, const char* name) noexcept { + if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { + beginSection(this, name); + } + } + + inline void traceEnd(uint32_t tag) noexcept { + if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { + endSection(this); + } + } + + inline void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept { + if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { + beginAsyncSection(this, name, cookie); + } + } + + inline void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept { + if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { + endAsyncSection(this, name, cookie); + } + } + + inline void value(uint32_t tag, const char* name, int32_t value) noexcept { + if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { + setCounter(this, name, value); + } + } + + inline void value(uint32_t tag, const char* name, int64_t value) noexcept { + if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { + setCounter(this, name, value); + } + } + + private: + friend class ScopedTrace; + + // whether tracing is supported at all by the platform + + using ATrace_isEnabled_t = bool (*)(); + using ATrace_beginSection_t = void (*)(const char* sectionName); + using ATrace_endSection_t = void (*)(); + using ATrace_beginAsyncSection_t = void (*)(const char* sectionName, int32_t cookie); + using ATrace_endAsyncSection_t = void (*)(const char* sectionName, int32_t cookie); + using ATrace_setCounter_t = void (*)(const char* counterName, int64_t counterValue); + + struct GlobalState { + bool isTracingAvailable; + std::atomic isTracingEnabled; + int markerFd; + + ATrace_isEnabled_t ATrace_isEnabled; + ATrace_beginSection_t ATrace_beginSection; + ATrace_endSection_t ATrace_endSection; + ATrace_beginAsyncSection_t ATrace_beginAsyncSection; + ATrace_endAsyncSection_t ATrace_endAsyncSection; + ATrace_setCounter_t ATrace_setCounter; + + void (*beginSection)(Systrace* that, const char* name); + void (*endSection)(Systrace* that); + void (*beginAsyncSection)(Systrace* that, const char* name, int32_t cookie); + void (*endAsyncSection)(Systrace* that, const char* name, int32_t cookie); + void (*setCounter)(Systrace* that, const char* name, int64_t value); + }; + + static GlobalState sGlobalState; + + + // per-instance versions for better performance + ATrace_isEnabled_t ATrace_isEnabled; + ATrace_beginSection_t ATrace_beginSection; + ATrace_endSection_t ATrace_endSection; + ATrace_beginAsyncSection_t ATrace_beginAsyncSection; + ATrace_endAsyncSection_t ATrace_endAsyncSection; + ATrace_setCounter_t ATrace_setCounter; + + void (*beginSection)(Systrace* that, const char* name); + void (*endSection)(Systrace* that); + void (*beginAsyncSection)(Systrace* that, const char* name, int32_t cookie); + void (*endAsyncSection)(Systrace* that, const char* name, int32_t cookie); + void (*setCounter)(Systrace* that, const char* name, int64_t value); + + void init(uint32_t tag) noexcept; + + // cached values for faster access, no need to be initialized + bool mIsTracingEnabled; + int mMarkerFd = -1; + pid_t mPid; + + static void setup() noexcept; + static void init_once() noexcept; + static bool isTracingEnabled(uint32_t tag) noexcept; + + static void begin_body(int fd, int pid, const char* name) noexcept; + static void end_body(int fd, int pid) noexcept; + static void async_begin_body(int fd, int pid, const char* name, int32_t cookie) noexcept; + static void async_end_body(int fd, int pid, const char* name, int32_t cookie) noexcept; + static void int64_body(int fd, int pid, const char* name, int64_t value) noexcept; +}; + +// ------------------------------------------------------------------------------------------------ + +class UTILS_PUBLIC ScopedTrace { +public: + // we don't inline this because it's relatively heavy due to a global check + ScopedTrace(uint32_t tag, const char* name) noexcept: mTrace(tag), mTag(tag) { + mTrace.traceBegin(tag, name); + } + + inline ~ScopedTrace() noexcept { + mTrace.traceEnd(mTag); + } + +private: + Systrace mTrace; + const uint32_t mTag; +}; + +} // namespace details +} // namespace utils #endif // TNT_UTILS_ANDROID_SYSTRACE_H diff --git a/libs/utils/include/utils/darwin/Systrace.h b/libs/utils/include/utils/darwin/Systrace.h index 33de07962d..e1521cf705 100644 --- a/libs/utils/include/utils/darwin/Systrace.h +++ b/libs/utils/include/utils/darwin/Systrace.h @@ -29,25 +29,13 @@ #include #include -#if SYSTRACE_TAG == SYSTRACE_TAG_DISABLED - -#define SYSTRACE_ENABLE() -#define SYSTRACE_CONTEXT() -#define SYSTRACE_NAME(name) -#define SYSTRACE_FRAME_ID(frame) -#define SYSTRACE_NAME_BEGIN(name) -#define SYSTRACE_NAME_END() -#define SYSTRACE_CALL() -#define SYSTRACE_ASYNC_BEGIN(name, cookie) -#define SYSTRACE_ASYNC_END(name, cookie) -#define SYSTRACE_VALUE32(name, val) -#define SYSTRACE_VALUE64(name, val) - -#else - // enable tracing #define SYSTRACE_ENABLE() ::utils::details::Systrace::enable(SYSTRACE_TAG) +// disable tracing +#define SYSTRACE_DISABLE() ::utils::details::Systrace::disable(SYSTRACE_TAG) + + /** * Creates a Systrace context in the current scope. needed for calling all other systrace * commands below. @@ -105,8 +93,6 @@ extern thread_local std::stack ___tracerSections; #define SYSTRACE_VALUE64(name, val) \ ___tracer.value(SYSTRACE_TAG, name, int64_t(val)) -#endif // SYSTRACE_TAG == SYSTRACE_TAG_DISABLED - // ------------------------------------------------------------------------------------------------ // No user serviceable code below... // ------------------------------------------------------------------------------------------------ @@ -132,40 +118,50 @@ namespace utils { namespace details { class Systrace { -public: + public: + + enum tags { + NEVER = SYSTRACE_TAG_NEVER, + ALWAYS = SYSTRACE_TAG_ALWAYS, + FILAMENT = SYSTRACE_TAG_FILAMENT, + JOBSYSTEM = SYSTRACE_TAG_JOBSYSTEM + // we could define more TAGS here, as we need them. + }; + explicit Systrace(uint32_t tag) noexcept { if (tag) init(tag); } - static void enable(uint32_t tag) noexcept; + static void enable(uint32_t tags) noexcept; + static void disable(uint32_t tags) noexcept; - void traceBegin(uint32_t tag, const char* name) noexcept { + inline void traceBegin(uint32_t tag, const char* name) noexcept { if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { APPLE_SIGNPOST_EMIT(sGlobalState.systraceLog, OS_SIGNPOST_INTERVAL_BEGIN, OS_SIGNPOST_ID_EXCLUSIVE, name, name) } } - void traceEnd(uint32_t tag, const char* name) noexcept { + inline void traceEnd(uint32_t tag, const char* name) noexcept { if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { APPLE_SIGNPOST_EMIT(sGlobalState.systraceLog, OS_SIGNPOST_INTERVAL_END, OS_SIGNPOST_ID_EXCLUSIVE, name, "") } } - void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept { + inline void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept { if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { // TODO } } - void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept { + inline void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept { if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { // TODO } } - void value(uint32_t tag, const char* name, int32_t value) noexcept { + inline void value(uint32_t tag, const char* name, int32_t value) noexcept { if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { char buf[64]; snprintf(buf, 64, "%s - %d", name, value); @@ -174,7 +170,7 @@ public: } } - void value(uint32_t tag, const char* name, int64_t value) noexcept { + inline void value(uint32_t tag, const char* name, int64_t value) noexcept { if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { char buf[64]; snprintf(buf, 64, "%s - %lld", name, value); @@ -183,16 +179,16 @@ public: } } - void frameId(uint32_t tag, uint32_t frame) noexcept { + inline void frameId(uint32_t tag, uint32_t frame) noexcept { if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) { - char buf[64]; - snprintf(buf, 64, "frame %u", frame); + char buf[64]; \ + snprintf(buf, 64, "frame %u", frame); \ APPLE_SIGNPOST_EMIT(sGlobalState.frameIdLog, OS_SIGNPOST_EVENT, OS_SIGNPOST_ID_EXCLUSIVE, "frame", buf) } } -private: + private: friend class ScopedTrace; struct GlobalState { @@ -217,25 +213,25 @@ private: // ------------------------------------------------------------------------------------------------ class ScopedTrace { -public: + public: // we don't inline this because it's relatively heavy due to a global check ScopedTrace(uint32_t tag, const char* name) noexcept : mTrace(tag), mName(name), mTag(tag) { mTrace.traceBegin(tag, name); } - ~ScopedTrace() noexcept { + inline ~ScopedTrace() noexcept { mTrace.traceEnd(mTag, mName); } - void value(uint32_t tag, const char* name, int32_t v) noexcept { + inline void value(uint32_t tag, const char* name, int32_t v) noexcept { mTrace.value(tag, name, v); } - void value(uint32_t tag, const char* name, int64_t v) noexcept { + inline void value(uint32_t tag, const char* name, int64_t v) noexcept { mTrace.value(tag, name, v); } -private: + private: Systrace mTrace; const char* mName; const uint32_t mTag; diff --git a/libs/utils/src/JobSystem.cpp b/libs/utils/src/JobSystem.cpp index 2651927dea..ae8bba5443 100644 --- a/libs/utils/src/JobSystem.cpp +++ b/libs/utils/src/JobSystem.cpp @@ -15,8 +15,10 @@ */ // Note: The overhead of SYSTRACE_TAG_JOBSYSTEM is not negligible especially with parallel_for(). -#define SYSTRACE_TAG SYSTRACE_TAG_DISABLED +#ifndef SYSTRACE_TAG //#define SYSTRACE_TAG SYSTRACE_TAG_JOBSYSTEM +#define SYSTRACE_TAG SYSTRACE_TAG_NEVER +#endif // when SYSTRACE_TAG_JOBSYSTEM is used, enables even heavier systraces #define HEAVY_SYSTRACE 0 diff --git a/libs/utils/src/android/Systrace.cpp b/libs/utils/src/android/Systrace.cpp index 3528312961..0b03d4951d 100644 --- a/libs/utils/src/android/Systrace.cpp +++ b/libs/utils/src/android/Systrace.cpp @@ -14,26 +14,206 @@ * limitations under the License. */ -#include -#include +#include +#include -#include +#include -PERFETTO_TRACK_EVENT_STATIC_STORAGE_IN_NAMESPACE(systrace); +#include -namespace { +#include +#include +#include +#include -class SystraceStaticInitialization { -public: - SystraceStaticInitialization() { - perfetto::TracingInitArgs args; - args.backends |= perfetto::kSystemBackend; - perfetto::Tracing::Initialize(args); - systrace::TrackEvent::Register(); - } -}; +namespace utils { +namespace details { -UTILS_UNUSED SystraceStaticInitialization sSystraceStaticInitialization{}; +static pthread_once_t atrace_once_control = PTHREAD_ONCE_INIT; +template +static void loadSymbol(T*& pfn, const char *symbol) noexcept { + pfn = (T*)dlsym(RTLD_DEFAULT, symbol); } +Systrace::GlobalState Systrace::sGlobalState = {}; + +void Systrace::init_once() noexcept { + GlobalState& s = sGlobalState; + + s.markerFd = -1; + + // API 23 + loadSymbol(s.ATrace_isEnabled, "ATrace_isEnabled"); + loadSymbol(s.ATrace_beginSection, "ATrace_beginSection"); + loadSymbol(s.ATrace_endSection, "ATrace_endSection"); + // API 29 + loadSymbol(s.ATrace_beginAsyncSection, "ATrace_beginAsyncSection"); + loadSymbol(s.ATrace_endAsyncSection, "ATrace_endAsyncSection"); + loadSymbol(s.ATrace_setCounter, "ATrace_setCounter"); + + + const bool hasBasicAtrace = s.ATrace_isEnabled && + s.ATrace_beginSection && + s.ATrace_endSection; + + const bool hasFullATrace = hasBasicAtrace && + s.ATrace_beginAsyncSection && + s.ATrace_endAsyncSection && + s.ATrace_setCounter; + + if (!hasFullATrace) { + s.markerFd = open("/sys/kernel/debug/tracing/trace_marker", O_WRONLY | O_CLOEXEC); + } + + if (hasBasicAtrace && !hasFullATrace) { + // no-op if we don't have all these + s.ATrace_beginAsyncSection = [](const char* sectionName, int32_t cookie){}; + s.ATrace_endAsyncSection = [](const char* sectionName, int32_t cookie){}; + s.ATrace_setCounter = [](const char* sectionName, int64_t counterValue){}; + } + + const bool hasLegacySystrace = s.markerFd != -1; + + if (hasLegacySystrace && !hasFullATrace) { + // use legacy + s.beginSection = [](Systrace* that, const char* name) { + begin_body(that->mMarkerFd, that->mPid, name); + }; + s.endSection = [](Systrace* that) { + end_body(that->mMarkerFd, that->mPid); + }; + s.beginAsyncSection = [](Systrace* that, const char* name, int32_t cookie) { + async_begin_body(that->mMarkerFd, that->mPid, name, cookie); + }; + s.endAsyncSection = [](Systrace* that, const char* name, int32_t cookie) { + async_end_body(that->mMarkerFd, that->mPid, name, cookie); + }; + s.setCounter = [](Systrace* that, const char* name, int64_t value) { + int64_body(that->mMarkerFd, that->mPid, name, value); + }; + } else if (hasBasicAtrace) { + // we have at least basic ATrace + s.beginSection = [](Systrace* that, const char* name) { + that->ATrace_beginSection(name); + }; + s.endSection = [](Systrace* that) { + that->ATrace_endSection(); + }; + s.beginAsyncSection = [](Systrace* that, const char* name, int32_t cookie) { + that->ATrace_beginAsyncSection(name, cookie); + }; + s.endAsyncSection = [](Systrace* that, const char* name, int32_t cookie) { + that->ATrace_endAsyncSection(name, cookie); + }; + s.setCounter = [](Systrace* that, const char* name, int64_t value) { + that->ATrace_setCounter(name, value); + }; + } + + s.isTracingAvailable = hasLegacySystrace || hasFullATrace || hasBasicAtrace; +} + +void Systrace::setup() noexcept { + pthread_once(&atrace_once_control, init_once); +} + +void Systrace::enable(uint32_t tags) noexcept { + setup(); + if (UTILS_LIKELY(sGlobalState.isTracingAvailable)) { + sGlobalState.isTracingEnabled.fetch_or(tags, std::memory_order_relaxed); + } +} + +void Systrace::disable(uint32_t tags) noexcept { + sGlobalState.isTracingEnabled.fetch_and(~tags, std::memory_order_relaxed); +} + +// unfortunately, this generates quite a bit of code because reading a global is not +// trivial. For this reason, we do not inline this method. +bool Systrace::isTracingEnabled(uint32_t tag) noexcept { + if (tag) { + setup(); + return bool((sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) | SYSTRACE_TAG_ALWAYS) & tag); + } + return false; +} + +// ------------------------------------------------------------------------------------------------ + +void Systrace::init(uint32_t tag) noexcept { + // must be called first + mIsTracingEnabled = isTracingEnabled(tag); + + // cache static variables for better efficiency + GlobalState& s = sGlobalState; + ATrace_isEnabled = s.ATrace_isEnabled; + ATrace_beginSection = s.ATrace_beginSection; + ATrace_endSection = s.ATrace_endSection; + ATrace_beginAsyncSection = s.ATrace_beginAsyncSection; + ATrace_endAsyncSection = s.ATrace_endAsyncSection; + ATrace_setCounter = s.ATrace_setCounter; + + beginSection = s.beginSection; + endSection = s.endSection; + beginAsyncSection = s.beginAsyncSection; + endAsyncSection = s.endAsyncSection; + setCounter = s.setCounter; + + mMarkerFd = s.markerFd; + + mPid = getpid(); +} + +// ------------------------------------------------------------------------------------------------ + +/** + * Maximum size of a message that can be logged to the trace buffer. + * Note this message includes a tag, the pid, and the string given as the name. + * Names should be kept short to get the most use of the trace buffer. + */ +#define ATRACE_MESSAGE_LENGTH 512 + +#define WRITE_MSG(format_begin, format_end, pid, name, value) { \ + char buf[ATRACE_MESSAGE_LENGTH]; \ + int len = snprintf(buf, sizeof(buf), format_begin "%s" format_end, pid, \ + name, value); \ + if (len >= (int) sizeof(buf)) { \ + /* Given the sizeof(buf), and all of the current format buffers, \ + * it is impossible for name_len to be < 0 if len >= sizeof(buf). */ \ + int name_len = strlen(name) - (len - sizeof(buf)) - 1; \ + /* Truncate the name to make the message fit. */ \ + len = snprintf(buf, sizeof(buf), format_begin "%.*s" format_end, pid, \ + name_len, name, value); \ + } \ + write(fd, buf, len); \ +} + +void Systrace::begin_body(int fd, int pid, const char* name) noexcept { + char buf[ATRACE_MESSAGE_LENGTH]; + ssize_t len = snprintf(buf, sizeof(buf), "B|%d|%s", pid, name); + if (len >= sizeof(buf)) { + len = sizeof(buf) - 1; + } + write(fd, buf, size_t(len)); +} + +void Systrace::end_body(int fd, int pid) noexcept { + const char END_TAG = 'E'; + write(fd, &END_TAG, 1); +} + +void Systrace::async_begin_body(int fd, int pid, const char* name, int32_t cookie) noexcept { + WRITE_MSG("S|%d|", "|%" PRId32, pid, name, cookie); +} + +void Systrace::async_end_body(int fd, int pid, const char* name, int32_t cookie) noexcept { + WRITE_MSG("F|%d|", "|%" PRId32, pid, name, cookie); +} + +void Systrace::int64_body(int fd, int pid, const char* name, int64_t value) noexcept { + WRITE_MSG("C|%d|", "|%" PRId64, pid, name, value); +} + +} // namespace details +} // namespace utils diff --git a/libs/utils/src/darwin/Systrace.cpp b/libs/utils/src/darwin/Systrace.cpp index 759ca25f83..523b7ebd02 100644 --- a/libs/utils/src/darwin/Systrace.cpp +++ b/libs/utils/src/darwin/Systrace.cpp @@ -14,17 +14,11 @@ * limitations under the License. */ -#include - -#ifndef FILAMENT_APPLE_SYSTRACE -# define FILAMENT_APPLE_SYSTRACE 0 -#endif +#include +#include #if FILAMENT_APPLE_SYSTRACE -#include -#include -#include #include static pthread_once_t atrace_once_control = PTHREAD_ONCE_INIT; @@ -47,24 +41,21 @@ void Systrace::setup() noexcept { pthread_once(&atrace_once_control, init_once); } -void Systrace::enable(uint32_t tag) noexcept { +void Systrace::enable(uint32_t tags) noexcept { setup(); - uint32_t const mask = 1 << tag; - sGlobalState.isTracingEnabled.fetch_or(mask, std::memory_order_relaxed); + sGlobalState.isTracingEnabled.fetch_or(tags, std::memory_order_relaxed); } -void Systrace::disable(uint32_t tag) noexcept { - uint32_t const mask = 1 << tag; - sGlobalState.isTracingEnabled.fetch_and(~mask, std::memory_order_relaxed); +void Systrace::disable(uint32_t tags) noexcept { + sGlobalState.isTracingEnabled.fetch_and(~tags, std::memory_order_relaxed); } -// Unfortunately, this generates quite a bit of code because reading a global is not +// unfortunately, this generates quite a bit of code because reading a global is not // trivial. For this reason, we do not inline this method. bool Systrace::isTracingEnabled(uint32_t tag) noexcept { if (tag) { setup(); - uint32_t const mask = 1 << tag; - return bool(sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) & mask); + return bool((sGlobalState.isTracingEnabled.load(std::memory_order_relaxed) | SYSTRACE_TAG_ALWAYS) & tag); } return false; } From 6d413a4fafc9102baf1bfb06cfdeac2a328eade7 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 5 May 2025 11:26:02 -0700 Subject: [PATCH 12/34] add missing depth formats to getMetalFormat() this prevented to upload depth data into a depth texture --- filament/backend/src/metal/MetalEnums.h | 1 + 1 file changed, 1 insertion(+) diff --git a/filament/backend/src/metal/MetalEnums.h b/filament/backend/src/metal/MetalEnums.h index 5a2038ed10..46e0dac622 100644 --- a/filament/backend/src/metal/MetalEnums.h +++ b/filament/backend/src/metal/MetalEnums.h @@ -185,6 +185,7 @@ inline MTLPixelFormat getMetalFormat(PixelDataFormat format, PixelDataType type) CONVERT(RGBA_INTEGER, UINT, RGBA32Uint); CONVERT(RGBA_INTEGER, INT, RGBA32Sint); CONVERT(RGBA, FLOAT, RGBA32Float); + CONVERT(DEPTH_COMPONENT, FLOAT, Depth32Float); #undef CONVERT return MTLPixelFormatInvalid; From 3e1ea7cdfd79aec636376ab3705cbcf53bd4d36c Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 2 May 2025 16:08:21 -0700 Subject: [PATCH 13/34] clean-up our handling of descriptor-sets a bit more the main aim of this PR is to consolidate how we access the "per view" descriptor set to a single place. some validation code is moved into DescriptorSets.cpp, so we get a more centralized idea of what we do with the descriptors. also factorize in one place the filtering of sampler list by layouts. --- .../include/private/filament/DescriptorSets.h | 11 +- .../private/filament/SamplerInterfaceBlock.h | 5 + libs/filabridge/src/DescriptorSets.cpp | 118 ++++++++++++++---- libs/filabridge/src/SamplerInterfaceBlock.cpp | 23 +++- libs/filamat/src/GLSLPostProcessor.cpp | 93 +++++++------- libs/filamat/src/MaterialVariants.cpp | 64 +--------- libs/filamat/src/SamplerBindingMap.cpp | 0 libs/filamat/src/SamplerBindingMap.h | 0 libs/filamat/src/shaders/ShaderGenerator.cpp | 44 +------ libs/filamat/src/shaders/ShaderGenerator.h | 7 -- 10 files changed, 180 insertions(+), 185 deletions(-) delete mode 100644 libs/filamat/src/SamplerBindingMap.cpp delete mode 100644 libs/filamat/src/SamplerBindingMap.h diff --git a/libs/filabridge/include/private/filament/DescriptorSets.h b/libs/filabridge/include/private/filament/DescriptorSets.h index 171c8650d1..5ee10729b4 100644 --- a/libs/filabridge/include/private/filament/DescriptorSets.h +++ b/libs/filabridge/include/private/filament/DescriptorSets.h @@ -20,6 +20,7 @@ #include #include +#include #include @@ -39,8 +40,16 @@ backend::DescriptorSetLayout getPerViewDescriptorSetLayout( ReflectionMode reflectionMode, RefractionMode refractionMode) noexcept; +backend::DescriptorSetLayout getPerViewDescriptorSetLayoutWithVariant( + Variant variant, + MaterialDomain domain, + UserVariantFilterMask variantFilter, + bool isLit, + ReflectionMode reflectionMode, + RefractionMode refractionMode) noexcept; + utils::CString getDescriptorName( - filament::DescriptorSetBindingPoints set, + DescriptorSetBindingPoints set, backend::descriptor_binding_t binding) noexcept; } // namespace filament::descriptor_sets diff --git a/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h b/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h index 243f2dcfb6..28e5e8d4eb 100644 --- a/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h +++ b/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h @@ -23,6 +23,8 @@ #include #include +#include + #include #include #include @@ -128,6 +130,9 @@ public: static utils::CString generateUniformName(const char* group, const char* sampler) noexcept; + static SamplerInfoList filterSamplerList(SamplerInfoList list, + backend::DescriptorSetLayout const& descriptorSetLayout); + private: friend class Builder; diff --git a/libs/filabridge/src/DescriptorSets.cpp b/libs/filabridge/src/DescriptorSets.cpp index b313fcc056..cdcd44b9d0 100644 --- a/libs/filabridge/src/DescriptorSets.cpp +++ b/libs/filabridge/src/DescriptorSets.cpp @@ -17,6 +17,7 @@ #include "private/filament/DescriptorSets.h" #include +#include #include @@ -26,34 +27,35 @@ #include #include -#include +#include #include +#include namespace filament::descriptor_sets { using namespace backend; -static DescriptorSetLayout const postProcessDescriptorSetLayout{{ +static constexpr std::initializer_list postProcessDescriptorSetLayoutList = { { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, -}}; +}; -static DescriptorSetLayout const depthVariantDescriptorSetLayout{{ +static constexpr std::initializer_list depthVariantDescriptorSetLayoutList = { { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, -}}; +}; // ssrVariantDescriptorSetLayout must match perViewDescriptorSetLayout's vertex stage. This is // because the SSR variant is always using the "standard" vertex shader (i.e. there is no // dedicated SSR vertex shader), which uses perViewDescriptorSetLayout. // This means that PerViewBindingPoints::SHADOWS must be in the layout even though it's not used // by the SSR variant. -static DescriptorSetLayout const ssrVariantDescriptorSetLayout{{ - { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, - { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS }, - { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE }, - { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR }, -}}; +static constexpr std::initializer_list ssrVariantDescriptorSetLayoutList = { + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR }, +}; -static DescriptorSetLayout perViewDescriptorSetLayout = {{ +static constexpr std::initializer_list perViewDescriptorSetLayoutList = { { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS }, { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::LIGHTS }, @@ -66,16 +68,30 @@ static DescriptorSetLayout perViewDescriptorSetLayout = {{ { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSAO }, { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR }, { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FOG }, -}}; +}; -static DescriptorSetLayout perRenderableDescriptorSetLayout = {{ +static constexpr std::initializer_list perRenderableDescriptorSetLayoutList = { { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::OBJECT_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET }, { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::BONES_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET }, { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::MORPHING_UNIFORMS }, { DescriptorType::SAMPLER, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS }, { DescriptorType::SAMPLER, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS }, { DescriptorType::SAMPLER, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::BONES_INDICES_AND_WEIGHTS }, -}}; +}; + +// used for post-processing passes +static DescriptorSetLayout const postProcessDescriptorSetLayout{ postProcessDescriptorSetLayoutList }; + +// used to generate shadow-maps +static DescriptorSetLayout const depthVariantDescriptorSetLayout{ depthVariantDescriptorSetLayoutList }; + +static DescriptorSetLayout const ssrVariantDescriptorSetLayout{ ssrVariantDescriptorSetLayoutList }; + +// Used for generating the color pass (i.e. the main pass). This is in fact a template that gets +// declined into 8 different layouts, based on variants. +static DescriptorSetLayout perViewDescriptorSetLayout = { perViewDescriptorSetLayoutList }; + +static DescriptorSetLayout perRenderableDescriptorSetLayout = { perRenderableDescriptorSetLayoutList }; DescriptorSetLayout const& getPostProcessLayout() noexcept { return postProcessDescriptorSetLayout; @@ -93,8 +109,8 @@ DescriptorSetLayout const& getPerRenderableLayout() noexcept { return perRenderableDescriptorSetLayout; } -utils::CString getDescriptorName(DescriptorSetBindingPoints set, - descriptor_binding_t binding) noexcept { +utils::CString getDescriptorName(DescriptorSetBindingPoints const set, + descriptor_binding_t const binding) noexcept { using namespace std::literals; static std::unordered_map const set0{{ @@ -140,11 +156,11 @@ utils::CString getDescriptorName(DescriptorSetBindingPoints set, } DescriptorSetLayout getPerViewDescriptorSetLayout( - MaterialDomain domain, - UserVariantFilterMask variantFilter, - bool isLit, - ReflectionMode reflectionMode, - RefractionMode refractionMode) noexcept { + MaterialDomain const domain, + UserVariantFilterMask const variantFilter, + bool const isLit, + ReflectionMode const reflectionMode, + RefractionMode const refractionMode) noexcept { bool const ssr = reflectionMode == ReflectionMode::SCREEN_SPACE || refractionMode == RefractionMode::SCREEN_SPACE; @@ -187,11 +203,65 @@ DescriptorSetLayout getPerViewDescriptorSetLayout( return layout; } case MaterialDomain::POST_PROCESS: - return descriptor_sets::getPostProcessLayout(); + return postProcessDescriptorSetLayout; case MaterialDomain::COMPUTE: // TODO: what's the layout for compute? - return descriptor_sets::getPostProcessLayout(); + return postProcessDescriptorSetLayout; } } +DescriptorSetLayout getPerViewDescriptorSetLayoutWithVariant( + Variant const variant, + MaterialDomain domain, + UserVariantFilterMask const variantFilter, + bool const isLit, + ReflectionMode const reflectionMode, + RefractionMode const refractionMode) noexcept { + if (Variant::isValidDepthVariant(variant)) { + return depthVariantDescriptorSetLayout; + } + if (Variant::isSSRVariant(variant)) { + return ssrVariantDescriptorSetLayout; + } + // We need to filter out all the descriptors not included in the "resolved" layout below + return getPerViewDescriptorSetLayout(domain, variantFilter, + isLit, reflectionMode, refractionMode); +} + + + +template +constexpr static ITERATOR find_if(ITERATOR first, ITERATOR last, PREDICATE pred) { + for (; first != last; ++first) + if (pred(*first)) break; + return first; +} + +constexpr static bool checkConsistency() noexcept { + // check that all descriptors that apply to the vertex stage in perViewDescriptorSetLayout + // are present in ssrVariantDescriptorSetLayout; meaning that the latter is compatible + // with the former. + for (auto const& r: perViewDescriptorSetLayoutList) { + if (hasShaderType(r.stageFlags, ShaderStage::VERTEX)) { + auto const pos = find_if( + ssrVariantDescriptorSetLayoutList.begin(), + ssrVariantDescriptorSetLayoutList.end(), + [r](auto const& l) { + return l.count == r.count && + l.type == r.type && + l.binding == r.binding && + l.flags == r.flags && + l.stageFlags == r.stageFlags; + }); + if (pos == ssrVariantDescriptorSetLayoutList.end()) { + return false; + } + } + } + return true; +} + +static_assert(checkConsistency(), "ssrVariantDescriptorSetLayout is not compatible with " + "perViewDescriptorSetLayout"); + } // namespace filament::descriptor_sets diff --git a/libs/filabridge/src/SamplerInterfaceBlock.cpp b/libs/filabridge/src/SamplerInterfaceBlock.cpp index 1d1f4c387f..da2a1a76c4 100644 --- a/libs/filabridge/src/SamplerInterfaceBlock.cpp +++ b/libs/filabridge/src/SamplerInterfaceBlock.cpp @@ -16,6 +16,7 @@ #include "private/filament/SamplerInterfaceBlock.h" +#include #include @@ -102,7 +103,7 @@ const SamplerInterfaceBlock::SamplerInfo* SamplerInterfaceBlock::getSamplerInfo( return &mSamplersInfoList[pos->second]; } -utils::CString SamplerInterfaceBlock::generateUniformName(const char* group, const char* sampler) noexcept { +CString SamplerInterfaceBlock::generateUniformName(const char* group, const char* sampler) noexcept { char uniformName[256]; // sampler interface block name @@ -117,9 +118,27 @@ utils::CString SamplerInterfaceBlock::generateUniformName(const char* group, con std::min(sizeof(uniformName) / 2 - 2, strlen(sampler)), prefix + 1); *last++ = 0; // null terminator - assert(last <= std::end(uniformName)); + assert_invariant(last <= std::end(uniformName)); return CString{ uniformName, size_t(last - uniformName) - 1u }; } +SamplerInterfaceBlock::SamplerInfoList SamplerInterfaceBlock::filterSamplerList( + SamplerInfoList list, backend::DescriptorSetLayout const& descriptorSetLayout) { + // remove all the samplers that are not included in the descriptor-set layout + list.erase( + std::remove_if(list.begin(), list.end(), + [&](auto const& entry) { + auto pos = std::find_if( + descriptorSetLayout.bindings.begin(), + descriptorSetLayout.bindings.end(), + [&entry](const auto& item) { + return item.binding == entry.binding; + }); + return pos == descriptorSetLayout.bindings.end(); + }), list.end()); + + return list; +} + } // namespace filament diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index 3283f2bd3f..52d4a30dc0 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -34,14 +34,19 @@ #include "MetalArgumentBuffer.h" #include "SpirvFixup.h" -#include "utils/ostream.h" #include +#include +#include #include +#include +#include +#include #include #include +#include #include #ifdef FILAMENT_SUPPORTS_WEBGPU @@ -151,21 +156,16 @@ DescriptorSetLayout getPerMaterialDescriptorSet(SamplerInterfaceBlock const& sib return layout; } -static void collectDescriptorsForSet(filament::DescriptorSetBindingPoints set, +static void collectDescriptorsForSet(DescriptorSetBindingPoints set, const GLSLPostProcessor::Config& config, DescriptorSetInfo& descriptors) { const MaterialInfo& material = *config.materialInfo; - DescriptorSetLayout const info = [&]() { + // get the descriptor set layout for the given pinding point + DescriptorSetLayout const descriptorSetLayout = [&] { switch (set) { case DescriptorSetBindingPoints::PER_VIEW: { - if (filament::Variant::isValidDepthVariant(config.variant)) { - return descriptor_sets::getDepthVariantLayout(); - } - if (filament::Variant::isSSRVariant(config.variant)) { - return descriptor_sets::getSsrVariantLayout(); - } - return descriptor_sets::getPerViewDescriptorSetLayout(config.domain, - config.variantFilter, + return descriptor_sets::getPerViewDescriptorSetLayoutWithVariant( + config.variant, config.domain, config.variantFilter, material.isLit || material.hasShadowMultiplier, material.reflectionMode, material.refractionMode); @@ -179,7 +179,8 @@ static void collectDescriptorsForSet(filament::DescriptorSetBindingPoints set, } }(); - auto samplerList = [&]() { + // get the sampler list for this binding point + auto samplerList = [&] { switch (set) { case DescriptorSetBindingPoints::PER_VIEW: return SibGenerator::getPerViewSib(config.variant).getSamplerInfoList(); @@ -192,42 +193,34 @@ static void collectDescriptorsForSet(filament::DescriptorSetBindingPoints set, } }(); - // remove all the samplers that are not included in the descriptor-set layout - samplerList.erase(std::remove_if(samplerList.begin(), samplerList.end(), - [&info](auto const& entry) { - auto pos = std::find_if(info.bindings.begin(), - info.bindings.end(), [&entry](const auto& item) { - return item.binding == entry.binding; - }); - return pos == info.bindings.end(); - }), - samplerList.end()); + // filter the list with the descriptor set layout + auto const descriptorSetSamplerList = + SamplerInterfaceBlock::filterSamplerList(std::move(samplerList), descriptorSetLayout); - auto getDescriptorName = [&](DescriptorSetBindingPoints set, descriptor_binding_t binding) { + // helper to get the name of a descriptor for this set, given a binding. + auto getDescriptorName = [set, &descriptorSetSamplerList](descriptor_binding_t binding) { if (set == DescriptorSetBindingPoints::PER_MATERIAL) { - auto pos = std::find_if(samplerList.begin(), samplerList.end(), + auto pos = std::find_if(descriptorSetSamplerList.begin(), descriptorSetSamplerList.end(), [&](const auto& entry) { return entry.binding == binding; }); - if (pos == samplerList.end()) { + if (pos == descriptorSetSamplerList.end()) { return descriptor_sets::getDescriptorName(set, binding); } - SamplerInterfaceBlock::SamplerInfo& sampler = *pos; - return sampler.uniformName; + return pos->uniformName; } return descriptor_sets::getDescriptorName(set, binding); }; - for (size_t i = 0; i < info.bindings.size(); i++) { - backend::descriptor_binding_t binding = info.bindings[i].binding; - auto name = getDescriptorName(set, binding); - if (info.bindings[i].type == DescriptorType::SAMPLER || - info.bindings[i].type == DescriptorType::SAMPLER_EXTERNAL) { - auto pos = std::find_if(samplerList.begin(), samplerList.end(), + for (auto descriptor : descriptorSetLayout.bindings) { + descriptor_binding_t binding = descriptor.binding; + auto name = getDescriptorName(binding); + if (descriptor.type == DescriptorType::SAMPLER || + descriptor.type == DescriptorType::SAMPLER_EXTERNAL) { + auto pos = std::find_if(descriptorSetSamplerList.begin(), descriptorSetSamplerList.end(), [&](const auto& entry) { return entry.binding == binding; }); - assert_invariant(pos != samplerList.end()); - SamplerInterfaceBlock::SamplerInfo& sampler = *pos; - descriptors.emplace_back(name, info.bindings[i], sampler); + assert_invariant(pos != descriptorSetSamplerList.end()); + descriptors.emplace_back(name, descriptor, *pos); } else { - descriptors.emplace_back(name, info.bindings[i], std::nullopt); + descriptors.emplace_back(name, descriptor, std::nullopt); } } @@ -365,7 +358,7 @@ static std::string stringifySpvOptimizerMessage(spv_message_level_t level, const } void GLSLPostProcessor::spirvToMsl(const SpirvBlob* spirv, std::string* outMsl, - filament::backend::ShaderStage stage, filament::backend::ShaderModel shaderModel, + ShaderStage stage, ShaderModel shaderModel, bool useFramebufferFetch, const DescriptorSets& descriptorSets, const ShaderMinifier* minifier) { using namespace msl; @@ -674,7 +667,7 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co // SpvRules should be enough. // I think this could cause the compilation to fail on gl_VertexID. using Type = std::underlying_type_t; - msg = EShMessages(Type(msg) | Type(EShMessages::EShMsgVulkanRules)); + msg = EShMessages(Type(msg) | Type(EShMsgVulkanRules)); } bool const ok = tShader.parse(&DefaultTBuiltInResource, internalConfig.langVersion, false, msg); @@ -684,7 +677,7 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co } // add texture lod bias - if (config.shaderType == backend::ShaderStage::FRAGMENT && + if (config.shaderType == ShaderStage::FRAGMENT && config.domain == MaterialDomain::SURFACE) { GLSLTools::textureLodBias(tShader); } @@ -760,8 +753,8 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co return true; } -bool GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader, - GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const { +bool GLSLPostProcessor::preprocessOptimization(TShader& tShader, + Config const& config, InternalConfig& internalConfig) const { using TargetApi = MaterialBuilder::TargetApi; assert_invariant(bool(internalConfig.spirvOutput) == (config.targetApi != TargetApi::OPENGL)); @@ -832,7 +825,7 @@ bool GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader, } bool GLSLPostProcessor::fullOptimization(const TShader& tShader, - GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const { + Config const& config, InternalConfig& internalConfig) const { SpirvBlob spirv; bool const optimizeForSize = mOptimization == MaterialBuilderBase::Optimization::SIZE; @@ -928,7 +921,7 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader, #else try { *internalConfig.glslOutput = glslCompiler.compile(); - } catch (spirv_cross::CompilerError e) { + } catch (CompilerError e) { slog.e << "ERROR: " << e.what() << io::endl; return false; } @@ -948,8 +941,8 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader, return true; } -std::shared_ptr GLSLPostProcessor::createEmptyOptimizer() { - auto optimizer = std::make_shared(SPV_ENV_UNIVERSAL_1_3); +std::shared_ptr GLSLPostProcessor::createEmptyOptimizer() { + auto optimizer = std::make_shared(SPV_ENV_UNIVERSAL_1_3); optimizer->SetMessageConsumer([](spv_message_level_t level, const char* source, const spv_position_t& position, const char* message) { if (!filterSpvOptimizerMessage(level)) { @@ -961,7 +954,7 @@ std::shared_ptr GLSLPostProcessor::createEmptyOptimizer() { return optimizer; } -std::shared_ptr GLSLPostProcessor::createOptimizer( +std::shared_ptr GLSLPostProcessor::createOptimizer( MaterialBuilder::Optimization optimization, Config const& config) { auto optimizer = createEmptyOptimizer(); @@ -1000,7 +993,7 @@ void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) } void GLSLPostProcessor::fixupClipDistance( - SpirvBlob& spirv, GLSLPostProcessor::Config const& config) const { + SpirvBlob& spirv, Config const& config) const { if (!config.usesClipDistance) { return; } @@ -1040,7 +1033,7 @@ void GLSLPostProcessor::fixupClipDistance( void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config const& config) { - auto RegisterPass = [&](spvtools::Optimizer::PassToken&& pass, + auto RegisterPass = [&](Optimizer::PassToken&& pass, MaterialBuilder::TargetApi apiFilter = MaterialBuilder::TargetApi::ALL) { if (!(config.targetApi & apiFilter)) { return; @@ -1085,7 +1078,7 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c } void GLSLPostProcessor::registerSizePasses(Optimizer& optimizer, Config const& config) { - auto RegisterPass = [&](spvtools::Optimizer::PassToken&& pass, + auto RegisterPass = [&](Optimizer::PassToken&& pass, MaterialBuilder::TargetApi apiFilter = MaterialBuilder::TargetApi::ALL) { if (!(config.targetApi & apiFilter)) { return; diff --git a/libs/filamat/src/MaterialVariants.cpp b/libs/filamat/src/MaterialVariants.cpp index b6ce7ccd74..2bccf95449 100644 --- a/libs/filamat/src/MaterialVariants.cpp +++ b/libs/filamat/src/MaterialVariants.cpp @@ -16,8 +16,6 @@ #include "MaterialVariants.h" -#include "shaders/ShaderGenerator.h" - #include #include @@ -25,16 +23,8 @@ #include -#include -#include -#include - -#include #include -#include -#include - namespace filamat { std::vector determineSurfaceVariants( @@ -62,58 +52,6 @@ std::vector determineSurfaceVariants( if (fragmentVariant == variant) { variants.emplace_back(variant, filament::backend::ShaderStage::FRAGMENT); } - - // Here we make sure that the combination of vertex and fragment variants have compatible - // PER_VIEW descriptor-set layouts. This could actually be a static/compile-time check - // because it is entirely decided in DescriptorSets.cpp. Unfortunately it's not possible - // to write this entirely as a constexpr. - - if (UTILS_UNLIKELY(vertexVariant != fragmentVariant)) { - // fragment and vertex variants are different, we need to check the layouts are - // compatible. - using filament::ReflectionMode; - using filament::RefractionMode; - using filament::backend::ShaderStage; - - // And we need to do that for all configurations of the "PER_VIEW" descriptor set - // layouts (there are eight). - // See ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant. - for (auto reflection: { - ReflectionMode::SCREEN_SPACE, - ReflectionMode::DEFAULT }) { - for (auto refraction: { - RefractionMode::SCREEN_SPACE, - RefractionMode::CUBEMAP, - RefractionMode::NONE }) { - auto const vdsl = ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( - vertexVariant, userVariantFilter, isLit || shadowMultiplier, - reflection, refraction); - auto const fdsl = ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( - fragmentVariant, userVariantFilter, isLit || shadowMultiplier, - reflection, refraction); - // Check that all bindings present in the vertex shader DescriptorSetLayout - // are also present in the fragment shader DescriptorSetLayout. - for (auto const& r: vdsl.bindings) { - if (!hasShaderType(r.stageFlags, ShaderStage::VERTEX)) { - // ignore descriptors that are of the fragment stage only - continue; - } - auto const pos = std::find_if(fdsl.bindings.begin(), fdsl.bindings.end(), - [r](auto const& l) { - return l.count == r.count && l.type == r.type && - l.binding == r.binding && l.flags == r.flags && - l.stageFlags == r.stageFlags; - }); - - // A mismatch is fatal. The material is ill-formed. This typically - // mean a bug / inconsistency in DescriptorsSets.cpp - FILAMENT_CHECK_POSTCONDITION(pos != fdsl.bindings.end()) - << "Variant " << +k << " has mismatched descriptorset layouts"; - } - } - } - } - } return variants; } @@ -133,7 +71,7 @@ std::vector determinePostProcessVariants() { std::vector determineComputeVariants() { // TODO: should we have variants for compute shaders? std::vector variants; - filament::Variant variant(0); + filament::Variant const variant(0); variants.emplace_back(variant, filament::backend::ShaderStage::COMPUTE); return variants; } diff --git a/libs/filamat/src/SamplerBindingMap.cpp b/libs/filamat/src/SamplerBindingMap.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/libs/filamat/src/SamplerBindingMap.h b/libs/filamat/src/SamplerBindingMap.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index 3c7cd4f7a5..e6fe317385 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -604,27 +604,13 @@ std::string ShaderGenerator::createSurfaceFragmentProgram(ShaderModel shaderMode if (featureLevel >= FeatureLevel::FEATURE_LEVEL_1) { assert_invariant(mMaterialDomain == MaterialDomain::SURFACE); - - auto const perViewDescriptorSetLayout = getPerViewDescriptorSetLayoutWithVariant( - variant, variantFilter, - material.isLit || material.hasShadowMultiplier, - material.reflectionMode, material.refractionMode); - // this is the list of samplers we need to filter - auto list = SibGenerator::getPerViewSib(variant).getSamplerInfoList(); - - // remove all the samplers that are not included in the descriptor-set layout - list.erase( - std::remove_if(list.begin(), list.end(), - [&perViewDescriptorSetLayout](auto const& entry) { - auto pos = std::find_if( - perViewDescriptorSetLayout.bindings.begin(), - perViewDescriptorSetLayout.bindings.end(), - [&entry](const auto& item) { - return item.binding == entry.binding; - }); - return pos == perViewDescriptorSetLayout.bindings.end(); - }), list.end()); + auto const list = SamplerInterfaceBlock::filterSamplerList( + SibGenerator::getPerViewSib(variant).getSamplerInfoList(), + descriptor_sets::getPerViewDescriptorSetLayoutWithVariant( + variant, mMaterialDomain, variantFilter, + material.isLit || material.hasShadowMultiplier, + material.reflectionMode, material.refractionMode)); cg.generateCommonSamplers(fs, DescriptorSetBindingPoints::PER_VIEW, list); } @@ -841,22 +827,4 @@ bool ShaderGenerator::hasStereo( && featureLevel > MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0; } -backend::DescriptorSetLayout ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( - filament::Variant variant, - UserVariantFilterMask variantFilter, - bool isLit, - ReflectionMode reflectionMode, - RefractionMode refractionMode) { - if (filament::Variant::isValidDepthVariant(variant)) { - return descriptor_sets::getDepthVariantLayout(); - } - if (filament::Variant::isSSRVariant(variant)) { - return descriptor_sets::getSsrVariantLayout(); - } - // We need to filter out all the descriptors not included in the "resolved" layout below - return descriptor_sets::getPerViewDescriptorSetLayout( - MaterialDomain::SURFACE, variantFilter, - isLit, reflectionMode, refractionMode); -} - } // namespace filament diff --git a/libs/filamat/src/shaders/ShaderGenerator.h b/libs/filamat/src/shaders/ShaderGenerator.h index fd597b13ca..9d9b470a91 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.h +++ b/libs/filamat/src/shaders/ShaderGenerator.h @@ -88,13 +88,6 @@ public: MaterialBuilder::FeatureLevel featureLevel, MaterialInfo const& material) noexcept; - static filament::backend::DescriptorSetLayout getPerViewDescriptorSetLayoutWithVariant( - filament::Variant variant, - filament::UserVariantFilterMask variantFilter, - bool isLit, - filament::ReflectionMode reflectionMode, - filament::RefractionMode refractionMode); - private: static void generateVertexDomainDefines(utils::io::sstream& out, filament::VertexDomain domain) noexcept; From 3d7832205846e2c1784f50e1dd312159866b741a Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 1 May 2025 12:41:16 -0700 Subject: [PATCH 14/34] clang-tidy cleanup --- .../filamat/include/filamat/MaterialBuilder.h | 6 +- libs/filamat/src/MaterialBuilder.cpp | 436 +++++++++--------- 2 files changed, 227 insertions(+), 215 deletions(-) diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index cea4a338ed..14ae424fec 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -603,7 +603,7 @@ public: * extension will be derived from the shader stage. For example, mymaterial_0x0e.frag, * mymaterial_0x18.vert, etc. */ - MaterialBuilder& saveRawVariants(bool saveVariants) noexcept; + MaterialBuilder& saveRawVariants(bool saveRawVariants) noexcept; //! If true, will include debugging information in generated SPIRV. MaterialBuilder& generateDebugInfo(bool generateDebugInfo) noexcept; @@ -635,7 +635,7 @@ public: * Build the material. If you are using the Filament engine with this library, you should use * the job system provided by Engine. */ - Package build(utils::JobSystem& jobSystem) noexcept; + Package build(utils::JobSystem& jobSystem); public: // The methods and types below are for internal use @@ -809,7 +809,7 @@ private: // Multiple calls to findProperties accumulate the property sets across fragment // and vertex shaders in mProperties. bool findProperties(filament::backend::ShaderStage type, - MaterialBuilder::PropertyList& allProperties, + MaterialBuilder::PropertyList const& allProperties, CodeGenParams const& semanticCodeGenParams) noexcept; bool runSemanticAnalysis(MaterialInfo* inOutInfo, diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index d1e145aa31..9727948375 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -17,58 +17,71 @@ #include "filamat/MaterialBuilder.h" #include +#include +#include #include "Includes.h" #include "MaterialVariants.h" #include "PushConstantDefinitions.h" -#include "shaders/SibGenerator.h" -#include "shaders/UibGenerator.h" - #include "GLSLPostProcessor.h" + #include "sca/GLSLTools.h" #include "shaders/MaterialInfo.h" #include "shaders/ShaderGenerator.h" +#include "shaders/UibGenerator.h" #include "eiff/BlobDictionary.h" +#include "eiff/ChunkContainer.h" +#include "eiff/DictionarySpirvChunk.h" +#include "eiff/DictionaryTextChunk.h" #include "eiff/LineDictionary.h" +#include "eiff/MaterialBinaryChunk.h" #include "eiff/MaterialInterfaceBlockChunk.h" #include "eiff/MaterialTextChunk.h" -#include "eiff/MaterialBinaryChunk.h" -#include "eiff/ChunkContainer.h" -#include "eiff/SimpleFieldChunk.h" -#include "eiff/DictionaryTextChunk.h" -#include "eiff/DictionarySpirvChunk.h" +#include "eiff/ShaderEntry.h" #include -#include -#include #include #include -#include +#include +#include +#include + +#include +#include #include #include -#include -#include +#include #include +#include #include #include #include #include -#include +#include +#include +#include + +#include #include #include +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include #include #include +#include namespace filamat { @@ -77,34 +90,34 @@ using namespace filament; // Note: the VertexAttribute enum value must match the index in the array const MaterialBuilder::AttributeDatabase MaterialBuilder::sAttributeDatabase = {{ - { "position", AttributeType::FLOAT4, VertexAttribute::POSITION }, - { "tangents", AttributeType::FLOAT4, VertexAttribute::TANGENTS }, - { "color", AttributeType::FLOAT4, VertexAttribute::COLOR }, - { "uv0", AttributeType::FLOAT2, VertexAttribute::UV0 }, - { "uv1", AttributeType::FLOAT2, VertexAttribute::UV1 }, - { "bone_indices", AttributeType::UINT4, VertexAttribute::BONE_INDICES }, - { "bone_weights", AttributeType::FLOAT4, VertexAttribute::BONE_WEIGHTS }, + { "position", AttributeType::FLOAT4, POSITION }, + { "tangents", AttributeType::FLOAT4, TANGENTS }, + { "color", AttributeType::FLOAT4, COLOR }, + { "uv0", AttributeType::FLOAT2, UV0 }, + { "uv1", AttributeType::FLOAT2, UV1 }, + { "bone_indices", AttributeType::UINT4, BONE_INDICES }, + { "bone_weights", AttributeType::FLOAT4, BONE_WEIGHTS }, { }, - { "custom0", AttributeType::FLOAT4, VertexAttribute::CUSTOM0 }, - { "custom1", AttributeType::FLOAT4, VertexAttribute::CUSTOM1 }, - { "custom2", AttributeType::FLOAT4, VertexAttribute::CUSTOM2 }, - { "custom3", AttributeType::FLOAT4, VertexAttribute::CUSTOM3 }, - { "custom4", AttributeType::FLOAT4, VertexAttribute::CUSTOM4 }, - { "custom5", AttributeType::FLOAT4, VertexAttribute::CUSTOM5 }, - { "custom6", AttributeType::FLOAT4, VertexAttribute::CUSTOM6 }, - { "custom7", AttributeType::FLOAT4, VertexAttribute::CUSTOM7 }, + { "custom0", AttributeType::FLOAT4, CUSTOM0 }, + { "custom1", AttributeType::FLOAT4, CUSTOM1 }, + { "custom2", AttributeType::FLOAT4, CUSTOM2 }, + { "custom3", AttributeType::FLOAT4, CUSTOM3 }, + { "custom4", AttributeType::FLOAT4, CUSTOM4 }, + { "custom5", AttributeType::FLOAT4, CUSTOM5 }, + { "custom6", AttributeType::FLOAT4, CUSTOM6 }, + { "custom7", AttributeType::FLOAT4, CUSTOM7 }, }}; std::atomic MaterialBuilderBase::materialBuilderClients(0); -inline void assertSingleTargetApi(MaterialBuilderBase::TargetApi api) { +static void assertSingleTargetApi(MaterialBuilderBase::TargetApi api) { // Assert that a single bit is set. - UTILS_UNUSED uint8_t bits = (uint8_t)api; - assert(bits && !(bits & bits - 1u)); + UTILS_UNUSED uint8_t const bits = uint8_t(api); + assert_invariant(bits && !(bits & bits - 1u)); } -void MaterialBuilderBase::prepare(bool vulkanSemantics, - filament::backend::FeatureLevel featureLevel) { +void MaterialBuilderBase::prepare(bool const vulkanSemantics, + backend::FeatureLevel const featureLevel) { mCodeGenPermutations.clear(); mShaderModels.reset(); @@ -118,12 +131,12 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics, } // OpenGL is a special case. If we're doing any optimization, then we need to go to Spir-V. - TargetLanguage glTargetLanguage = mOptimization > MaterialBuilder::Optimization::PREPROCESSOR ? + TargetLanguage glTargetLanguage = mOptimization > Optimization::PREPROCESSOR ? TargetLanguage::SPIRV : TargetLanguage::GLSL; if (vulkanSemantics) { // Currently GLSLPostProcessor.cpp is incapable of compiling SPIRV to GLSL without // running the optimizer. For now we just activate the optimizer in that case. - mOptimization = MaterialBuilder::Optimization::PERFORMANCE; + mOptimization = Optimization::PERFORMANCE; glTargetLanguage = TargetLanguage::SPIRV; } @@ -134,8 +147,8 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics, // Generally build for a minimum of feature level 1. If feature level 0 is specified, an extra // permutation is specifically included for the OpenGL/mobile target. - MaterialBuilder::FeatureLevel effectiveFeatureLevel = - std::max(featureLevel, filament::backend::FeatureLevel::FEATURE_LEVEL_1); + MaterialBuilder::FeatureLevel const effectiveFeatureLevel = + std::max(featureLevel, backend::FeatureLevel::FEATURE_LEVEL_1); // Build a list of codegen permutations, which is useful across all types of material builders. static_assert(backend::SHADER_MODEL_COUNT == 2); @@ -153,13 +166,13 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics, effectiveFeatureLevel, }); if (mIncludeEssl1 - && featureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0 + && featureLevel == backend::FeatureLevel::FEATURE_LEVEL_0 && shaderModel == ShaderModel::MOBILE) { mCodeGenPermutations.push_back({ shaderModel, TargetApi::OPENGL, glTargetLanguage, - filament::backend::FeatureLevel::FEATURE_LEVEL_0 + backend::FeatureLevel::FEATURE_LEVEL_0 }); } } @@ -200,12 +213,12 @@ MaterialBuilder::MaterialBuilder() : mMaterialName("Unnamed") { MaterialBuilder::~MaterialBuilder() = default; void MaterialBuilderBase::init() { - materialBuilderClients++; + ++materialBuilderClients; GLSLTools::init(); } void MaterialBuilderBase::shutdown() { - materialBuilderClients--; + --materialBuilderClients; GLSLTools::shutdown(); } @@ -214,12 +227,12 @@ MaterialBuilder& MaterialBuilder::name(const char* name) noexcept { return *this; } -MaterialBuilder& MaterialBuilder::fileName(const char* fileName) noexcept { - mFileName = CString(fileName); +MaterialBuilder& MaterialBuilder::fileName(const char* name) noexcept { + mFileName = CString(name); return *this; } -MaterialBuilder& MaterialBuilder::material(const char* code, size_t line) noexcept { +MaterialBuilder& MaterialBuilder::material(const char* code, size_t const line) noexcept { mMaterialFragmentCode.setUnresolved(CString(code)); mMaterialFragmentCode.setLineOffset(line); return *this; @@ -230,18 +243,18 @@ MaterialBuilder& MaterialBuilder::includeCallback(IncludeCallback callback) noex return *this; } -MaterialBuilder& MaterialBuilder::materialVertex(const char* code, size_t line) noexcept { +MaterialBuilder& MaterialBuilder::materialVertex(const char* code, size_t const line) noexcept { mMaterialVertexCode.setUnresolved(CString(code)); mMaterialVertexCode.setLineOffset(line); return *this; } -MaterialBuilder& MaterialBuilder::shading(Shading shading) noexcept { +MaterialBuilder& MaterialBuilder::shading(Shading const shading) noexcept { mShading = shading; return *this; } -MaterialBuilder& MaterialBuilder::interpolation(Interpolation interpolation) noexcept { +MaterialBuilder& MaterialBuilder::interpolation(Interpolation const interpolation) noexcept { mInterpolation = interpolation; return *this; } @@ -253,7 +266,7 @@ MaterialBuilder& MaterialBuilder::variable(Variable v, const char* name) noexcep case Variable::CUSTOM2: case Variable::CUSTOM3: case Variable::CUSTOM4: - assert(size_t(v) < MATERIAL_VARIABLES_COUNT); + assert_invariant(size_t(v) < MATERIAL_VARIABLES_COUNT); mVariables[size_t(v)] = { CString(name), Precision::DEFAULT, false }; break; } @@ -261,14 +274,14 @@ MaterialBuilder& MaterialBuilder::variable(Variable v, const char* name) noexcep } MaterialBuilder& MaterialBuilder::variable(Variable v, - const char* name, ParameterPrecision precision) noexcept { + const char* name, ParameterPrecision const precision) noexcept { switch (v) { case Variable::CUSTOM0: case Variable::CUSTOM1: case Variable::CUSTOM2: case Variable::CUSTOM3: case Variable::CUSTOM4: - assert(size_t(v) < MATERIAL_VARIABLES_COUNT); + assert_invariant(size_t(v) < MATERIAL_VARIABLES_COUNT); mVariables[size_t(v)] = { CString(name), precision, true }; break; } @@ -282,8 +295,8 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, size_t size, Unifo return *this; } -MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType type, - ParameterPrecision precision) noexcept { +MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType const type, + ParameterPrecision const precision) noexcept { return parameter(name, 1, type, precision); } @@ -303,9 +316,9 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType sample } template -MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType type, T defaultValue) { +MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const type, T defaultValue) { auto result = std::find_if(mConstants.begin(), mConstants.end(), [name](const Constant& c) { - return c.name == utils::CString(name); + return c.name == CString(name); }); FILAMENT_CHECK_POSTCONDITION(result == mConstants.end()) << "There is already a constant parameter present with the name " << name << "."; @@ -313,7 +326,7 @@ MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType type, .name = CString(name), .type = type, }; - auto toString = [](ConstantType t) { + auto toString = [](ConstantType const t) { switch (t) { case ConstantType::INT: return "INT"; case ConstantType::FLOAT: return "FLOAT"; @@ -352,7 +365,7 @@ template MaterialBuilder& MaterialBuilder::constant( MaterialBuilder& MaterialBuilder::buffer(BufferInterfaceBlock bib) noexcept { FILAMENT_CHECK_POSTCONDITION(mBuffers.size() < MAX_BUFFERS_COUNT) << "Too many buffers"; - mBuffers.emplace_back(std::make_unique(std::move(bib))); + mBuffers.emplace_back(std::make_unique(std::move(bib))); return *this; } @@ -366,32 +379,32 @@ MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, SamplerFormat return *this; } -MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, SamplerFormat format, +MaterialBuilder& MaterialBuilder::subpass(SubpassType const subpassType, SamplerFormat const format, const char* name) noexcept { return subpass(subpassType, format, ParameterPrecision::DEFAULT, name); } -MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, ParameterPrecision precision, +MaterialBuilder& MaterialBuilder::subpass(SubpassType const subpassType, ParameterPrecision const precision, const char* name) noexcept { return subpass(subpassType, SamplerFormat::FLOAT, precision, name); } -MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, const char* name) noexcept { +MaterialBuilder& MaterialBuilder::subpass(SubpassType const subpassType, const char* name) noexcept { return subpass(subpassType, SamplerFormat::FLOAT, ParameterPrecision::DEFAULT, name); } -MaterialBuilder& MaterialBuilder::require(VertexAttribute attribute) noexcept { +MaterialBuilder& MaterialBuilder::require(VertexAttribute const attribute) noexcept { mRequiredAttributes.set(attribute); return *this; } -MaterialBuilder& MaterialBuilder::groupSize(filament::math::uint3 groupSize) noexcept { +MaterialBuilder& MaterialBuilder::groupSize(math::uint3 const groupSize) noexcept { mGroupSize = groupSize; return *this; } MaterialBuilder& MaterialBuilder::materialDomain( - MaterialBuilder::MaterialDomain materialDomain) noexcept { + MaterialDomain const materialDomain) noexcept { mMaterialDomain = materialDomain; if (mMaterialDomain == MaterialDomain::COMPUTE) { // compute implies feature level 2 @@ -402,34 +415,34 @@ MaterialBuilder& MaterialBuilder::materialDomain( return *this; } -MaterialBuilder& MaterialBuilder::refractionMode(RefractionMode refraction) noexcept { +MaterialBuilder& MaterialBuilder::refractionMode(RefractionMode const refraction) noexcept { mRefractionMode = refraction; return *this; } -MaterialBuilder& MaterialBuilder::refractionType(RefractionType refractionType) noexcept { +MaterialBuilder& MaterialBuilder::refractionType(RefractionType const refractionType) noexcept { mRefractionType = refractionType; return *this; } -MaterialBuilder& MaterialBuilder::quality(ShaderQuality quality) noexcept { +MaterialBuilder& MaterialBuilder::quality(ShaderQuality const quality) noexcept { mShaderQuality = quality; return *this; } -MaterialBuilder& MaterialBuilder::featureLevel(FeatureLevel featureLevel) noexcept { +MaterialBuilder& MaterialBuilder::featureLevel(FeatureLevel const featureLevel) noexcept { mFeatureLevel = featureLevel; return *this; } -MaterialBuilder& MaterialBuilder::blending(BlendingMode blending) noexcept { +MaterialBuilder& MaterialBuilder::blending(BlendingMode const blending) noexcept { mBlendingMode = blending; return *this; } MaterialBuilder& MaterialBuilder::customBlendFunctions( - BlendFunction srcRGB, BlendFunction srcA, - BlendFunction dstRGB, BlendFunction dstA) noexcept { + BlendFunction const srcRGB, BlendFunction const srcA, + BlendFunction const dstRGB, BlendFunction const dstA) noexcept { mCustomBlendFunctions[0] = srcRGB; mCustomBlendFunctions[1] = srcA; mCustomBlendFunctions[2] = dstRGB; @@ -437,162 +450,162 @@ MaterialBuilder& MaterialBuilder::customBlendFunctions( return *this; } -MaterialBuilder& MaterialBuilder::postLightingBlending(BlendingMode blending) noexcept { +MaterialBuilder& MaterialBuilder::postLightingBlending(BlendingMode const blending) noexcept { mPostLightingBlendingMode = blending; return *this; } -MaterialBuilder& MaterialBuilder::vertexDomain(VertexDomain domain) noexcept { +MaterialBuilder& MaterialBuilder::vertexDomain(VertexDomain const domain) noexcept { mVertexDomain = domain; return *this; } -MaterialBuilder& MaterialBuilder::culling(CullingMode culling) noexcept { +MaterialBuilder& MaterialBuilder::culling(CullingMode const culling) noexcept { mCullingMode = culling; return *this; } -MaterialBuilder& MaterialBuilder::colorWrite(bool enable) noexcept { +MaterialBuilder& MaterialBuilder::colorWrite(bool const enable) noexcept { mColorWrite = enable; return *this; } -MaterialBuilder& MaterialBuilder::depthWrite(bool enable) noexcept { +MaterialBuilder& MaterialBuilder::depthWrite(bool const enable) noexcept { mDepthWrite = enable; mDepthWriteSet = true; return *this; } -MaterialBuilder& MaterialBuilder::depthCulling(bool enable) noexcept { +MaterialBuilder& MaterialBuilder::depthCulling(bool const enable) noexcept { mDepthTest = enable; return *this; } -MaterialBuilder& MaterialBuilder::instanced(bool enable) noexcept { +MaterialBuilder& MaterialBuilder::instanced(bool const enable) noexcept { mInstanced = enable; return *this; } -MaterialBuilder& MaterialBuilder::doubleSided(bool doubleSided) noexcept { +MaterialBuilder& MaterialBuilder::doubleSided(bool const doubleSided) noexcept { mDoubleSided = doubleSided; mDoubleSidedCapability = true; return *this; } -MaterialBuilder& MaterialBuilder::maskThreshold(float threshold) noexcept { +MaterialBuilder& MaterialBuilder::maskThreshold(float const threshold) noexcept { mMaskThreshold = threshold; return *this; } -MaterialBuilder& MaterialBuilder::alphaToCoverage(bool enable) noexcept { +MaterialBuilder& MaterialBuilder::alphaToCoverage(bool const enable) noexcept { mAlphaToCoverage = enable; mAlphaToCoverageSet = true; return *this; } -MaterialBuilder& MaterialBuilder::shadowMultiplier(bool shadowMultiplier) noexcept { +MaterialBuilder& MaterialBuilder::shadowMultiplier(bool const shadowMultiplier) noexcept { mShadowMultiplier = shadowMultiplier; return *this; } -MaterialBuilder& MaterialBuilder::transparentShadow(bool transparentShadow) noexcept { +MaterialBuilder& MaterialBuilder::transparentShadow(bool const transparentShadow) noexcept { mTransparentShadow = transparentShadow; return *this; } -MaterialBuilder& MaterialBuilder::specularAntiAliasing(bool specularAntiAliasing) noexcept { +MaterialBuilder& MaterialBuilder::specularAntiAliasing(bool const specularAntiAliasing) noexcept { mSpecularAntiAliasing = specularAntiAliasing; return *this; } -MaterialBuilder& MaterialBuilder::specularAntiAliasingVariance(float screenSpaceVariance) noexcept { +MaterialBuilder& MaterialBuilder::specularAntiAliasingVariance(float const screenSpaceVariance) noexcept { mSpecularAntiAliasingVariance = screenSpaceVariance; return *this; } -MaterialBuilder& MaterialBuilder::specularAntiAliasingThreshold(float threshold) noexcept { +MaterialBuilder& MaterialBuilder::specularAntiAliasingThreshold(float const threshold) noexcept { mSpecularAntiAliasingThreshold = threshold; return *this; } -MaterialBuilder& MaterialBuilder::clearCoatIorChange(bool clearCoatIorChange) noexcept { +MaterialBuilder& MaterialBuilder::clearCoatIorChange(bool const clearCoatIorChange) noexcept { mClearCoatIorChange = clearCoatIorChange; return *this; } -MaterialBuilder& MaterialBuilder::flipUV(bool flipUV) noexcept { +MaterialBuilder& MaterialBuilder::flipUV(bool const flipUV) noexcept { mFlipUV = flipUV; return *this; } -MaterialBuilder& MaterialBuilder::customSurfaceShading(bool customSurfaceShading) noexcept { +MaterialBuilder& MaterialBuilder::customSurfaceShading(bool const customSurfaceShading) noexcept { mCustomSurfaceShading = customSurfaceShading; return *this; } -MaterialBuilder& MaterialBuilder::multiBounceAmbientOcclusion(bool multiBounceAO) noexcept { +MaterialBuilder& MaterialBuilder::multiBounceAmbientOcclusion(bool const multiBounceAO) noexcept { mMultiBounceAO = multiBounceAO; mMultiBounceAOSet = true; return *this; } -MaterialBuilder& MaterialBuilder::specularAmbientOcclusion(SpecularAmbientOcclusion specularAO) noexcept { +MaterialBuilder& MaterialBuilder::specularAmbientOcclusion(SpecularAmbientOcclusion const specularAO) noexcept { mSpecularAO = specularAO; mSpecularAOSet = true; return *this; } -MaterialBuilder& MaterialBuilder::transparencyMode(TransparencyMode mode) noexcept { +MaterialBuilder& MaterialBuilder::transparencyMode(TransparencyMode const mode) noexcept { mTransparencyMode = mode; return *this; } -MaterialBuilder& MaterialBuilder::stereoscopicType(StereoscopicType stereoscopicType) noexcept { +MaterialBuilder& MaterialBuilder::stereoscopicType(StereoscopicType const stereoscopicType) noexcept { mStereoscopicType = stereoscopicType; return *this; } -MaterialBuilder& MaterialBuilder::stereoscopicEyeCount(uint8_t eyeCount) noexcept { +MaterialBuilder& MaterialBuilder::stereoscopicEyeCount(uint8_t const eyeCount) noexcept { mStereoscopicEyeCount = eyeCount; return *this; } -MaterialBuilder& MaterialBuilder::reflectionMode(ReflectionMode mode) noexcept { +MaterialBuilder& MaterialBuilder::reflectionMode(ReflectionMode const mode) noexcept { mReflectionMode = mode; return *this; } -MaterialBuilder& MaterialBuilder::platform(Platform platform) noexcept { +MaterialBuilder& MaterialBuilder::platform(Platform const platform) noexcept { mPlatform = platform; return *this; } -MaterialBuilder& MaterialBuilder::targetApi(TargetApi targetApi) noexcept { +MaterialBuilder& MaterialBuilder::targetApi(TargetApi const targetApi) noexcept { mTargetApi |= targetApi; return *this; } -MaterialBuilder& MaterialBuilder::optimization(Optimization optimization) noexcept { +MaterialBuilder& MaterialBuilder::optimization(Optimization const optimization) noexcept { mOptimization = optimization; return *this; } -MaterialBuilder& MaterialBuilder::printShaders(bool printShaders) noexcept { +MaterialBuilder& MaterialBuilder::printShaders(bool const printShaders) noexcept { mPrintShaders = printShaders; return *this; } -MaterialBuilder& MaterialBuilder::saveRawVariants(bool saveRawVariants) noexcept { +MaterialBuilder& MaterialBuilder::saveRawVariants(bool const saveRawVariants) noexcept { mSaveRawVariants = saveRawVariants; return *this; } -MaterialBuilder& MaterialBuilder::generateDebugInfo(bool generateDebugInfo) noexcept { +MaterialBuilder& MaterialBuilder::generateDebugInfo(bool const generateDebugInfo) noexcept { mGenerateDebugInfo = generateDebugInfo; return *this; } -MaterialBuilder& MaterialBuilder::variantFilter(UserVariantFilterMask variantFilter) noexcept { +MaterialBuilder& MaterialBuilder::variantFilter(UserVariantFilterMask const variantFilter) noexcept { mVariantFilter = variantFilter; return *this; } @@ -602,7 +615,7 @@ MaterialBuilder& MaterialBuilder::shaderDefine(const char* name, const char* val return *this; } -bool MaterialBuilder::hasSamplerType(SamplerType samplerType) const noexcept { +bool MaterialBuilder::hasSamplerType(SamplerType const samplerType) const noexcept { for (size_t i = 0, c = mParameterCount; i < c; i++) { auto const& param = mParameters[i]; if (param.isSampler() && param.samplerType == samplerType) { @@ -613,7 +626,7 @@ bool MaterialBuilder::hasSamplerType(SamplerType samplerType) const noexcept { } void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { - MaterialBuilderBase::prepare(mEnableFramebufferFetch, mFeatureLevel); + prepare(mEnableFramebufferFetch, mFeatureLevel); // Build the per-material sampler block and uniform block. SamplerInterfaceBlock::Builder sbb; @@ -643,10 +656,8 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { assert_invariant(param.isSubpass()); // For now, we only support a single subpass for attachment 0. // Subpasses belong to the "MaterialParams" block. - const uint8_t attachmentIndex = 0; - const uint8_t binding = 0; info.subpass = { CString("MaterialParams"), param.name, param.subpassType, - param.format, param.precision, attachmentIndex, binding }; + param.format, param.precision, 0, 0 }; } for (auto const& buffer : mBuffers) { @@ -668,9 +679,9 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { ibb.add({{ "_doubleSided", 0, UniformType::BOOL, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }}); } - mRequiredAttributes.set(VertexAttribute::POSITION); + mRequiredAttributes.set(POSITION); if (mShading != Shading::UNLIT || mShadowMultiplier) { - mRequiredAttributes.set(VertexAttribute::TANGENTS); + mRequiredAttributes.set(TANGENTS); } info.sib = sbb.name("MaterialParams").build(); @@ -714,7 +725,7 @@ void MaterialBuilder::initPushConstants() noexcept { mPushConstants.reserve(PUSH_CONSTANTS.size()); mPushConstants.resize(PUSH_CONSTANTS.size()); std::transform(PUSH_CONSTANTS.cbegin(), PUSH_CONSTANTS.cend(), mPushConstants.begin(), - [](filament::MaterialPushConstant const& inConstant) -> PushConstant { + [](MaterialPushConstant const& inConstant) -> PushConstant { return { .name = inConstant.name, .type = inConstant.type, @@ -723,11 +734,11 @@ void MaterialBuilder::initPushConstants() noexcept { }); } -bool MaterialBuilder::findProperties(backend::ShaderStage type, - MaterialBuilder::PropertyList& allProperties, +bool MaterialBuilder::findProperties(backend::ShaderStage const type, + PropertyList const& allProperties, CodeGenParams const& semanticCodeGenParams) noexcept { - GLSLTools glslTools; - std::string shaderCodeAllProperties = peek(type, semanticCodeGenParams, allProperties); + GLSLTools const glslTools; + std::string const shaderCodeAllProperties = peek(type, semanticCodeGenParams, allProperties); // Populate mProperties with the properties set in the shader. if (!glslTools.findProperties(type, shaderCodeAllProperties, mProperties, semanticCodeGenParams.targetApi, @@ -751,7 +762,7 @@ bool MaterialBuilder::findAllProperties(CodeGenParams const& semanticCodeGenPara // Some fields in MaterialInputs only exist if the property is set (e.g: normal, subsurface // for cloth shading model). Give our shader all properties. This will enable us to parse and // static code analyse the AST. - MaterialBuilder::PropertyList allProperties; + PropertyList allProperties; std::fill_n(allProperties, MATERIAL_PROPERTIES_COUNT, true); if (!findProperties(ShaderStage::FRAGMENT, allProperties, semanticCodeGenParams)) { return false; @@ -778,7 +789,7 @@ bool MaterialBuilder::runSemanticAnalysis(MaterialInfo* inOutInfo, bool success = false; std::string shaderCode; ShaderModel const model = semanticCodeGenParams.shaderModel; - if (mMaterialDomain == filament::MaterialDomain::COMPUTE) { + if (mMaterialDomain == MaterialDomain::COMPUTE) { shaderCode = peek(ShaderStage::COMPUTE, semanticCodeGenParams, mProperties); success = GLSLTools::analyzeComputeShader(shaderCode, model, targetApi, targetLanguage); @@ -788,7 +799,7 @@ bool MaterialBuilder::runSemanticAnalysis(MaterialInfo* inOutInfo, targetApi, targetLanguage); if (success) { shaderCode = peek(ShaderStage::FRAGMENT, semanticCodeGenParams, mProperties); - auto result = GLSLTools::analyzeFragmentShader(shaderCode, model, mMaterialDomain, + auto const result = GLSLTools::analyzeFragmentShader(shaderCode, model, mMaterialDomain, targetApi, targetLanguage, mCustomSurfaceShading); success = result.has_value(); if (success) { @@ -805,7 +816,7 @@ bool MaterialBuilder::runSemanticAnalysis(MaterialInfo* inOutInfo, bool MaterialBuilder::ShaderCode::resolveIncludes(IncludeCallback callback, const CString& fileName) noexcept { if (!mCode.empty()) { - ResolveOptions options { + ResolveOptions const options { .insertLineDirectives = true, .insertLineDirectiveCheck = true }; @@ -815,7 +826,7 @@ bool MaterialBuilder::ShaderCode::resolveIncludes(IncludeCallback callback, .lineNumberOffset = getLineOffset(), .name = CString("") }; - if (!::filamat::resolveIncludes(source, std::move(callback), options)) { + if (!filamat::resolveIncludes(source, std::move(callback), options)) { return false; } mCode = source.text; @@ -825,14 +836,14 @@ bool MaterialBuilder::ShaderCode::resolveIncludes(IncludeCallback callback, return true; } -static void showErrorMessage(const char* materialName, filament::Variant variant, - MaterialBuilder::TargetApi targetApi, backend::ShaderStage shaderType, - MaterialBuilder::FeatureLevel featureLevel, +static void showErrorMessage(const char* materialName, filament::Variant const variant, + MaterialBuilder::TargetApi const targetApi, backend::ShaderStage const shaderType, + MaterialBuilder::FeatureLevel const featureLevel, const std::string& shaderCode) { using ShaderStage = backend::ShaderStage; using TargetApi = MaterialBuilder::TargetApi; - const char* targetApiString; + const char* targetApiString = "unknown"; switch (targetApi) { case TargetApi::OPENGL: targetApiString = (featureLevel == MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0) @@ -848,11 +859,11 @@ static void showErrorMessage(const char* materialName, filament::Variant variant targetApiString = "WebGPU.\n"; break; case TargetApi::ALL: - assert(0); // Unreachable. + assert_invariant(false); // Unreachable. break; } - const char* shaderStageString; + const char* shaderStageString = "unknown"; switch (shaderType) { case ShaderStage::VERTEX: shaderStageString = "Vertex Shader\n"; @@ -900,7 +911,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector(ChunkType::MaterialHasCustomDepthShader, needsStandardDepthProgram()); + container.emplace(MaterialHasCustomDepthShader, needsStandardDepthProgram()); std::atomic_bool cancelJobs(false); bool firstJob = true; @@ -962,32 +973,33 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector const lock(entriesLock); + std::unique_lock const lock(entriesLock); // below we rely on casting ShaderStage to uint8_t - static_assert(sizeof(filament::backend::ShaderStage) == 1); + static_assert(sizeof(backend::ShaderStage) == 1); switch (targetApi) { case TargetApi::WEBGPU: - assert(!spirv.empty()); - assert(wgsl.length() > 0); + assert_invariant(!spirv.empty()); + assert_invariant(!wgsl.empty()); wgslEntry.stage = v.stage; wgslEntry.shader = wgsl; wgslEntries.push_back(wgslEntry); @@ -1071,8 +1083,8 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector d(reinterpret_cast(spirv.data()), + assert_invariant(!spirv.empty()); + std::vector d(reinterpret_cast(spirv.data()), reinterpret_cast(spirv.data() + spirv.size())); spirvEntry.stage = v.stage; spirvEntry.data = std::move(d); @@ -1080,8 +1092,8 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector 0); + assert_invariant(!spirv.empty()); + assert_invariant(!msl.empty()); metalEntry.stage = v.stage; metalEntry.shader = msl; metalEntries.push_back(metalEntry); @@ -1129,7 +1141,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector spirv = std::move(s.data); + std::vector const spirv{ std::move(s.data) }; s.dictionaryIndex = spirvDictionary.addBlob(spirv); } for (const auto& s : metalEntries) { @@ -1140,38 +1152,38 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector( - std::move(textDictionary), ChunkType::DictionaryText); + const auto& dictionaryChunk = container.push( + std::move(textDictionary), DictionaryText); // Emit GLSL chunk (MaterialTextChunk). if (!glslEntries.empty()) { container.push(std::move(glslEntries), - dictionaryChunk.getDictionary(), ChunkType::MaterialGlsl); + dictionaryChunk.getDictionary(), MaterialGlsl); } // Emit ESSL1 chunk (MaterialTextChunk). if (!essl1Entries.empty()) { container.push(std::move(essl1Entries), - dictionaryChunk.getDictionary(), ChunkType::MaterialEssl1); + dictionaryChunk.getDictionary(), MaterialEssl1); } // Emit SPIRV chunks (SpirvDictionaryReader and MaterialBinaryChunk). if (!spirvEntries.empty()) { const bool stripInfo = !mGenerateDebugInfo; - container.push(std::move(spirvDictionary), stripInfo); - container.push(std::move(spirvEntries), ChunkType::MaterialSpirv); + container.push(std::move(spirvDictionary), stripInfo); + container.push(std::move(spirvEntries), MaterialSpirv); } // Emit Metal chunk (MaterialTextChunk). if (!metalEntries.empty()) { container.push(std::move(metalEntries), - dictionaryChunk.getDictionary(), ChunkType::MaterialMetal); + dictionaryChunk.getDictionary(), MaterialMetal); } // Emit WGSL chunk (MaterialTextChunk). if (!wgslEntries.empty()) { container.push(std::move(wgslEntries), - dictionaryChunk.getDictionary(), ChunkType::MaterialWgsl); + dictionaryChunk.getDictionary(), MaterialWgsl); } return true; @@ -1213,7 +1225,7 @@ MaterialBuilder& MaterialBuilder::output(VariableQualifier qualifier, OutputTarg FILAMENT_CHECK_PRECONDITION(depthOutputCount <= MAX_DEPTH_OUTPUT) << "A maximum of " << MAX_DEPTH_OUTPUT << " DEPTH output is allowed."; - assert(mOutputs.size() <= MAX_COLOR_OUTPUT + MAX_DEPTH_OUTPUT); + assert_invariant(mOutputs.size() <= MAX_COLOR_OUTPUT + MAX_DEPTH_OUTPUT); return *this; } @@ -1225,7 +1237,7 @@ MaterialBuilder& MaterialBuilder::enableFramebufferFetch() noexcept { return *this; } -MaterialBuilder& MaterialBuilder::vertexDomainDeviceJittered(bool enabled) noexcept { +MaterialBuilder& MaterialBuilder::vertexDomainDeviceJittered(bool const enabled) noexcept { mVertexDomainDeviceJittered = enabled; return *this; } @@ -1235,8 +1247,7 @@ MaterialBuilder& MaterialBuilder::useLegacyMorphing() noexcept { return *this; } -Package MaterialBuilder::build(JobSystem& jobSystem) noexcept { - bool success; +Package MaterialBuilder::build(JobSystem& jobSystem) { if (materialBuilderClients == 0) { slog.e << "Error: MaterialBuilder::init() must be called before build()." << io::endl; // Return an empty package to signal a failure to build the material. @@ -1257,7 +1268,7 @@ error: } if (mMaterialDomain == MaterialDomain::SURFACE) { - if (mRequiredAttributes[VertexAttribute::COLOR] && + if (mRequiredAttributes[COLOR] && !mVariables[int(Variable::CUSTOM4)].name.empty()) { // both the color attribute and the custom4 variable are present, that's not supported slog.e << "Error: when the 'color' attribute is required 'Variable::CUSTOM4' is not supported." << io::endl; @@ -1310,7 +1321,7 @@ error: } // adjust variant-filter for feature level *before* we start writing into the container - if (mFeatureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0) { + if (mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { // at feature level 0, many variants are not supported mVariantFilter |= uint32_t(UserVariantFilterBit::DIRECTIONAL_LIGHTING); mVariantFilter |= uint32_t(UserVariantFilterBit::DYNAMIC_LIGHTING); @@ -1343,7 +1354,7 @@ error: break; } - success = generateShaders(jobSystem, variants, container, info); + bool const success = generateShaders(jobSystem, variants, container, info); if (!success) { // Return an empty package to signal a failure to build the material. goto error; @@ -1358,7 +1369,7 @@ error: } using namespace backend; -static const char* to_string(ShaderStageFlags stageFlags) noexcept { +static const char* to_string(ShaderStageFlags const stageFlags) noexcept { switch (stageFlags) { case ShaderStageFlags::NONE: return "{ }"; case ShaderStageFlags::VERTEX: return "{ vertex }"; @@ -1404,7 +1415,7 @@ bool MaterialBuilder::checkMaterialLevelFeatures(MaterialInfo const& info) const break; } - auto const maxTextureCount = backend::FEATURE_LEVEL_CAPS[1].MAX_FRAGMENT_SAMPLER_COUNT; + constexpr auto maxTextureCount = FEATURE_LEVEL_CAPS[1].MAX_FRAGMENT_SAMPLER_COUNT; // count how many samplers filament uses based on the material properties // note: currently SSAO is not used with unlit, but we want to keep that possibility. @@ -1416,7 +1427,7 @@ bool MaterialBuilder::checkMaterialLevelFeatures(MaterialInfo const& info) const info.refractionMode == RefractionMode::SCREEN_SPACE) { textureUsedByFilamentCount += 1; // ssr } - if (mVariantFilter & (uint32_t)UserVariantFilterBit::FOG) { + if (mVariantFilter & uint32_t(UserVariantFilterBit::FOG)) { textureUsedByFilamentCount -= 1; // fog texture } @@ -1477,10 +1488,10 @@ bool MaterialBuilder::needsStandardDepthProgram() const noexcept { mBlendingMode == BlendingMode::FADE)); } -std::string MaterialBuilder::peek(backend::ShaderStage stage, +std::string MaterialBuilder::peek(backend::ShaderStage const stage, const CodeGenParams& params, const PropertyList& properties) noexcept { - ShaderGenerator sg(properties, mVariables, mOutputs, mDefines, mConstants, mPushConstants, + ShaderGenerator const sg(properties, mVariables, mOutputs, mDefines, mConstants, mPushConstants, mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(), mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(), mMaterialDomain); @@ -1489,19 +1500,20 @@ std::string MaterialBuilder::peek(backend::ShaderStage stage, prepareToBuild(info); switch (stage) { - case backend::ShaderStage::VERTEX: + case ShaderStage::VERTEX: return sg.createSurfaceVertexProgram( params.shaderModel, params.targetApi, params.targetLanguage, params.featureLevel, info, {}, mInterpolation, mVertexDomain); - case backend::ShaderStage::FRAGMENT: + case ShaderStage::FRAGMENT: return sg.createSurfaceFragmentProgram( params.shaderModel, params.targetApi, params.targetLanguage, params.featureLevel, info, {}, mInterpolation, mVariantFilter); - case backend::ShaderStage::COMPUTE: + case ShaderStage::COMPUTE: return sg.createSurfaceComputeProgram( params.shaderModel, params.targetApi, params.targetLanguage, params.featureLevel, info); } + return {}; } static Program::UniformInfo extractUniforms(BufferInterfaceBlock const& uib) noexcept { @@ -1532,22 +1544,22 @@ static Program::UniformInfo extractUniforms(BufferInterfaceBlock const& uib) noe } void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& info) const noexcept { - container.emplace(ChunkType::MaterialVersion, MATERIAL_VERSION); - container.emplace(ChunkType::MaterialFeatureLevel, (uint8_t)info.featureLevel); - container.emplace(ChunkType::MaterialName, mMaterialName.c_str_safe()); - container.emplace(ChunkType::MaterialShaderModels, mShaderModels.getValue()); + container.emplace(MaterialVersion, MATERIAL_VERSION); + container.emplace(MaterialFeatureLevel, (uint8_t)info.featureLevel); + container.emplace(MaterialName, mMaterialName.c_str_safe()); + container.emplace(MaterialShaderModels, mShaderModels.getValue()); container.emplace(ChunkType::MaterialDomain, static_cast(mMaterialDomain)); // if that ever needed to change, this would require a material version bump static_assert(sizeof(uint32_t) >= sizeof(UserVariantFilterMask)); - container.emplace(ChunkType::MaterialVariantFilterMask, mVariantFilter); + container.emplace(MaterialVariantFilterMask, mVariantFilter); using namespace filament; if (info.featureLevel == FeatureLevel::FEATURE_LEVEL_0) { // FIXME: don't hardcode this - FixedCapacityVector> list({ + FixedCapacityVector> list({ { 0, "FrameUniforms", extractUniforms(UibGenerator::getPerViewUib()) }, { 1, "ObjectUniforms", extractUniforms(UibGenerator::getPerRenderableUib()) }, { 2, "MaterialParams", extractUniforms(info.uib) }, @@ -1582,12 +1594,12 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& container.push(std::move(list)); - using Container = utils::FixedCapacityVector>; + using Container = FixedCapacityVector>; auto attributes = Container::with_capacity(sAttributeDatabase.size()); for (auto const& attribute: sAttributeDatabase) { std::string name("mesh_"); name.append(attribute.name); - attributes.emplace_back(utils::CString{ name.data(), name.size() }, attribute.location); + attributes.emplace_back(CString{ name.data(), name.size() }, attribute.location); } container.push(std::move(attributes)); } @@ -1599,7 +1611,7 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& container.push(info.sib); - backend::DescriptorSetLayout const perViewDescriptorSetLayout = + DescriptorSetLayout const perViewDescriptorSetLayout = descriptor_sets::getPerViewDescriptorSetLayout( mMaterialDomain, mVariantFilter, info.isLit || info.hasShadowMultiplier, info.reflectionMode, info.refractionMode); @@ -1609,18 +1621,18 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& container.push(info.sib, perViewDescriptorSetLayout); // User constant parameters - utils::FixedCapacityVector constantsEntry(mConstants.size()); + FixedCapacityVector constantsEntry(mConstants.size()); std::transform(mConstants.begin(), mConstants.end(), constantsEntry.begin(), [](Constant const& c) { return MaterialConstant(c.name.c_str(), c.type); }); container.push(std::move(constantsEntry)); - utils::FixedCapacityVector pushConstantsEntry(mPushConstants.size()); + FixedCapacityVector pushConstantsEntry(mPushConstants.size()); std::transform(mPushConstants.begin(), mPushConstants.end(), pushConstantsEntry.begin(), [](PushConstant const& c) { return MaterialPushConstant(c.name.c_str(), c.type, c.stage); }); container.push( - utils::CString(PUSH_CONSTANT_STRUCT_VAR_NAME), std::move(pushConstantsEntry)); + CString(PUSH_CONSTANT_STRUCT_VAR_NAME), std::move(pushConstantsEntry)); // TODO: should we write the SSBO info? this would only be needed if we wanted to provide // an interface to set [get?] values in the buffer. But we can do that easily @@ -1630,9 +1642,9 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& // User Subpass container.push(info.subpass); - container.emplace(ChunkType::MaterialDoubleSidedSet, mDoubleSidedCapability); - container.emplace(ChunkType::MaterialDoubleSided, mDoubleSided); - container.emplace(ChunkType::MaterialBlendingMode, + container.emplace(MaterialDoubleSidedSet, mDoubleSidedCapability); + container.emplace(MaterialDoubleSided, mDoubleSided); + container.emplace(MaterialBlendingMode, static_cast(mBlendingMode)); if (mBlendingMode == BlendingMode::CUSTOM) { @@ -1641,21 +1653,21 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& (uint32_t(mCustomBlendFunctions[1]) << 16) | (uint32_t(mCustomBlendFunctions[2]) << 8) | (uint32_t(mCustomBlendFunctions[3]) << 0); - container.emplace< uint32_t >(ChunkType::MaterialBlendFunction, blendFunctions); + container.emplace< uint32_t >(MaterialBlendFunction, blendFunctions); } - container.emplace(ChunkType::MaterialTransparencyMode, + container.emplace(MaterialTransparencyMode, static_cast(mTransparencyMode)); - container.emplace(ChunkType::MaterialReflectionMode, + container.emplace(MaterialReflectionMode, static_cast(mReflectionMode)); - container.emplace(ChunkType::MaterialColorWrite, mColorWrite); - container.emplace(ChunkType::MaterialDepthWriteSet, mDepthWriteSet); - container.emplace(ChunkType::MaterialDepthWrite, mDepthWrite); - container.emplace(ChunkType::MaterialDepthTest, mDepthTest); - container.emplace(ChunkType::MaterialInstanced, mInstanced); - container.emplace(ChunkType::MaterialAlphaToCoverageSet, mAlphaToCoverageSet); - container.emplace(ChunkType::MaterialAlphaToCoverage, mAlphaToCoverage); - container.emplace(ChunkType::MaterialCullingMode, + container.emplace(MaterialColorWrite, mColorWrite); + container.emplace(MaterialDepthWriteSet, mDepthWriteSet); + container.emplace(MaterialDepthWrite, mDepthWrite); + container.emplace(MaterialDepthTest, mDepthTest); + container.emplace(MaterialInstanced, mInstanced); + container.emplace(MaterialAlphaToCoverageSet, mAlphaToCoverageSet); + container.emplace(MaterialAlphaToCoverage, mAlphaToCoverage); + container.emplace(MaterialCullingMode, static_cast(mCullingMode)); uint64_t properties = 0; @@ -1665,56 +1677,56 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& properties |= uint64_t(1u) << i; } } - container.emplace(ChunkType::MaterialProperties, properties); - container.emplace(ChunkType::MaterialStereoscopicType, static_cast(mStereoscopicType)); + container.emplace(MaterialProperties, properties); + container.emplace(MaterialStereoscopicType, static_cast(mStereoscopicType)); } // create a unique material id auto const& vert = mMaterialVertexCode.getResolved(); auto const& frag = mMaterialFragmentCode.getResolved(); std::hash const hasher; - size_t const materialId = utils::hash::combine( + size_t const materialId = hash::combine( MATERIAL_VERSION, - utils::hash::combine( + hash::combine( hasher({ vert.data(), vert.size() }), hasher({ frag.data(), frag.size() }))); - container.emplace(ChunkType::MaterialCacheId, materialId); + container.emplace(MaterialCacheId, materialId); } void MaterialBuilder::writeSurfaceChunks(ChunkContainer& container) const noexcept { if (mBlendingMode == BlendingMode::MASKED) { - container.emplace(ChunkType::MaterialMaskThreshold, mMaskThreshold); + container.emplace(MaterialMaskThreshold, mMaskThreshold); } - container.emplace(ChunkType::MaterialShading, static_cast(mShading)); + container.emplace(MaterialShading, static_cast(mShading)); if (mShading == Shading::UNLIT) { - container.emplace(ChunkType::MaterialShadowMultiplier, mShadowMultiplier); + container.emplace(MaterialShadowMultiplier, mShadowMultiplier); } - container.emplace(ChunkType::MaterialRefraction, static_cast(mRefractionMode)); - container.emplace(ChunkType::MaterialRefractionType, + container.emplace(MaterialRefraction, static_cast(mRefractionMode)); + container.emplace(MaterialRefractionType, static_cast(mRefractionType)); - container.emplace(ChunkType::MaterialClearCoatIorChange, mClearCoatIorChange); - container.emplace(ChunkType::MaterialRequiredAttributes, + container.emplace(MaterialClearCoatIorChange, mClearCoatIorChange); + container.emplace(MaterialRequiredAttributes, mRequiredAttributes.getValue()); - container.emplace(ChunkType::MaterialSpecularAntiAliasing, mSpecularAntiAliasing); - container.emplace(ChunkType::MaterialSpecularAntiAliasingVariance, + container.emplace(MaterialSpecularAntiAliasing, mSpecularAntiAliasing); + container.emplace(MaterialSpecularAntiAliasingVariance, mSpecularAntiAliasingVariance); - container.emplace(ChunkType::MaterialSpecularAntiAliasingThreshold, + container.emplace(MaterialSpecularAntiAliasingThreshold, mSpecularAntiAliasingThreshold); - container.emplace(ChunkType::MaterialVertexDomain, static_cast(mVertexDomain)); - container.emplace(ChunkType::MaterialInterpolation, + container.emplace(MaterialVertexDomain, static_cast(mVertexDomain)); + container.emplace(MaterialInterpolation, static_cast(mInterpolation)); } -MaterialBuilder& MaterialBuilder::noSamplerValidation(bool enabled) noexcept { +MaterialBuilder& MaterialBuilder::noSamplerValidation(bool const enabled) noexcept { mNoSamplerValidation = enabled; return *this; } -MaterialBuilder& MaterialBuilder::includeEssl1(bool enabled) noexcept { +MaterialBuilder& MaterialBuilder::includeEssl1(bool const enabled) noexcept { mIncludeEssl1 = enabled; return *this; } From 7e6839f5351ac09271c7b9cd6c5fdd06697f24b7 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 1 May 2025 16:06:48 -0700 Subject: [PATCH 15/34] remove dependence on per-view descset layout from filamat I've been going back and forth on whether we information about the per-view descriptor set layout should be written in the material file. In this change, we remove that dependency. By definition the "per view" descriptor-set layout should only depend on view parameters and obviously, all materials must be compatible. In practice, a material does affect the layout so a reconciliation needs to happen somewhere. It's easier to maintain to have all this logic in one place (in Filament) instead of split between material generation and filament. All this change really does is to remove the information about the per-view layout from the material file, and move it to Material in filament, where it is hardcoded (before it was hardcoded in filamat), but because both sides needed to match there was shared code. **Material recompilation needed** --- NEW_RELEASE_NOTES.md | 2 + filament/backend/include/backend/Program.h | 4 +- filament/src/MaterialParser.cpp | 112 ++++++++---------- filament/src/MaterialParser.h | 2 +- filament/src/details/Material.cpp | 34 +++++- libs/filamat/src/MaterialBuilder.cpp | 10 +- .../src/eiff/MaterialInterfaceBlockChunk.cpp | 75 +++--------- .../src/eiff/MaterialInterfaceBlockChunk.h | 64 +++++----- 8 files changed, 127 insertions(+), 176 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 4a1a9c7fa7..7028f80197 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -7,3 +7,5 @@ for next branch cut* header. appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut + +- materials: remove dependence on per-view descset layout from filamat. [⚠️ **New Material Version**] diff --git a/filament/backend/include/backend/Program.h b/filament/backend/include/backend/Program.h index f8e9d50eb8..d19d5b1ba8 100644 --- a/filament/backend/include/backend/Program.h +++ b/filament/backend/include/backend/Program.h @@ -44,8 +44,8 @@ public: struct Descriptor { utils::CString name; - backend::DescriptorType type; - backend::descriptor_binding_t binding; + DescriptorType type; + descriptor_binding_t binding; }; struct SpecializationConstant { diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp index c217f6c911..e3800804c7 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -648,44 +648,32 @@ bool ChunkAttributeInfo::unflatten(Unflattener& unflattener, bool ChunkDescriptorBindingsInfo::unflatten(Unflattener& unflattener, MaterialParser::DescriptorBindingsContainer* container) { - uint8_t setCount; - if (!unflattener.read(&setCount)) { + static_assert(sizeof(DescriptorSetBindingPoints) == sizeof(uint8_t)); + + uint8_t descriptorCount; + if (!unflattener.read(&descriptorCount)) { return false; } - for (size_t j = 0; j < setCount; j++) { - static_assert(sizeof(DescriptorSetBindingPoints) == sizeof(uint8_t)); - - DescriptorSetBindingPoints set; - if (!unflattener.read(reinterpret_cast(&set))) { + auto& descriptors = (*container)[+DescriptorSetBindingPoints::PER_MATERIAL]; + descriptors.reserve(descriptorCount); + for (size_t i = 0; i < descriptorCount; i++) { + CString name; + if (!unflattener.read(&name)) { return false; } - - uint8_t descriptorCount; - if (!unflattener.read(&descriptorCount)) { + uint8_t type; + if (!unflattener.read(&type)) { return false; } - - auto& descriptors = (*container)[+set]; - descriptors.reserve(descriptorCount); - for (size_t i = 0; i < descriptorCount; i++) { - CString name; - if (!unflattener.read(&name)) { - return false; - } - uint8_t type; - if (!unflattener.read(&type)) { - return false; - } - uint8_t binding; - if (!unflattener.read(&binding)) { - return false; - } - descriptors.push_back({ - std::move(name), - DescriptorType(type), - descriptor_binding_t(binding)}); + uint8_t binding; + if (!unflattener.read(&binding)) { + return false; } + descriptors.push_back({ + std::move(name), + DescriptorType(type), + descriptor_binding_t(binding)}); } return true; @@ -693,42 +681,40 @@ bool ChunkDescriptorBindingsInfo::unflatten(Unflattener& unflattener, bool ChunkDescriptorSetLayoutInfo::unflatten(Unflattener& unflattener, MaterialParser::DescriptorSetLayoutContainer* container) { - for (size_t j = 0; j < 2; j++) { - uint8_t descriptorCount; - if (!unflattener.read(&descriptorCount)) { + uint8_t descriptorCount; + if (!unflattener.read(&descriptorCount)) { + return false; + } + auto& descriptors = container->bindings; + descriptors.reserve(descriptorCount); + for (size_t i = 0; i < descriptorCount; i++) { + uint8_t type; + if (!unflattener.read(&type)) { return false; } - auto& descriptors = (*container)[j].bindings; - descriptors.reserve(descriptorCount); - for (size_t i = 0; i < descriptorCount; i++) { - uint8_t type; - if (!unflattener.read(&type)) { - return false; - } - uint8_t stageFlags; - if (!unflattener.read(&stageFlags)) { - return false; - } - uint8_t binding; - if (!unflattener.read(&binding)) { - return false; - } - uint8_t flags; - if (!unflattener.read(&flags)) { - return false; - } - uint16_t count; - if (!unflattener.read(&count)) { - return false; - } - descriptors.push_back({ - DescriptorType(type), - ShaderStageFlags(stageFlags), - descriptor_binding_t(binding), - DescriptorFlags(flags), - count, - }); + uint8_t stageFlags; + if (!unflattener.read(&stageFlags)) { + return false; } + uint8_t binding; + if (!unflattener.read(&binding)) { + return false; + } + uint8_t flags; + if (!unflattener.read(&flags)) { + return false; + } + uint16_t count; + if (!unflattener.read(&count)) { + return false; + } + descriptors.push_back({ + DescriptorType(type), + ShaderStageFlags(stageFlags), + descriptor_binding_t(binding), + DescriptorFlags(flags), + count, + }); } return true; } diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index 761698d526..9787d64ab5 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -93,7 +93,7 @@ public: using DescriptorBindingsContainer = backend::Program::DescriptorSetInfo; bool getDescriptorBindings(DescriptorBindingsContainer* container) const noexcept; - using DescriptorSetLayoutContainer = std::array; + using DescriptorSetLayoutContainer = backend::DescriptorSetLayout; bool getDescriptorSetLayout(DescriptorSetLayoutContainer* container) const noexcept; bool getDepthWriteSet(bool* value) const noexcept; diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 421ab9f637..51a28bb603 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -609,9 +609,12 @@ Program FMaterial::getProgramWithVariants( program.attributes(mAttributeInfo); } - program.descriptorBindings(0, mProgramDescriptorBindings[0]); - program.descriptorBindings(1, mProgramDescriptorBindings[1]); - program.descriptorBindings(2, mProgramDescriptorBindings[2]); + program.descriptorBindings(+DescriptorSetBindingPoints::PER_VIEW, + mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_VIEW]); + program.descriptorBindings(+DescriptorSetBindingPoints::PER_RENDERABLE, + mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]); + program.descriptorBindings(+DescriptorSetBindingPoints::PER_MATERIAL, + mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_MATERIAL]); program.specializationConstants(mSpecializationConstants); program.pushConstants(ShaderStage::VERTEX, mPushConstants[uint8_t(ShaderStage::VERTEX)]); @@ -1143,17 +1146,36 @@ void FMaterial::processDescriptorSets(FEngine& engine, MaterialParser const* con success = parser->getDescriptorBindings(&mProgramDescriptorBindings); assert_invariant(success); - std::array descriptorSetLayout; + backend::DescriptorSetLayout descriptorSetLayout; success = parser->getDescriptorSetLayout(&descriptorSetLayout); assert_invariant(success); + // get the PER_VIEW descriptor binding info + auto const& perViewDescriptorSetLayout = descriptor_sets::getPerViewDescriptorSetLayout( + mMaterialDomain, mVariantFilterMask, + mIsVariantLit || mHasShadowMultiplier, mReflectionMode, mRefractionMode); + + // get the PER_RENDERABLE and PER_VIEW descriptor binding info + for (auto&& [bindingPoint, descriptorSetLayout] : { + std::pair{ DescriptorSetBindingPoints::PER_RENDERABLE, + descriptor_sets::getPerRenderableLayout() }, + std::pair{ DescriptorSetBindingPoints::PER_VIEW, + perViewDescriptorSetLayout }}) { + Program::DescriptorBindingsInfo& descriptors = mProgramDescriptorBindings[+bindingPoint]; + descriptors.reserve(descriptorSetLayout.bindings.size()); + for (auto const& entry: descriptorSetLayout.bindings) { + auto const& name = descriptor_sets::getDescriptorName(bindingPoint, entry.binding); + descriptors.push_back({ name, entry.type, entry.binding }); + } + } + mDescriptorSetLayout = { engine.getDescriptorSetLayoutFactory(), - engine.getDriverApi(), std::move(descriptorSetLayout[0]) }; + engine.getDriverApi(), std::move(descriptorSetLayout) }; mPerViewDescriptorSetLayout = { engine.getDescriptorSetLayoutFactory(), - engine.getDriverApi(), std::move(descriptorSetLayout[1]) }; + engine.getDriverApi(), perViewDescriptorSetLayout }; } descriptor_binding_t FMaterial::getSamplerBinding( diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 9727948375..a02e28f73a 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -1610,15 +1610,9 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& // User texture parameters container.push(info.sib); - - DescriptorSetLayout const perViewDescriptorSetLayout = - descriptor_sets::getPerViewDescriptorSetLayout( - mMaterialDomain, mVariantFilter, - info.isLit || info.hasShadowMultiplier, info.reflectionMode, info.refractionMode); - // Descriptor layout and descriptor name/binding mapping - container.push(info.sib, perViewDescriptorSetLayout); - container.push(info.sib, perViewDescriptorSetLayout); + container.push(info.sib); + container.push(info.sib); // User constant parameters FixedCapacityVector constantsEntry(mConstants.size()); diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp index 838e76d810..cc24a55f09 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp @@ -39,7 +39,7 @@ namespace filamat { MaterialUniformInterfaceBlockChunk::MaterialUniformInterfaceBlockChunk( BufferInterfaceBlock const& uib) : - Chunk(ChunkType::MaterialUib), + Chunk(MaterialUib), mUib(uib) { } @@ -60,7 +60,7 @@ void MaterialUniformInterfaceBlockChunk::flatten(Flattener& f) { MaterialSamplerInterfaceBlockChunk::MaterialSamplerInterfaceBlockChunk( SamplerInterfaceBlock const& sib) : - Chunk(ChunkType::MaterialSib), + Chunk(MaterialSib), mSib(sib) { } @@ -81,7 +81,7 @@ void MaterialSamplerInterfaceBlockChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ MaterialSubpassInterfaceBlockChunk::MaterialSubpassInterfaceBlockChunk(SubpassInfo const& subpass) : - Chunk(ChunkType::MaterialSubpass), + Chunk(MaterialSubpass), mSubpass(subpass) { } @@ -101,8 +101,8 @@ void MaterialSubpassInterfaceBlockChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ MaterialConstantParametersChunk::MaterialConstantParametersChunk( - utils::FixedCapacityVector constants) - : Chunk(ChunkType::MaterialConstants), mConstants(std::move(constants)) {} + FixedCapacityVector constants) + : Chunk(MaterialConstants), mConstants(std::move(constants)) {} void MaterialConstantParametersChunk::flatten(Flattener& f) { f.writeUint64(mConstants.size()); @@ -115,8 +115,8 @@ void MaterialConstantParametersChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ MaterialPushConstantParametersChunk::MaterialPushConstantParametersChunk( - CString const& structVarName, utils::FixedCapacityVector constants) - : Chunk(ChunkType::MaterialPushConstants), + CString const& structVarName, FixedCapacityVector constants) + : Chunk(MaterialPushConstants), mStructVarName(structVarName), mConstants(std::move(constants)) {} @@ -133,7 +133,7 @@ void MaterialPushConstantParametersChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ MaterialBindingUniformInfoChunk::MaterialBindingUniformInfoChunk(Container list) noexcept - : Chunk(ChunkType::MaterialBindingUniformInfo), + : Chunk(MaterialBindingUniformInfo), mBindingUniformInfo(std::move(list)) { } @@ -155,7 +155,7 @@ void MaterialBindingUniformInfoChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ MaterialAttributesInfoChunk::MaterialAttributesInfoChunk(Container list) noexcept - : Chunk(ChunkType::MaterialAttributeInfo), + : Chunk(MaterialAttributeInfo), mAttributeInfo(std::move(list)) { } @@ -170,11 +170,9 @@ void MaterialAttributesInfoChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ -MaterialDescriptorBindingsChuck::MaterialDescriptorBindingsChuck(Container const& sib, - backend::DescriptorSetLayout const& perViewLayout) noexcept - : Chunk(ChunkType::MaterialDescriptorBindingsInfo), - mSamplerInterfaceBlock(sib), - mPerViewLayout(perViewLayout) { +MaterialDescriptorBindingsChuck::MaterialDescriptorBindingsChuck(Container const& sib) noexcept + : Chunk(MaterialDescriptorBindingsInfo), + mSamplerInterfaceBlock(sib) { } void MaterialDescriptorBindingsChuck::flatten(Flattener& f) { @@ -183,13 +181,6 @@ void MaterialDescriptorBindingsChuck::flatten(Flattener& f) { using namespace backend; - - // number of descriptor-sets - f.writeUint8(3); - - // set - f.writeUint8(+DescriptorSetBindingPoints::PER_MATERIAL); - // samplers + 1 descriptor for the UBO f.writeUint8(mSamplerInterfaceBlock.getSize() + 1); @@ -210,37 +201,13 @@ void MaterialDescriptorBindingsChuck::flatten(Flattener& f) { } f.writeUint8(entry.binding); } - - // set - f.writeUint8(+DescriptorSetBindingPoints::PER_RENDERABLE); - f.writeUint8(descriptor_sets::getPerRenderableLayout().bindings.size()); - for (auto const& entry: descriptor_sets::getPerRenderableLayout().bindings) { - auto const& name = descriptor_sets::getDescriptorName( - DescriptorSetBindingPoints::PER_RENDERABLE, entry.binding); - f.writeString({ name.data(), name.size() }); - f.writeUint8(uint8_t(entry.type)); - f.writeUint8(entry.binding); - } - - // set - f.writeUint8(+DescriptorSetBindingPoints::PER_VIEW); - f.writeUint8(mPerViewLayout.bindings.size()); - for (auto const& entry: mPerViewLayout.bindings) { - auto const& name = descriptor_sets::getDescriptorName( - DescriptorSetBindingPoints::PER_VIEW, entry.binding); - f.writeString({ name.data(), name.size() }); - f.writeUint8(uint8_t(entry.type)); - f.writeUint8(entry.binding); - } } // ------------------------------------------------------------------------------------------------ -MaterialDescriptorSetLayoutChunk::MaterialDescriptorSetLayoutChunk(Container const& sib, - backend::DescriptorSetLayout const& perViewLayout) noexcept - : Chunk(ChunkType::MaterialDescriptorSetLayoutInfo), - mSamplerInterfaceBlock(sib), - mPerViewLayout(perViewLayout) { +MaterialDescriptorSetLayoutChunk::MaterialDescriptorSetLayoutChunk(Container const& sib) noexcept + : Chunk(MaterialDescriptorSetLayoutInfo), + mSamplerInterfaceBlock(sib) { } void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) { @@ -271,18 +238,6 @@ void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) { f.writeUint8(uint8_t(DescriptorFlags::NONE)); f.writeUint16(0); } - - // samplers + 1 descriptor for the UBO - f.writeUint8(mPerViewLayout.bindings.size()); - - // all the material's sampler descriptors - for (auto const& entry: mPerViewLayout.bindings) { - f.writeUint8(uint8_t(entry.type)); - f.writeUint8(uint8_t(entry.stageFlags)); - f.writeUint8(entry.binding); - f.writeUint8(uint8_t(entry.flags)); - f.writeUint16(entry.count); - } } } // namespace filamat diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h index cbb3a85446..6d428400ab 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h @@ -19,16 +19,12 @@ #include "Chunk.h" -#include - -#include #include #include #include #include -#include #include @@ -45,10 +41,10 @@ namespace filamat { class MaterialUniformInterfaceBlockChunk final : public Chunk { public: explicit MaterialUniformInterfaceBlockChunk(filament::BufferInterfaceBlock const& uib); - ~MaterialUniformInterfaceBlockChunk() final = default; + ~MaterialUniformInterfaceBlockChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; filament::BufferInterfaceBlock const& mUib; }; @@ -58,10 +54,10 @@ private: class MaterialSamplerInterfaceBlockChunk final : public Chunk { public: explicit MaterialSamplerInterfaceBlockChunk(filament::SamplerInterfaceBlock const& sib); - ~MaterialSamplerInterfaceBlockChunk() final = default; + ~MaterialSamplerInterfaceBlockChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; filament::SamplerInterfaceBlock const& mSib; }; @@ -71,10 +67,10 @@ private: class MaterialSubpassInterfaceBlockChunk final : public Chunk { public: explicit MaterialSubpassInterfaceBlockChunk(filament::SubpassInfo const& subpass); - ~MaterialSubpassInterfaceBlockChunk() final = default; + ~MaterialSubpassInterfaceBlockChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; filament::SubpassInfo const& mSubpass; }; @@ -84,41 +80,41 @@ private: class MaterialConstantParametersChunk final : public Chunk { public: explicit MaterialConstantParametersChunk( - utils::FixedCapacityVector constants); - ~MaterialConstantParametersChunk() final = default; + FixedCapacityVector constants); + ~MaterialConstantParametersChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; - utils::FixedCapacityVector mConstants; + FixedCapacityVector mConstants; }; // ------------------------------------------------------------------------------------------------ class MaterialPushConstantParametersChunk final : public Chunk { public: - explicit MaterialPushConstantParametersChunk(utils::CString const& structVarName, - utils::FixedCapacityVector constants); - ~MaterialPushConstantParametersChunk() final = default; + explicit MaterialPushConstantParametersChunk(CString const& structVarName, + FixedCapacityVector constants); + ~MaterialPushConstantParametersChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; - utils::CString mStructVarName; - utils::FixedCapacityVector mConstants; + CString mStructVarName; + FixedCapacityVector mConstants; }; // ------------------------------------------------------------------------------------------------ class MaterialBindingUniformInfoChunk final : public Chunk { using Container = FixedCapacityVector>; + uint8_t, CString, filament::backend::Program::UniformInfo>>; public: explicit MaterialBindingUniformInfoChunk(Container list) noexcept; - ~MaterialBindingUniformInfoChunk() final = default; + ~MaterialBindingUniformInfoChunk() override = default; private: - void flatten(Flattener &) final; + void flatten(Flattener &) override; Container mBindingUniformInfo; }; @@ -126,13 +122,13 @@ private: // ------------------------------------------------------------------------------------------------ class MaterialAttributesInfoChunk final : public Chunk { - using Container = FixedCapacityVector>; + using Container = FixedCapacityVector>; public: explicit MaterialAttributesInfoChunk(Container list) noexcept; - ~MaterialAttributesInfoChunk() final = default; + ~MaterialAttributesInfoChunk() override = default; private: - void flatten(Flattener &) final; + void flatten(Flattener &) override; Container mAttributeInfo; }; @@ -142,15 +138,13 @@ private: class MaterialDescriptorBindingsChuck final : public Chunk { using Container = filament::SamplerInterfaceBlock; public: - explicit MaterialDescriptorBindingsChuck(Container const& sib, - filament::backend::DescriptorSetLayout const& perViewLayout) noexcept; - ~MaterialDescriptorBindingsChuck() final = default; + explicit MaterialDescriptorBindingsChuck(Container const& sib) noexcept; + ~MaterialDescriptorBindingsChuck() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; Container const& mSamplerInterfaceBlock; - filament::backend::DescriptorSetLayout mPerViewLayout; }; // ------------------------------------------------------------------------------------------------ @@ -158,15 +152,13 @@ private: class MaterialDescriptorSetLayoutChunk final : public Chunk { using Container = filament::SamplerInterfaceBlock; public: - explicit MaterialDescriptorSetLayoutChunk(Container const& sib, - filament::backend::DescriptorSetLayout const& perViewLayout) noexcept; - ~MaterialDescriptorSetLayoutChunk() final = default; + explicit MaterialDescriptorSetLayoutChunk(Container const& sib) noexcept; + ~MaterialDescriptorSetLayoutChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; Container const& mSamplerInterfaceBlock; - filament::backend::DescriptorSetLayout mPerViewLayout; }; } // namespace filamat From 7734fd4ad92e98b4250d198871162026eb99fcd1 Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Wed, 7 May 2025 10:37:46 -0400 Subject: [PATCH 16/34] Add CString Append function --- libs/utils/include/utils/CString.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/utils/include/utils/CString.h b/libs/utils/include/utils/CString.h index b799172d3a..bea466ae29 100644 --- a/libs/utils/include/utils/CString.h +++ b/libs/utils/include/utils/CString.h @@ -128,6 +128,7 @@ public: CString& replace(size_type pos, size_type len, const CString& str) noexcept; CString& insert(size_type pos, const CString& str) noexcept { return replace(pos, 0, str); } + CString& append(const CString& str) noexcept { return insert(length(), str); } const_reference operator[](size_type pos) const noexcept { assert(pos < size()); From ba1d3f6c7645a1cc5ac326d13f5d71fe8e07c6fc Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Wed, 7 May 2025 11:09:33 -0400 Subject: [PATCH 17/34] Minimal labels implementation for descriptorset layout --- filament/backend/include/backend/DriverEnums.h | 2 ++ filament/src/details/Material.cpp | 10 ++++++++-- libs/filabridge/src/DescriptorSets.cpp | 17 ++++++++++++----- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index d8b281000c..bfb63363f1 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -1139,6 +1140,7 @@ struct ExternalSamplerDatum { static_assert(sizeof(ExternalSamplerDatum) == 12); struct DescriptorSetLayout { + std::variant label; utils::FixedCapacityVector bindings; // TODO: uncomment when needed diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 51a28bb603..91447aed0d 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -1149,11 +1149,17 @@ void FMaterial::processDescriptorSets(FEngine& engine, MaterialParser const* con backend::DescriptorSetLayout descriptorSetLayout; success = parser->getDescriptorSetLayout(&descriptorSetLayout); assert_invariant(success); + auto perMatLabel = mName; + perMatLabel.append("_perMat"); + descriptorSetLayout.label = std::move(perMatLabel); // get the PER_VIEW descriptor binding info - auto const& perViewDescriptorSetLayout = descriptor_sets::getPerViewDescriptorSetLayout( - mMaterialDomain, mVariantFilterMask, + auto perViewDescriptorSetLayout = + descriptor_sets::getPerViewDescriptorSetLayout(mMaterialDomain, mVariantFilterMask, mIsVariantLit || mHasShadowMultiplier, mReflectionMode, mRefractionMode); + auto perViewLabel = mName; + perViewLabel.append("_perView"); + perViewDescriptorSetLayout.label = std::move(perViewLabel); // get the PER_RENDERABLE and PER_VIEW descriptor binding info for (auto&& [bindingPoint, descriptorSetLayout] : { diff --git a/libs/filabridge/src/DescriptorSets.cpp b/libs/filabridge/src/DescriptorSets.cpp index cdcd44b9d0..b32bcdda5b 100644 --- a/libs/filabridge/src/DescriptorSets.cpp +++ b/libs/filabridge/src/DescriptorSets.cpp @@ -80,18 +80,25 @@ static constexpr std::initializer_list perRenderable }; // used for post-processing passes -static DescriptorSetLayout const postProcessDescriptorSetLayout{ postProcessDescriptorSetLayoutList }; +static DescriptorSetLayout const postProcessDescriptorSetLayout{ utils::StaticString("postProcess"), + postProcessDescriptorSetLayoutList }; // used to generate shadow-maps -static DescriptorSetLayout const depthVariantDescriptorSetLayout{ depthVariantDescriptorSetLayoutList }; +static DescriptorSetLayout const depthVariantDescriptorSetLayout{ + utils::StaticString("depthVariant"), depthVariantDescriptorSetLayoutList +}; -static DescriptorSetLayout const ssrVariantDescriptorSetLayout{ ssrVariantDescriptorSetLayoutList }; +static DescriptorSetLayout const ssrVariantDescriptorSetLayout{ utils::StaticString("ssrVariant"), + ssrVariantDescriptorSetLayoutList }; // Used for generating the color pass (i.e. the main pass). This is in fact a template that gets // declined into 8 different layouts, based on variants. -static DescriptorSetLayout perViewDescriptorSetLayout = { perViewDescriptorSetLayoutList }; +static DescriptorSetLayout perViewDescriptorSetLayout = { utils::StaticString("perView"), + perViewDescriptorSetLayoutList }; -static DescriptorSetLayout perRenderableDescriptorSetLayout = { perRenderableDescriptorSetLayoutList }; +static DescriptorSetLayout perRenderableDescriptorSetLayout = { + utils::StaticString("perRenderable"), perRenderableDescriptorSetLayoutList +}; DescriptorSetLayout const& getPostProcessLayout() noexcept { return postProcessDescriptorSetLayout; From fca200d5492c6a66d910a5eac2c4e0b19fa7dce9 Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Tue, 6 May 2025 13:39:27 -0400 Subject: [PATCH 18/34] Use variant layout label if available --- filament/backend/src/webgpu/WebGPUHandles.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/filament/backend/src/webgpu/WebGPUHandles.cpp b/filament/backend/src/webgpu/WebGPUHandles.cpp index e380af099c..1de9f09e41 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.cpp +++ b/filament/backend/src/webgpu/WebGPUHandles.cpp @@ -201,6 +201,15 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& wgpu::Device const& device) { assert_invariant(device); + std::string baseLabel; + if (std::holds_alternative(layout.label)) { + const auto& temp = std::get_if(&layout.label); + baseLabel = temp->c_str(); + } else if (std::holds_alternative(layout.label)) { + const auto& temp = std::get_if(&layout.label); + baseLabel = temp->c_str(); + } + // TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for // debugging. For now, hack an incrementing value. static int layoutNum = 0; @@ -258,7 +267,7 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& } // fEntry.count is unused currently } - std::string label = "layout_"+ std::to_string(++layoutNum) ; + std::string label = "layout_" + baseLabel + std::to_string(++layoutNum) ; wgpu::BindGroupLayoutDescriptor layoutDescriptor{ .label{label.c_str()}, // Use .c_str() if label needs to be const char* .entryCount = wEntries.size(), From b231de0b5b10ba851f90e181526ef2273ecc1164 Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Tue, 6 May 2025 13:40:30 -0400 Subject: [PATCH 19/34] WebGPU: HelloTraingle hacks Co-authored-by: Andy Hovingh <6198728+AndyHovingh@users.noreply.github.com> --- filament/backend/src/webgpu/WebGPUHandles.cpp | 192 +++++++++++++----- filament/backend/src/webgpu/WebGPUHandles.h | 16 +- 2 files changed, 148 insertions(+), 60 deletions(-) diff --git a/filament/backend/src/webgpu/WebGPUHandles.cpp b/filament/backend/src/webgpu/WebGPUHandles.cpp index 1de9f09e41..06c0344334 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.cpp +++ b/filament/backend/src/webgpu/WebGPUHandles.cpp @@ -119,6 +119,44 @@ wgpu::VertexFormat getVertexFormat(filament::backend::ElementType type, bool nor } } +wgpu::StringView getUserTextureLabel(filament::backend::SamplerType target) { + // TODO will be helpful to get more useful info than this + using filament::backend::SamplerType; + switch (target) { + case SamplerType::SAMPLER_2D: + return "a_2D_user_texture"; + case SamplerType::SAMPLER_2D_ARRAY: + return "a_2D_array_user_texture"; + case SamplerType::SAMPLER_CUBEMAP: + return "a_cube_map_user_texture"; + case SamplerType::SAMPLER_EXTERNAL: + return "an_external_user_texture"; + case SamplerType::SAMPLER_3D: + return "a_3D_user_texture"; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + return "a_cube_mape_array_user_texture"; + } +} + +wgpu::StringView getUserTextureViewLabel(filament::backend::SamplerType target) { + // TODO will be helpful to get more useful info than this + using filament::backend::SamplerType; + switch (target) { + case SamplerType::SAMPLER_2D: + return "a_2D_user_texture_view"; + case SamplerType::SAMPLER_2D_ARRAY: + return "a_2D_array_user_texture_view"; + case SamplerType::SAMPLER_CUBEMAP: + return "a_cube_map_user_texture_view"; + case SamplerType::SAMPLER_EXTERNAL: + return "an_external_user_texture_view"; + case SamplerType::SAMPLER_3D: + return "a_3D_user_texture_view"; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + return "a_cube_mape_array_user_texture_view"; + } +} + }// namespace namespace filament::backend { @@ -244,6 +282,14 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& // We are simply hoping that undefined and defaults suffices here. samplerEntry.sampler.type = wgpu::SamplerBindingType::NonFiltering; // Example default wEntry.texture.sampleType = wgpu::TextureSampleType::Float; // Example default + // TODO: FIX! THIS IS HACK FOR HELLO-TRIANGLE! + if (baseLabel.find("Skybox") != std::string::npos || + (baseLabel == "Filament Default Material_perView" && wEntry.binding == 22)) { + wEntry.texture.viewDimension = wgpu::TextureViewDimension::Cube; + } else { + wEntry.texture.viewDimension = + wgpu::TextureViewDimension::e2D;// Example default + } entryInfo.type = WebGPUDescriptorSetLayout::BindGroupEntryType::TEXTURE_VIEW; break; } @@ -459,49 +505,65 @@ size_t WebGPUDescriptorSet::countEntitiesWithDynamicOffsets() const { // From createTextureR WGPUTexture::WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, - wgpu::Device device) noexcept { - - // First the texture aspect - wgpu::TextureDescriptor desc; - + wgpu::Device const& device) noexcept { + assert_invariant( + samples == 1 || + samples == 4 && + "An invalid number of samples were requested, as WGPU requires the sample " + "count to either be 1 (no multisampling) or 4, at least as of April 2025 of " + "the spec. See https://www.w3.org/TR/webgpu/#texture-creation or " + "https://gpuweb.github.io/gpuweb/#multisample-state"); + // First, the texture aspect, starting with the defaults/basic configuration + mUsage = fToWGPUTextureUsage(usage); + mFormat = fToWGPUTextureFormat(format); + wgpu::TextureDescriptor textureDescriptor{ + .label = getUserTextureLabel(target), + .usage = mUsage, + .dimension = target == SamplerType::SAMPLER_3D ? wgpu::TextureDimension::e3D + : wgpu::TextureDimension::e2D, + .size = { .width = width, .height = height, .depthOrArrayLayers = depth }, + .format = mFormat, + .mipLevelCount = levels, + .sampleCount = samples, + // TODO Is this fine? Could do all-the-things, a naive mapping or get something from + // Filament + .viewFormatCount = 0, + .viewFormats = nullptr, + }; + // adjust for specific cases switch (target) { - case SamplerType::SAMPLER_CUBEMAP: - case SamplerType::SAMPLER_CUBEMAP_ARRAY: case SamplerType::SAMPLER_2D: + mArrayLayerCount = 1; + break; case SamplerType::SAMPLER_2D_ARRAY: - // Should be safe to assume external is 2d - case SamplerType::SAMPLER_EXTERNAL: { - desc.dimension = wgpu::TextureDimension::e2D; + mArrayLayerCount = textureDescriptor.size.depthOrArrayLayers; break; - } - case SamplerType::SAMPLER_3D: { - desc.dimension = wgpu::TextureDimension::e3D; + case SamplerType::SAMPLER_CUBEMAP: + textureDescriptor.size.depthOrArrayLayers = 6; + mArrayLayerCount = textureDescriptor.size.depthOrArrayLayers; + break; + case SamplerType::SAMPLER_EXTERNAL: + case SamplerType::SAMPLER_3D: + mArrayLayerCount = 1; + break; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + textureDescriptor.size.depthOrArrayLayers = depth * 6; + mArrayLayerCount = textureDescriptor.size.depthOrArrayLayers; break; - } } - desc.size = { .width = width, .height = height, .depthOrArrayLayers = depth }; - desc.format = fToWGPUTextureFormat(format); - assert_invariant(desc.format != wgpu::TextureFormat::Undefined); - - // WGPU requires this to be true. Filament should comply - assert(samples == 1 || samples || 4); - - desc.sampleCount = samples; - desc.usage = fToWGPUTextureUsage(usage); - desc.mipLevelCount = levels; - - // TODO Is this fine? Could do all-the-things, a naive mapping or get something from Filament - desc.viewFormats = nullptr; - - texture = device.CreateTexture(&desc); - - // TODO should a default levelCount be something other than 0? Sample count? - texView = makeTextureView(0, 1); + assert_invariant(textureDescriptor.format != wgpu::TextureFormat::Undefined && + "Could not find appropriate WebGPU format"); + mTexture = device.CreateTexture(&textureDescriptor); + FILAMENT_CHECK_POSTCONDITION(mTexture) + << "Failed to create texture for " << textureDescriptor.label; + // Second, the texture view aspect + mTexView = makeTextureView(0, levels, target); } + // From createTextureViewR WGPUTexture::WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept { - texture = src->texture; - texView = makeTextureView(baseLevel, levelCount); + mTexture = src->mTexture; + mTexView = makeTextureView(baseLevel, levelCount, target); } wgpu::TextureUsage WGPUTexture::fToWGPUTextureUsage(const TextureUsage& fUsage) { @@ -792,26 +854,48 @@ wgpu::TextureFormat WGPUTexture::fToWGPUTextureFormat(const TextureFormat& fUsag } } -wgpu::TextureView WGPUTexture::makeTextureView(const uint8_t& baseLevel, - const uint8_t& levelCount) { - wgpu::TextureViewDescriptor desc; - desc.baseMipLevel = baseLevel; - desc.mipLevelCount = levelCount; - - // baseArrayLayer is required, making a guess - desc.baseArrayLayer = 0; - // Have not found an analouge to aspect in other drivers, but ALL should be unrestrictive. - // TODO Can we make this better? - desc.aspect = wgpu::TextureAspect::All; - - // The rest of the properties should be fine to leave as default, using the texture params. - desc.label = "TODO"; - - desc.format = wgpu::TextureFormat::Undefined; - desc.dimension = wgpu::TextureViewDimension::Undefined; - desc.usage = wgpu::TextureUsage::None; - - return texture.CreateView(&desc); +wgpu::TextureView WGPUTexture::makeTextureView(const uint8_t& baseLevel, const uint8_t& levelCount, + SamplerType target) { + // starting with the defaults/basic configuration + wgpu::TextureViewDescriptor textureViewDescriptor{ + .label = getUserTextureViewLabel(target), + .format = mFormat, + // dimension depends on target and is set below + .baseMipLevel = baseLevel, + .mipLevelCount = levelCount, + // baseArrayLayer is required, making a guess + .baseArrayLayer = 0, + .arrayLayerCount = mArrayLayerCount, + // Have not found an analog to aspect in other drivers, but ALL should be unrestrictive. + // TODO Can we make this better? + .aspect = wgpu::TextureAspect::All, + .usage = mUsage + }; + // adjust for specific cases + switch (target) { + case SamplerType::SAMPLER_2D: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2D; + break; + case SamplerType::SAMPLER_2D_ARRAY: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2DArray; + break; + case SamplerType::SAMPLER_CUBEMAP: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::Cube; + break; + case SamplerType::SAMPLER_EXTERNAL: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2D; + break; + case SamplerType::SAMPLER_3D: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::e3D; + break; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::CubeArray; + break; + } + wgpu::TextureView textureView = mTexture.CreateView(&textureViewDescriptor); + FILAMENT_CHECK_POSTCONDITION(textureView) + << "Failed to create texture view " << textureViewDescriptor.label; + return textureView; } WGPURenderTarget::Attachment WGPURenderTarget::getDrawColorAttachment(size_t index) { diff --git a/filament/backend/src/webgpu/WebGPUHandles.h b/filament/backend/src/webgpu/WebGPUHandles.h index dddf4ab39a..fdc51e92b0 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.h +++ b/filament/backend/src/webgpu/WebGPUHandles.h @@ -173,23 +173,27 @@ class WGPUTexture : public HwTexture { public: WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, - wgpu::Device device) noexcept; + wgpu::Device const& device) noexcept; WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept; - const wgpu::Texture& getTexture() const { return texture; } - const wgpu::TextureView& getTexView() const { return texView; } + const wgpu::Texture& getTexture() const { return mTexture; } + const wgpu::TextureView& getTexView() const { return mTexView; } // Public to allow checking for support of a texture format static wgpu::TextureFormat fToWGPUTextureFormat(const filament::backend::TextureFormat& fUsage); private: - wgpu::TextureView makeTextureView(const uint8_t& baseLevel, const uint8_t& levelCount); + wgpu::TextureView makeTextureView(const uint8_t& baseLevel, const uint8_t& levelCount, + SamplerType target); // CreateTextureR has info for a texture and sampler. Texture Views are needed for binding, // along with a sampler Current plan: Inherit the sampler and Texture to always exist (It is a // ref counted pointer) when making views. View is optional - wgpu::Texture texture = nullptr; - wgpu::TextureView texView = nullptr; + wgpu::Texture mTexture = nullptr; + wgpu::TextureUsage mUsage = wgpu::TextureUsage::None; + wgpu::TextureFormat mFormat = wgpu::TextureFormat::Undefined; + uint32_t mArrayLayerCount = 1; + wgpu::TextureView mTexView = nullptr; wgpu::TextureUsage fToWGPUTextureUsage(const filament::backend::TextureUsage& fUsage); }; From b94c802076efc28e94e8c3d2e06e64ac49e200a6 Mon Sep 17 00:00:00 2001 From: Syed Idris Shah Date: Thu, 24 Apr 2025 13:24:37 -0400 Subject: [PATCH 20/34] Dawn change to enable writeBuffer for size != multiple of 4 --- third_party/dawn/src/dawn/native/CommandValidation.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/third_party/dawn/src/dawn/native/CommandValidation.cpp b/third_party/dawn/src/dawn/native/CommandValidation.cpp index 7fa92a301a..65b37c8f27 100644 --- a/third_party/dawn/src/dawn/native/CommandValidation.cpp +++ b/third_party/dawn/src/dawn/native/CommandValidation.cpp @@ -229,10 +229,10 @@ MaybeError ValidateWriteBuffer(const DeviceBase* device, uint64_t size) { DAWN_TRY(device->ValidateObject(buffer)); - DAWN_INVALID_IF(bufferOffset % 4 != 0, "BufferOffset (%u) is not a multiple of 4.", - bufferOffset); - - DAWN_INVALID_IF(size % 4 != 0, "Size (%u) is not a multiple of 4.", size); + // DAWN_INVALID_IF(bufferOffset % 4 != 0, "BufferOffset (%u) is not a multiple of 4.", + // bufferOffset); + // + // DAWN_INVALID_IF(size % 4 != 0, "Size (%u) is not a multiple of 4.", size); uint64_t bufferSize = buffer->GetSize(); DAWN_INVALID_IF(bufferOffset > bufferSize || size > (bufferSize - bufferOffset), From 6adf140fb4c18be9d2ab5da2d7289554c7eff35e Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Tue, 6 May 2025 16:59:32 -0400 Subject: [PATCH 21/34] Simple and possibly incomplete pipeline hashing --- filament/backend/src/webgpu/WebGPUDriver.cpp | 12 ++++++++++-- filament/backend/src/webgpu/WebGPUDriver.h | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index 8f8e5bc103..56b4bc114c 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +#include #include "webgpu/WebGPUDriver.h" #include "WebGPUPipelineCreation.h" @@ -916,6 +916,14 @@ void WebGPUDriver::blit( } void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) { + // TODO Investigate implications of this hash more closely. Vulkan has a whole class + // VulkanPipelineCache to handle this, may be missing nuance + static auto pipleineStateHasher = utils::hash::MurmurHashFn(); + auto hash = pipleineStateHasher(pipelineState); + if(mPipelineMap.find(hash) != mPipelineMap.end()){ + mRenderPassEncoder.SetPipeline(mPipelineMap[hash]); + return; + } const auto* program = handleCast(pipelineState.program); assert_invariant(program); assert_invariant(program->computeShaderModule == nullptr && @@ -952,7 +960,7 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) { *vertexBufferInfo, layout, pipelineState.rasterState, pipelineState.stencilState, pipelineState.polygonOffset, pipelineState.primitiveType, mSwapChain->getColorFormat(), mSwapChain->getDepthFormat()); - // TODO: uncomment once we have a valid pipeline to set + mPipelineMap[hash] = pipeline; mRenderPassEncoder.SetPipeline(pipeline); } diff --git a/filament/backend/src/webgpu/WebGPUDriver.h b/filament/backend/src/webgpu/WebGPUDriver.h index 2f8386d552..c6a2d99d23 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.h +++ b/filament/backend/src/webgpu/WebGPUDriver.h @@ -93,6 +93,8 @@ private: wgpu::RenderPassEncoder mRenderPassEncoder = nullptr; wgpu::CommandBuffer mCommandBuffer = nullptr; WGPURenderTarget* mDefaultRenderTarget = nullptr; + + tsl::robin_map mPipelineMap; /* * Driver interface */ From de7bcd2df7f0823d45e95a3974d46647486da263 Mon Sep 17 00:00:00 2001 From: Juan David Caldas Date: Tue, 6 May 2025 19:47:51 -0400 Subject: [PATCH 22/34] explicitly set bindings --- filament/backend/test/Shader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filament/backend/test/Shader.cpp b/filament/backend/test/Shader.cpp index 30b6e50a0e..bed1b0ee31 100644 --- a/filament/backend/test/Shader.cpp +++ b/filament/backend/test/Shader.cpp @@ -57,7 +57,7 @@ Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup if (!kLayouts.empty()) { mDescriptorSetLayout = - cleanup.add(api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts })); + cleanup.add(api.createDescriptorSetLayout(DescriptorSetLayout{ .bindings = kLayouts })); } } From 44a75dd44b8df3a331cd22b4d05752850693c3e1 Mon Sep 17 00:00:00 2001 From: rafadevai Date: Wed, 7 May 2025 12:08:34 -0700 Subject: [PATCH 23/34] VK: Add a new platform method to check UMA (#8704) * VK: Add a new context method to check UMA This heuristic should work on almost all devices, can be extended later as needed. * Addressing PR comments --------- Co-authored-by: Powei Feng Co-authored-by: Serge Metral --- filament/backend/src/vulkan/VulkanContext.h | 5 +++++ .../src/vulkan/platform/VulkanPlatform.cpp | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h index d4804bf31b..0ca6d05722 100644 --- a/filament/backend/src/vulkan/VulkanContext.h +++ b/filament/backend/src/vulkan/VulkanContext.h @@ -142,6 +142,10 @@ public: return mPortabilitySubsetFeatures.imageView2DOn3DImage == VK_TRUE; } + inline bool isUnifiedMemoryArchitecture() const noexcept { + return mIsUnifiedMemoryArchitecture; + } + private: VkPhysicalDeviceMemoryProperties mMemoryProperties = {}; VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = { @@ -164,6 +168,7 @@ private: bool mDebugUtilsSupported = false; bool mLazilyAllocatedMemorySupported = false; bool mProtectedMemorySupported = false; + bool mIsUnifiedMemoryArchitecture = false; fvkutils::VkFormatList mDepthStencilFormats; fvkutils::VkFormatList mBlittableDepthStencilFormats; diff --git a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp index 4323aba877..46763e5c8a 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp @@ -631,6 +631,21 @@ fvkutils::VkFormatList findBlittableDepthStencilFormats(VkPhysicalDevice device) return ret; } +/** + * Check if the GPU has a unified memory architecture. + */ +bool hasUnifiedMemoryArchitecture(VkPhysicalDeviceMemoryProperties memoryProperties) noexcept { + // Try to identify if the platform is running on a Unified Memory Architecture by inspecting the + // memory heap flags, if they are all VK_MEMORY_HEAP_DEVICE_LOCAL_BIT it's UMA, otherwise not + // enough information to make a decision, so default to false. + for (uint32_t i = 0; i < memoryProperties.memoryHeapCount; ++i) { + if ((memoryProperties.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0) { + return false; + } + } + return true; +} + }// anonymous namespace using SwapChainPtr = VulkanPlatform::SwapChainPtr; @@ -864,6 +879,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext, } } + context.mIsUnifiedMemoryArchitecture = hasUnifiedMemoryArchitecture(context.mMemoryProperties); + #ifdef NDEBUG // If we are in release build, we should not have turned on debug extensions FILAMENT_CHECK_POSTCONDITION(!context.mDebugUtilsSupported && !context.mDebugMarkersSupported) From 3d10ae3ee3bc827a3f70f6da0fca3d7ca66b1512 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Wed, 7 May 2025 12:39:11 -0700 Subject: [PATCH 24/34] github: more build file refactoring (#8678) - Move emscripten download into its own script - Refactor the common "CI choice" prompt into its own file. - Move the content of `build/common/ci-common.sh` to the "CI choice" script. - Mention the get-emscripten.sh script in BUILDING.md --- .github/actions/dep-versions/action.yml | 2 +- .github/actions/web-prereq/action.yml | 16 +++++++++++++ .github/workflows/presubmit.yml | 1 + .github/workflows/release.yml | 1 + .github/workflows/web-continuous.yml | 1 + BUILDING.md | 2 ++ build/android/build.sh | 29 +---------------------- build/common/build-common.sh | 15 ++++++++++++ build/common/ci-check.sh | 19 +++++++++++++++ build/common/ci-common.sh | 5 ---- build/common/get-emscripten.sh | 22 ++++++++++++++++++ build/common/versions | 3 ++- build/ios/build.sh | 26 +-------------------- build/linux/build.sh | 31 ++----------------------- build/mac/build.sh | 27 ++------------------- build/web/build.sh | 26 +-------------------- build/web/ci-common.sh | 11 --------- 17 files changed, 87 insertions(+), 150 deletions(-) create mode 100644 .github/actions/web-prereq/action.yml create mode 100644 build/common/ci-check.sh delete mode 100755 build/common/ci-common.sh create mode 100755 build/common/get-emscripten.sh delete mode 100755 build/web/ci-common.sh diff --git a/.github/actions/dep-versions/action.yml b/.github/actions/dep-versions/action.yml index dc2b48b59c..bdd2e32a76 100644 --- a/.github/actions/dep-versions/action.yml +++ b/.github/actions/dep-versions/action.yml @@ -3,5 +3,5 @@ runs: using: "composite" steps: - name: Set up dependency versions - shell: bash + shell: bash run: cat ./build/common/versions >> $GITHUB_ENV diff --git a/.github/actions/web-prereq/action.yml b/.github/actions/web-prereq/action.yml new file mode 100644 index 0000000000..d239dea0f1 --- /dev/null +++ b/.github/actions/web-prereq/action.yml @@ -0,0 +1,16 @@ +name: 'Web Preqrequisites' +runs: + using: "composite" + steps: + - uses: ./.github/actions/dep-versions + - name: Cache EMSDK + id: emsdk-cache + uses: actions/cache@v4 # Use a specific version + with: + path: emsdk + key: ${{ runner.os }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }} + - name: Install Web Prerequisites + shell: bash + run: | + bash ./build/common/get-emscripten.sh + echo "EMSDK=$PWD/emsdk" >> $GITHUB_ENV diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index ca4b3e2a89..42d2af00a9 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -96,6 +96,7 @@ jobs: with: fetch-depth: 0 - uses: ./.github/actions/linux-prereq + - uses: ./.github/actions/web-prereq - name: Run build script run: | cd build/web && printf "y" | ./build.sh presubmit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ed2148880..d1b2fba3bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -118,6 +118,7 @@ jobs: with: ref: ${{ steps.git_ref.outputs.ref }} - uses: ./.github/actions/linux-prereq + - uses: ./.github/actions/web-prereq - name: Run build script env: TAG: ${{ steps.git_ref.outputs.tag }} diff --git a/.github/workflows/web-continuous.yml b/.github/workflows/web-continuous.yml index 74aa57de5c..180f1a26f5 100644 --- a/.github/workflows/web-continuous.yml +++ b/.github/workflows/web-continuous.yml @@ -17,6 +17,7 @@ jobs: with: fetch-depth: 0 - uses: ./.github/actions/linux-prereq + - uses: ./.github/actions/web-prereq - name: Run build script run: | cd build/web && printf "y" | ./build.sh continuous diff --git a/BUILDING.md b/BUILDING.md index 167e1736ed..24614ccf46 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -363,6 +363,8 @@ python ./emsdk.py activate latest source ./emsdk_env.sh ``` +Alternatively, you can try running the script `build/common/get-emscripten.sh`. + After this you can invoke the [easy build](#easy-build) script as follows: ```shell diff --git a/build/android/build.sh b/build/android/build.sh index da673271e7..2fab3f833f 100755 --- a/build/android/build.sh +++ b/build/android/build.sh @@ -1,28 +1,6 @@ #!/bin/bash -# Usage: the first argument selects the build type: -# - release, to build release only -# - debug, to build debug only -# - continuous, to build release and debug -# - presubmit, for presubmit builds -# -# The default is release - -echo "This script is intended to run in a CI environment and may modify your current environment." -echo "Please refer to BUILDING.md for more information." - -read -r -p "Do you wish to proceed (y/n)? " choice -case "${choice}" in - y|Y) - echo "Build will proceed..." - ;; - n|N) - exit 0 - ;; - *) - exit 0 - ;; -esac +source `dirname $0`/../common/ci-check.sh set -e set -x @@ -30,11 +8,6 @@ set -x UNAME=`echo $(uname)` LC_UNAME=`echo $UNAME | tr '[:upper:]' '[:lower:]'` -# build-common.sh will generate the following variables: -# $GENERATE_ARCHIVES -# $BUILD_DEBUG -# $BUILD_RELEASE -source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/build-common.sh if [[ "$GITHUB_WORKFLOW" ]]; then diff --git a/build/common/build-common.sh b/build/common/build-common.sh index 79c2231115..df4094add9 100755 --- a/build/common/build-common.sh +++ b/build/common/build-common.sh @@ -1,5 +1,20 @@ #!/bin/bash +# build-common.sh will generate the following variables: +# $GENERATE_ARCHIVES +# $BUILD_DEBUG +# $BUILD_RELEASE + +# Typically a build script (build.sh) would source this script. For example, +# source `dirname $0`/../common/build-common.sh + +# Usage: the first argument selects the build type: +# - release, to build release only +# - debug, to build debug only +# - continuous, to build release and debug +# - presubmit, for presubmit builds +# +# The default is release if [[ ! "$TARGET" ]]; then if [[ "$1" ]]; then TARGET=$1 diff --git a/build/common/ci-check.sh b/build/common/ci-check.sh new file mode 100644 index 0000000000..022d1523e0 --- /dev/null +++ b/build/common/ci-check.sh @@ -0,0 +1,19 @@ +echo "This script is intended to run in a CI environment and may modify your current environment." +echo "Please refer to BUILDING.md for more information." + +read -r -p "Do you wish to proceed (y/n)? " choice +case "${choice}" in + y|Y) + echo "Build will proceed..." + ;; + n|N) + exit 0 + ;; + *) + exit 0 + ;; +esac + +if [[ "$GITHUB_WORKFLOW" ]]; then + echo "Running workflow $GITHUB_WORKFLOW (event: $GITHUB_EVENT_NAME, action: $GITHUB_ACTION)" +fi diff --git a/build/common/ci-common.sh b/build/common/ci-common.sh deleted file mode 100755 index 2b66ec0375..0000000000 --- a/build/common/ci-common.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -if [[ "$GITHUB_WORKFLOW" ]]; then - echo "Running workflow $GITHUB_WORKFLOW (event: $GITHUB_EVENT_NAME, action: $GITHUB_ACTION)" -fi diff --git a/build/common/get-emscripten.sh b/build/common/get-emscripten.sh new file mode 100755 index 0000000000..ec16d3c144 --- /dev/null +++ b/build/common/get-emscripten.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +if [ -d "./emsdk" ]; then + echo "emsdk folder found. Assume emsdk has been installed." + cd emsdk + ./emsdk activate latest + source ./emsdk_env.sh + export EMSDK="$PWD" + cd .. + exit 0 +fi + +# Install emscripten. +EMSDK_VERSION=${GITHUB_EMSDK_VERSION-3.1.60} +curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/${EMSDK_VERSION}.zip > emsdk.zip +unzip emsdk.zip ; mv emsdk-* emsdk ; cd emsdk +./emsdk install latest +./emsdk activate latest +source ./emsdk_env.sh + +export EMSDK="$PWD" +cd .. diff --git a/build/common/versions b/build/common/versions index 49749b5231..bc836873e8 100644 --- a/build/common/versions +++ b/build/common/versions @@ -3,4 +3,5 @@ GITHUB_CMAKE_VERSION=3.19.5 GITHUB_NINJA_VERSION=1.10.2 GITHUB_MESA_VERSION=24.2.1 GITHUB_LLVM_VERSION=16 -GITHUB_NDK_VERSION=27.0.11718014 \ No newline at end of file +GITHUB_NDK_VERSION=27.0.11718014 +GITHUB_EMSDK_VERSION=3.1.60 \ No newline at end of file diff --git a/build/ios/build.sh b/build/ios/build.sh index e43ddbb12b..b9940a6ead 100755 --- a/build/ios/build.sh +++ b/build/ios/build.sh @@ -1,35 +1,11 @@ #!/bin/bash -# Usage: the first argument selects the build type: -# - release, to build release only -# - debug, to build debug only -# - continuous, to build release and debug -# - presubmit, for presubmit builds -# -# The default is release - -echo "This script is intended to run in a CI environment and may modify your current environment." -echo "Please refer to BUILDING.md for more information." - -read -r -p "Do you wish to proceed (y/n)? " choice -case "${choice}" in - y|Y) - echo "Build will proceed..." - ;; - n|N) - exit 0 - ;; - *) - exit 0 - ;; -esac +source `dirname $0`/../common/ci-check.sh set -e set -x -source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/build-common.sh - pushd `dirname $0`/../.. > /dev/null # If we're generating an archive for release or continuous builds, then we'll also build for the diff --git a/build/linux/build.sh b/build/linux/build.sh index 021800ad13..06a64afdde 100755 --- a/build/linux/build.sh +++ b/build/linux/build.sh @@ -1,38 +1,11 @@ #!/bin/bash -# Usage: the first argument selects the build type: -# - release, to build release only -# - debug, to build debug only -# - continuous, to build release and debug -# - presubmit, for presubmit builds -# -# The default is release - -echo "This script is intended to run in a CI environment and may modify your current environment." -echo "Please refer to BUILDING.md for more information." - -read -r -p "Do you wish to proceed (y/n)? " choice -case "${choice}" in - y|Y) - echo "Build will proceed..." - ;; - n|N) - exit 0 - ;; - *) - exit 0 - ;; -esac +source `dirname $0`/../common/ci-check.sh set -e set -x -# build-common.sh will generate the following variables: -# $GENERATE_ARCHIVES -# $BUILD_DEBUG -# $BUILD_RELEASE -source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/build-common.sh - pushd `dirname $0`/../.. > /dev/null + ./build.sh -c $RUN_TESTS $GENERATE_ARCHIVES $BUILD_DEBUG $BUILD_RELEASE diff --git a/build/mac/build.sh b/build/mac/build.sh index 388316c10d..06a64afdde 100755 --- a/build/mac/build.sh +++ b/build/mac/build.sh @@ -1,34 +1,11 @@ #!/bin/bash -# Usage: the first argument selects the build type: -# - release, to build release only -# - debug, to build debug only -# - continuous, to build release and debug -# - presubmit, for presubmit builds -# -# The default is release - -echo "This script is intended to run in a CI environment and may modify your current environment." -echo "Please refer to BUILDING.md for more information." - -read -r -p "Do you wish to proceed (y/n)? " choice -case "${choice}" in - y|Y) - echo "Build will proceed..." - ;; - n|N) - exit 0 - ;; - *) - exit 0 - ;; -esac +source `dirname $0`/../common/ci-check.sh set -e set -x -source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/build-common.sh - pushd `dirname $0`/../.. > /dev/null + ./build.sh -c $RUN_TESTS $GENERATE_ARCHIVES $BUILD_DEBUG $BUILD_RELEASE diff --git a/build/web/build.sh b/build/web/build.sh index 624c091d53..dc038b7581 100755 --- a/build/web/build.sh +++ b/build/web/build.sh @@ -1,34 +1,10 @@ #!/bin/bash -# Usage: the first argument selects the build type: -# - release, to build release only -# - debug, to build debug only -# - continuous, to build release and debug -# - presubmit, for presubmit builds -# -# The default is release - -echo "This script is intended to run in a CI environment and may modify your current environment." -echo "Please refer to BUILDING.md for more information." - -read -r -p "Do you wish to proceed (y/n)? " choice -case "${choice}" in - y|Y) - echo "Build will proceed..." - ;; - n|N) - exit 0 - ;; - *) - exit 0 - ;; -esac +source `dirname $0`/../common/ci-check.sh set -e set -x -source `dirname $0`/../common/ci-common.sh -source `dirname $0`/ci-common.sh source `dirname $0`/../common/build-common.sh pushd `dirname $0`/../.. > /dev/null diff --git a/build/web/ci-common.sh b/build/web/ci-common.sh deleted file mode 100755 index 1a5a19a4b1..0000000000 --- a/build/web/ci-common.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# Install emscripten. -curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.60.zip > emsdk.zip -unzip emsdk.zip ; mv emsdk-* emsdk ; cd emsdk -./emsdk install latest -./emsdk activate latest -source ./emsdk_env.sh - -export EMSDK="$PWD" -cd .. From 9683eb649c574af0bab0be6edcd6c3407f4139af Mon Sep 17 00:00:00 2001 From: Matthew Hoffman Date: Wed, 7 May 2025 15:48:55 -0500 Subject: [PATCH 25/34] Backend test python script updated to display side by side images. (#8695) --- filament/backend/test/BackendTest.cpp | 30 ++- filament/backend/test/BackendTest.h | 10 +- filament/backend/test/ImageExpectations.cpp | 15 +- filament/backend/test/ImageExpectations.h | 5 + .../test/move_actual_images_to_expected.py | 191 +++++++++++++----- 5 files changed, 188 insertions(+), 63 deletions(-) diff --git a/filament/backend/test/BackendTest.cpp b/filament/backend/test/BackendTest.cpp index 31c81040f2..2788bad598 100644 --- a/filament/backend/test/BackendTest.cpp +++ b/filament/backend/test/BackendTest.cpp @@ -45,6 +45,7 @@ namespace test { Backend BackendTest::sBackend = Backend::NOOP; OperatingSystem BackendTest::sOperatingSystem = OperatingSystem::OTHER; bool BackendTest::sIsMobilePlatform = false; +std::vector BackendTest::sFailedImages; void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform) { sBackend = backend; @@ -63,11 +64,12 @@ BackendTest::~BackendTest() { flushAndWait(); mImageExpectations->evaluate(); // Note: Don't terminate the driver for OpenGL, as it wipes away the context and removes the buffer from the screen. - if (sBackend == Backend::OPENGL) { - return; + if (sBackend != Backend::OPENGL) { + driver->terminate(); + delete driver; } - driver->terminate(); - delete driver; + + recordFailedImages(); } void BackendTest::initializeDriver() { @@ -167,8 +169,24 @@ bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem) { return sOperatingSystem == operatingSystem; } -bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem, Backend backend) { - return matchesEnvironment(operatingSystem) && matchesEnvironment(backend); +void BackendTest::markImageAsFailure(std::string failedImageName) { + sFailedImages.emplace_back(std::move(failedImageName)); +} + +void BackendTest::recordFailedImages() { + if (!sFailedImages.empty()) { + std::string failedImages; + for (auto& failedTestImageName: sFailedImages) { + if (failedImages.empty()) { + failedImages = failedTestImageName; + } else { + failedImages.append(","); + failedImages.append(failedTestImageName); + } + } + RecordProperty("FailedImages", failedImages); + } + sFailedImages.clear(); } class Environment : public ::testing::Environment { diff --git a/filament/backend/test/BackendTest.h b/filament/backend/test/BackendTest.h index c2d05cb37d..a5c16fd203 100644 --- a/filament/backend/test/BackendTest.h +++ b/filament/backend/test/BackendTest.h @@ -38,6 +38,9 @@ public: static OperatingSystem sOperatingSystem; static bool sIsMobilePlatform; + // Takes the name of the image that wasn't correct, without the .png suffix + static void markImageAsFailure(std::string failedImageName); + protected: BackendTest(); @@ -73,8 +76,13 @@ protected: static bool matchesEnvironment(Backend backend); static bool matchesEnvironment(OperatingSystem operatingSystem); - static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend); private: + // Adds all the images that failed an ImageExpectation to the XML metadata for the current tests + // case. Add --gtest_output=xml as a command line argument to generate a test_detail.xml file in + // the directory where the tests are run. + static void recordFailedImages(); + + static std::vector sFailedImages; filament::backend::Driver* driver = nullptr; filament::backend::CommandBufferQueue commandBufferQueue; diff --git a/filament/backend/test/ImageExpectations.cpp b/filament/backend/test/ImageExpectations.cpp index 0ad4ce2543..054b118792 100644 --- a/filament/backend/test/ImageExpectations.cpp +++ b/filament/backend/test/ImageExpectations.cpp @@ -21,6 +21,7 @@ #include "utils/Hash.h" #include +#include "BackendTest.h" #include "backend/PixelBufferDescriptor.h" #include "private/backend/DriverApi.h" @@ -32,6 +33,8 @@ #endif +namespace test { + ScreenshotParams::ScreenshotParams(int width, int height, std::string fileName, uint32_t expectedHash, bool isSrgb) : mWidth(width), @@ -80,6 +83,10 @@ std::string ScreenshotParams::expectedFilePath() const { return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName()); } +const std::string ScreenshotParams::filePrefix() const { + return mFileName; +} + ImageExpectation::ImageExpectation(const char* fileName, int lineNumber, filament::backend::DriverApi& api, ScreenshotParams params, filament::backend::RenderTargetHandle renderTarget) @@ -113,7 +120,11 @@ void ImageExpectation::compareImage() const { #ifndef FILAMENT_IOS LoadedPng loadedImage(mParams.expectedFilePath()); uint32_t loadedImageHash = loadedImage.hash(); - EXPECT_THAT(actualHash, testing::Eq(loadedImageHash)) << mParams.expectedFileName(); + auto compareToImageMatcher = testing::Eq(loadedImageHash); + if (!testing::Matches(compareToImageMatcher)(actualHash)) { + BackendTest::markImageAsFailure(mParams.filePrefix()); + } + EXPECT_THAT(actualHash, compareToImageMatcher) << mParams.expectedFileName(); #endif // For builds that can't load PNGs (currently iOS only) use the expected hash. EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash())) << mParams.expectedFileName(); @@ -237,3 +248,5 @@ uint32_t LoadedPng::hash() const { const std::vector& LoadedPng::bytes() const { return mBytes; } + +} // namespace test diff --git a/filament/backend/test/ImageExpectations.h b/filament/backend/test/ImageExpectations.h index 0432e00b7f..c3a293184f 100644 --- a/filament/backend/test/ImageExpectations.h +++ b/filament/backend/test/ImageExpectations.h @@ -35,6 +35,8 @@ do { \ screenshotParams); \ } while (0) +namespace test { + /** * Stores user-provided configuration values for an image expectation */ @@ -54,6 +56,7 @@ public: static std::string expectedDirectoryPath(); std::string expectedFileName() const; std::string expectedFilePath() const; + const std::string filePrefix() const; private: int mWidth; @@ -153,4 +156,6 @@ private: std::vector> mExpectations; }; +} // namespace test + #endif //TNT_IMAGE_EXPECTATIONS_H diff --git a/filament/backend/test/move_actual_images_to_expected.py b/filament/backend/test/move_actual_images_to_expected.py index 612882a370..a76a30350b 100644 --- a/filament/backend/test/move_actual_images_to_expected.py +++ b/filament/backend/test/move_actual_images_to_expected.py @@ -1,62 +1,143 @@ -import os, shutil, argparse, typing - -def match_sufffix(file_name: str, suffix: str, accepted_prefixes: typing.List[str]) -> str: - """ - Check if the file name is one of the searched for ones with the given suffix and if so return it. - :param accepted_prefixes: If None accepts any prefix - :return: file_name with the suffix removed or "" if it doesn't match. This does mean a string that - is just the suffix is considered to not match as it will return the empty string. - """ - if file_name.endswith(suffix): - prefix = file_name.removesuffix(suffix) - if accepted_prefixes is None or prefix in accepted_prefixes: - return prefix - return "" +import os, shutil, argparse, typing, xml.etree.ElementTree, subprocess, platform -def replace_file_names(path: str, removed: str, replacement: str = "", output_path: str = "", - prefixes: typing.List[str] = None): - if not output_path: - output_path = path - for file_name in os.listdir(path=path): - prefix = match_sufffix(file_name, removed, prefixes) - if prefix: - # Remove the prefix from the list so that prefixes is the list of intended but not yet found - # files. - if prefixes is not None: - prefixes.remove(prefix) - new_file_name = prefix + replacement - new_file_path = os.path.join(output_path, new_file_name) - old_file_path = os.path.join(path, file_name) - print(f'{old_file_path} to {new_file_path}') - shutil.move(old_file_path, new_file_path) - if prefixes is not None: - for unfound_prefix in prefixes: - print(f'Failed to find {unfound_prefix}_actual.png') +class TestResults(object): + ACTUAL_SUFFIX = '_actual.png' + EXPECTED_SUFFIX = '.png' + + def __init__(self, results_directory: str, source_expected_directory: str): + self.results_directory = results_directory + self.actual_directory = os.path.join(self.results_directory, 'images', 'actual_images') + self.expected_directory = os.path.join(self.results_directory, 'images', 'expected_images') + self.source_expected_directory = source_expected_directory + + def get_latest_failed_images(self) -> typing.List[str]: + failed_images = [] + xml_tree = xml.etree.ElementTree.parse( + os.path.join(self.results_directory, 'test_detail.xml')) + testsuites = xml_tree.getroot() + for testsuite in testsuites.findall('testsuite'): + for testcase in testsuite.findall('testcase'): + for properties in testcase.findall('properties'): + for property in properties.findall('property'): + if property.get('name') == 'FailedImages': + failed_images.extend(property.get('value').split(',')) + + return failed_images + + def handle_failed_image(self, failed_image): + self.show_images(failed_image) + print(f'Update {failed_image}\'s expected image? y/n') + while True: + user_input = input() + if user_input == 'y': + self.move_actual_to_source([failed_image]) + break + elif user_input == 'n': + break + + def handle_all_failed_images(self): + for failed_image in self.get_latest_failed_images(): + self.handle_failed_image(failed_image) + + def show_images(self, failed_image): + # TODO: Test more on non-mac systems + open_command: str + os_name = platform.system().lower() + if 'windows' in os_name: + open_command = 'start' + elif 'osx' in os_name or 'darwin' in os_name: + open_command = 'open' + else: + open_command = 'xdg-open' + + subprocess.run( + [open_command, + os.path.join(self.actual_directory, failed_image + TestResults.ACTUAL_SUFFIX)]) + subprocess.run( + [open_command, + os.path.join(self.expected_directory, failed_image + TestResults.EXPECTED_SUFFIX)]) + + def move_actual_to_source(self, file_prefixes: typing.List[str]): + replace_file_names(path=self.actual_directory, removed=TestResults.ACTUAL_SUFFIX, + replacement=TestResults.EXPECTED_SUFFIX, + output_path=self.source_expected_directory, prefixes=file_prefixes) + + def batch_move(self, prefixes: typing.Optional[typing.List[str]] = None): + replace_file_names(path=self.actual_directory, removed=TestResults.ACTUAL_SUFFIX, + replacement=TestResults.EXPECTED_SUFFIX, + output_path=self.source_expected_directory, prefixes=prefixes) + + +def match_suffix(file_name: str, suffix: str, accepted_prefixes: typing.List[str]) -> str: + """ + Check if the file name is one of the searched for ones with the given suffix and if so return + it. + :param accepted_prefixes: If None accepts any prefix + :return: file_name with the suffix removed or "" if it doesn't match. This does mean a string + that is just the suffix is considered to not match as it will return the empty string. + """ + if file_name.endswith(suffix): + prefix = file_name.removesuffix(suffix) + if accepted_prefixes is None or prefix in accepted_prefixes: + return prefix + return '' + + +def replace_file_names(path: str, removed: str, replacement: str = '', output_path: str = '', + prefixes: typing.Optional[typing.List[str]] = None): + if not output_path: + output_path = path + for file_name in os.listdir(path=path): + prefix = match_suffix(file_name, removed, prefixes) + if prefix: + # Remove the prefix from the list so that prefixes is the list of intended but not yet + # found files. + if prefixes is not None: + prefixes.remove(prefix) + new_file_name = prefix + replacement + new_file_path = os.path.join(output_path, new_file_name) + old_file_path = os.path.join(path, file_name) + print(f'{old_file_path} to {new_file_path}') + shutil.copyfile(old_file_path, new_file_path) + if prefixes is not None: + for unfound_prefix in prefixes: + print(f'Failed to find {unfound_prefix}_actual.png') + if __name__ == "__main__": - parser = argparse.ArgumentParser(prog='Backend Test File Renamer', - description='Moves actual generated test images to the expected ' - 'images directory, to update the test requirements. ' - 'test_cases accepts multiple arguments that should ' - 'be the name of the expected image file without the ' - '.png suffix. Also --all can be passed to copy all ' - 'images.\n' - 'Remember to sync CMake after running this to move ' - 'the new expected images to the binary directory.') - parser.add_argument('-i', '--input_path') - parser.add_argument('-o', '--output_path', default="./expected_images") - parser.add_argument('-t', '--test_cases', action='extend', nargs='*') - parser.add_argument('-a', '--all', action='store_true') + parser = argparse.ArgumentParser(prog='Backend Test File Renamer', + description='Moves actual generated test images to the ' + 'expected images directory, to update the test ' + 'requirements. test_cases accepts multiple ' + 'arguments that should be the name of the ' + 'expected image file without the .png suffix. ' + 'Also --all can be passed to copy all images.\n' + 'Remember to sync CMake after running this to ' + 'move the new expected images to the binary ' + 'directory.') + parser.add_argument('-r', '--results_path') + parser.add_argument('-s', '--source_expected_path', default="./expected_images") + # The mutually exclusive options for how to process the actual images + parser.add_argument('-b', '--batch', action='extend', nargs='*') + parser.add_argument('-a', '--all', action='store_true') + parser.add_argument('-t', '--tests', action='store_true') + parser.add_argument('-c', '--compare', action='extend', nargs='*') - args = parser.parse_args() - input_path = "." - if args.input_path: - input_path = args.input_path + args = parser.parse_args() + if not args.results_path: + raise AssertionError("No result path provided") + results_path = args.results_path - prefixes = args.test_cases - if args.all: - prefixes = None + results = TestResults(results_directory=results_path, + source_expected_directory=args.source_expected_path) - replace_file_names(path=input_path, output_path=args.output_path, removed="_actual.png", - replacement=".png", prefixes=prefixes) + if args.all: + results.batch_move() + elif args.tests: + results.handle_all_failed_images() + elif args.compare: + for file_prefix in args.compare: + results.show_images(file_prefix) + else: + results.batch_move(args.batch) From 28ecf5c35d2323dd7db762fe55641d4bd6d0df10 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Wed, 7 May 2025 14:20:22 -0700 Subject: [PATCH 26/34] renderdiff: add golden repo support (#8689) - Add GoldenManager to manage access to the repo containing the goldens - Add tif comparison code - Enable comparison by default for actual test --- .github/workflows/presubmit.yml | 12 +- test/renderdiff/src/golden_manager.py | 80 ++++++++++++ test/renderdiff/src/image_diff.py | 29 +++++ test/renderdiff/src/run.py | 117 ++++++++++++------ test/renderdiff/src/workflow_presubmit_msg.py | 3 +- test/renderdiff/test.sh | 5 +- 6 files changed, 201 insertions(+), 45 deletions(-) create mode 100644 test/renderdiff/src/golden_manager.py create mode 100644 test/renderdiff/src/image_diff.py diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index 42d2af00a9..f9b738f154 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -124,13 +124,15 @@ jobs: - uses: ./.github/actions/mac-prereq - name: Cache Mesa and deps id: mesa-cache - uses: actions/cache@v4 # Use a specific version + uses: actions/cache@v4 with: path: mesa key: ${{ runner.os }}-mesa-deps-2-${{ vars.MESA_VERSION }} - - name: Get Mesa - id: mesa-prereq - run: bash test/utils/get_mesa.sh + - name: Prerequisites + id: prereqs + run: | + bash test/utils/get_mesa.sh + pip install tifffile numpy - name: Run Test run: bash test/renderdiff/test.sh - uses: actions/upload-artifact@v4 @@ -151,7 +153,7 @@ jobs: - name: Run test run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl* - code-correcteness: + code-correctness: name: code-correctness runs-on: 'macos-14-xlarge' steps: diff --git a/test/renderdiff/src/golden_manager.py b/test/renderdiff/src/golden_manager.py new file mode 100644 index 0000000000..6daabc1fda --- /dev/null +++ b/test/renderdiff/src/golden_manager.py @@ -0,0 +1,80 @@ +import os +import shutil + +from utils import execute, ArgParseImpl + +GOLDENS_DIR = 'renderdiff' + +class GoldenManager: + def __init__(self, working_dir, access_token=None): + self.working_dir_ = working_dir + self.access_token_ = access_token + assert os.path.isdir(self.working_dir_),\ + f"working directory {self.working_dir_} does not exist" + self._prepare() + + def _assets_dir(self): + return os.path.join(self.working_dir_, "filament-assets") + + def _prepare(self): + assets_dir = self._assets_dir() + if not os.path.exists(assets_dir): + access_token_part = '' + if self.access_token_: + access_token_part = f'x-access-token:{self.access_token_}@' + execute( + f'git clone --depth=1 https://{access_token_part}github.com/google/filament-assets.git', + cwd=self.working_dir_) + else: + self.update() + + def update(self): + self._git_exec('fetch') + self._git_exec('checkout main') + self._git_exec('rebase') + + def _git_exec(self, cmd): + execute(f'git {cmd}', cwd=self._assets_dir(), capture_output=False) + + def merge_to_main(self, branch, push_to_remote=False): + self.update() + assets_dir = self._assets_dir() + self._git_exec(f'checkout main') + self._git_exec(f'merge --no-ff {branch}') + if push_to_remote and self.access_token_: + self._git_exec(f'push origin main') + + def source_from_and_commit(self, src_dir, commit_msg, branch, push_to_remote=False): + assets_dir = self._assets_dir() + self._git_exec(f'checkout main') + # Force create the branch (note will overwrite the old branch) + self._git_exec(f'switch -C {branch}') + rdiff_dir = os.path.join(assets_dir, GOLDENS_DIR) + execute(f'rm -rf {rdiff_dir}') + execute(f'mkdir -p {rdiff_dir}') + shutil.copytree(src_dir, rdiff_dir, dirs_exist_ok=True) + self._git_exec(f'add {GOLDENS_DIR}') + + TMP_GOLDEN_COMMIT_FILE = '/tmp/golden_commit.txt' + + with open(TMP_GOLDEN_COMMIT_FILE, 'w') as f: + f.write(commit_msg) + self._git_exec(f'commit -F {TMP_GOLDEN_COMMIT_FILE}') + if push_to_remote and self.access_token_: + self._git_exec(f'push -f origin ${branch}') + + def download_to(self, dest_dir, branch='main'): + assets_dir = self._assets_dir() + execute(f'mkdir -p {dest_dir}') + rdiff_dir = os.path.join(assets_dir, GOLDENS_DIR) + shutil.copytree(rdiff_dir, dest_dir, dirs_exist_ok=True) + +# For testing only +if __name__ == "__main__": + golden_manager = GoldenManager(os.getcwd()) + # golden_manager.source_from_and_commit( + # os.path.join(os.getcwd(), 'out/renderdiff_tests'), + # 'First commit (local)', + # branch='branch-test') + # golden_manager.merge_to_main('branch-test', push_to_remote=True) + # golden_manager.download_to(os.path.join(os.getcwd(), 'tmp/goldens')) diff --git a/test/renderdiff/src/image_diff.py b/test/renderdiff/src/image_diff.py new file mode 100644 index 0000000000..b4c3d3eedb --- /dev/null +++ b/test/renderdiff/src/image_diff.py @@ -0,0 +1,29 @@ +import tifffile +import numpy + +def same_image(tiff_file_a, tiff_file_b): + try: + img1_data = tifffile.imread(tiff_file_a) + img2_data = tifffile.imread(tiff_file_b) + + # If the dimensions (height, width, number of channels, number of pages/frames) + # are different, the images are not the same. + if img1_data.shape != img2_data.shape: + print(f"Images have different shapes: {img1_data.shape} vs {img2_data.shape}") + return False + + # numpy.array_equal() checks if two arrays have the same shape and elements. + if numpy.array_equal(img1_data, img2_data): + return True + else: + return False + + except FileNotFoundError: + print(f"Error: One or both files not found ('{file_path1}', '{file_path2}').") + return False + except tifffile.TiffFileError as e: + print(f"Error: One or both files are not valid TIFF files or could not be read. Details: {e}") + return False + except Exception as e: + print(f"An unexpected error occurred: {e}") + return False diff --git a/test/renderdiff/src/run.py b/test/renderdiff/src/run.py index 15c5663eba..63334faa03 100644 --- a/test/renderdiff/src/run.py +++ b/test/renderdiff/src/run.py @@ -14,9 +14,13 @@ import sys import os +import json +import glob from utils import execute, ArgParseImpl from parse_test_json import parse_test_config_from_path +from golden_manager import GoldenManager +from image_diff import same_image def important_print(msg): lines = msg.split('\n') @@ -28,8 +32,16 @@ def important_print(msg): print(information) print('-' * (max_len + 8)) -def render_test(gltf_viewer, test_config, output_dir, - opengl_lib=None, vk_icd=None): +RESULT_OK = 'ok' +RESULT_FAILED_TO_RENDER = 'failed-to-render' +RESULT_FAILED_IMAGE_DIFF = 'failed-image-diff' +RESULT_FAILED_NO_GOLDEN = 'failed-no-golden' + +def run_test(gltf_viewer, + test_config, + output_dir, + opengl_lib=None, + vk_icd=None): assert os.path.isdir(output_dir), f"output directory {output_dir} does not exist" assert os.access(gltf_viewer, os.X_OK) @@ -60,43 +72,46 @@ def render_test(gltf_viewer, test_config, output_dir, important_print(f'Rendering {test_desc}') - res, _ = execute(f'{gltf_viewer} -a {backend} --batch={test_json_path} -e {model_path} --headless', - env=env, capture_output=False) + out_code, _ = execute( + f'{gltf_viewer} -a {backend} --batch={test_json_path} -e {model_path} --headless', + env=env, capture_output=False + ) - if res == 0: - execute(f'mv -f {test.name}0.tif {named_output_dir}/{out_name}.tif', capture_output=False) - execute(f'mv -f {test.name}0.json {named_output_dir}/{test.name}.json', capture_output=False) + result = '' + if out_code == 0: + result = RESULT_OK + out_tif_basename = f'{out_name}.tif' + out_tif_name = f'{named_output_dir}/{out_tif_basename}' + execute(f'mv -f {test.name}0.tif {out_tif_name}', capture_output=False) + execute(f'mv -f {test.name}0.json {named_output_dir}/{test.name}.json', + capture_output=False) else: - important_print(f'{test_desc} failed with error={res}') - print('') + result = RESULT_FAILED_TO_RENDER + important_print(f'{test_desc} rendering failed with error={out_code}') - results.append((out_name, res)) - return results + results.append({ + 'name': out_name, + 'result': result, + 'result_code': out_code, + }) + return named_output_dir, results -GOLDENS_DIR = 'renderdiff_goldens' +def compare_goldens(render_results, output_dir, goldens): + for result in render_results: + if result['result'] != RESULT_OK: + continue -# We pull the goldens from the filament-assets repo -def pull_goldens(output_dir): - assert os.path.isdir(output_dir), f"output directory {output_dir} does not exist" - golden_dir = os.path.join(output_dir, "golden") - assets_dir = os.path.join(output_dir, "filament-assets") + out_tif_basename = f"{result['name']}.tif" + out_tif_name = f'{output_dir}/{out_tif_basename}' + golden_path = goldens.get(out_tif_basename) + if not golden_path: + result['result'] = RESULT_FAILED_NO_GOLDEN + result['result_code'] = 1 + elif not same_image(golden_path, out_tif_name): + result['result'] = RESULT_FAILED_IMAGE_DIFF + result['result_code'] = 1 - if not os.path.exists(assets_dir): - execute('git clone --depth 1 git@github.com:google/filament-assets.git', cwd=output_dir) - else: - execute('git fetch', cwd=assets_dir) - execute('git checkout main ', cwd=assets_dir) - execute('git rebase', cwd=assets_dir) - - if os.path.exists(golden_dir): - execute('rm -f goldens/*', cwd=output_dir) - execute(f'cp filament-assets/{GOLDENS_DIR}/* goldens', cwd=output_dir) - -def push_goldens(output_dir, test_name, filter_func=lambda a:True): - for test in test_config.tests: - for backend in test_config.backends: - for model in test.models: - pass + return render_results if __name__ == "__main__": parser = ArgParseImpl() @@ -105,12 +120,40 @@ if __name__ == "__main__": parser.add_argument('--output_dir', help='Output Directory', required=True) parser.add_argument('--opengl_lib', help='Path to the folder containing OpenGL driver lib (for LD_LIBRARY_PATH)') parser.add_argument('--vk_icd', help='Path to VK ICD file') + parser.add_argument('--golden_branch', help='Branch of the golden repo to compare against') args, _ = parser.parse_known_args(sys.argv[1:]) test = parse_test_config_from_path(args.test) - render_result = render_test(args.gltf_viewer, test, args.output_dir, opengl_lib=args.opengl_lib, vk_icd=args.vk_icd) - failed = [f' {tname}' for tname, res in render_result if res != 0] - success_count = len(render_result) - len(failed ) - important_print(f'Successfully rendered {success_count} / {len(render_result)}' + + output_dir, results = \ + run_test(args.gltf_viewer, + test, + args.output_dir, + opengl_lib=args.opengl_lib, + vk_icd=args.vk_icd) + + # The presence of this argument indicates comparison against a set of goldens. + if args.golden_branch: + # prepare goldens working directory + tmp_golden_dir = '/tmp/renderdiff-goldens' + execute(f'mkdir -p {tmp_golden_dir}') + + # Download the golden repo into the current working directory + golden_manager = GoldenManager(os.getcwd()) + golden_manager.download_to(tmp_golden_dir, branch=args.golden_branch) + + goldens = { + os.path.basename(fpath) : fpath for fpath in \ + glob.glob(f'{os.path.join(tmp_golden_dir, test.name)}/**/*.tif', recursive=True) + } + results = compare_goldens(results, output_dir, goldens) + + + with open(f'{output_dir}/results.json', 'w') as f: + f.write(json.dumps(results)) + execute(f'cp {args.test} {output_dir}/test.json') + + failed = [f" {k['name']}" for k in results if k['result'] != RESULT_OK] + success_count = len(results) - len(failed) + important_print(f'Successfully tested {success_count} / {len(results)}' + ('\nFailed:\n' + ('\n'.join(failed)) if len(failed) > 0 else '')) diff --git a/test/renderdiff/src/workflow_presubmit_msg.py b/test/renderdiff/src/workflow_presubmit_msg.py index 06d8975364..e971aa7b33 100644 --- a/test/renderdiff/src/workflow_presubmit_msg.py +++ b/test/renderdiff/src/workflow_presubmit_msg.py @@ -25,7 +25,8 @@ def get_last_commit(): return ( commit.split(' ')[1], title.strip(), - desc) + desc + ) def sanitized_split(line, split_atom='\n'): return list(filter(lambda x: len(x) > 0, map(lambda x: x.strip(), line.split(split_atom)))) diff --git a/test/renderdiff/test.sh b/test/renderdiff/test.sh index 354cdf6236..d219419a58 100755 --- a/test/renderdiff/test.sh +++ b/test/renderdiff/test.sh @@ -43,9 +43,10 @@ function prepare_mesa() { set -ex && prepare_mesa && \ mkdir -p ${OUTPUT_DIR} && \ - CXX=`which clang++` CC=`which clang` ./build.sh -X ${MESA_DIR} -p desktop debug gltf_viewer && \ + CXX=`which clang++` CC=`which clang` ./build.sh -f -X ${MESA_DIR} -p desktop debug gltf_viewer && \ python3 ${RENDERDIFF_TEST_DIR}/src/run.py \ --gltf_viewer="$(pwd)/out/cmake-debug/samples/gltf_viewer" \ --test=${RENDERDIFF_TEST_DIR}/tests/presubmit.json \ --output_dir=${OUTPUT_DIR} \ - --opengl_lib=${MESA_LIB_DIR} + --opengl_lib=${MESA_LIB_DIR} \ + --golden_branch=main From f10d22656582671e1c587e01cb68a9869198a5e9 Mon Sep 17 00:00:00 2001 From: Daisuke Kasuga <40908783+dkasuga@users.noreply.github.com> Date: Fri, 9 May 2025 00:43:40 +0900 Subject: [PATCH 27/34] Add getter functions for settings to build ColorGrading object (#8699) * add getter methods for ColorGrading builder settings * add tone mapper impl clone funcs * update the release notes --------- Co-authored-by: Daisuke Kasuga --- NEW_RELEASE_NOTES.md | 2 +- .../src/main/cpp/ColorGrading.cpp | 6 +- .../google/android/filament/ColorGrading.java | 6 +- filament/include/filament/ColorGrading.h | 124 +++++++++++++- filament/include/filament/ToneMapper.h | 28 +++- filament/src/ToneMapper.cpp | 50 +++++- filament/src/details/ColorGrading.cpp | 155 ++++++++++++++++-- filament/src/details/ColorGrading.h | 2 + libs/viewer/src/Settings.cpp | 4 +- 9 files changed, 348 insertions(+), 29 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 7028f80197..9c24d8a078 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -7,5 +7,5 @@ for next branch cut* header. appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut - - materials: remove dependence on per-view descset layout from filamat. [⚠️ **New Material Version**] +- `ColorGrading::Builder::toneMapper` now takes a `shared_ptr` diff --git a/android/filament-android/src/main/cpp/ColorGrading.cpp b/android/filament-android/src/main/cpp/ColorGrading.cpp index c2d3711975..34a07888c4 100644 --- a/android/filament-android/src/main/cpp/ColorGrading.cpp +++ b/android/filament-android/src/main/cpp/ColorGrading.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -67,8 +68,9 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_ColorGrading_nBuilderToneMapper(JNIEnv*, jclass, jlong nativeBuilder, jlong toneMapper_) { ColorGrading::Builder* builder = (ColorGrading::Builder*) nativeBuilder; - const ToneMapper* toneMapper = (const ToneMapper*) toneMapper_; - builder->toneMapper(toneMapper); + ToneMapper* toneMapper = reinterpret_cast(toneMapper_); + std::shared_ptr toneMapperCopy(toneMapper->clone()); + builder->toneMapper(toneMapperCopy); } #pragma clang diagnostic push diff --git a/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java b/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java index e0397b199b..13aeb35f46 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java +++ b/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java @@ -214,9 +214,9 @@ public class ColorGrading { * * The default tone mapping operator is {@link ToneMapper.ACESLegacy}. * - * The specified tone mapper must have a lifecycle that exceeds the lifetime of - * this builder. Since the build(Engine&) method is synchronous, it is safe to - * delete the tone mapper object after that finishes executing. + * The copy of the specified tone mapper is set to this builder. It is safe to delete + * the original tone mapper object while the copied one is held by the built ColorGrading + * object. * * @param toneMapper The tone mapping operator to apply to the HDR color buffer * diff --git a/filament/include/filament/ColorGrading.h b/filament/include/filament/ColorGrading.h index 3e4916fbb2..ac5276dccd 100644 --- a/filament/include/filament/ColorGrading.h +++ b/filament/include/filament/ColorGrading.h @@ -21,13 +21,17 @@ #include #include +#include #include #include +#include +#include #include #include +#include namespace filament { @@ -201,15 +205,14 @@ public: * * The default tone mapping operator is ACESLegacyToneMapper. * - * The specified tone mapper must have a lifecycle that exceeds the lifetime of - * this builder. Since the build(Engine&) method is synchronous, it is safe to - * delete the tone mapper object after that finishes executing. + * The ownership of the specified tone mapper is shared with the builder and built + * ColorGrading object. * * @param toneMapper The tone mapping operator to apply to the HDR color buffer * * @return This Builder, for chaining calls */ - Builder& toneMapper(ToneMapper const* UTILS_NULLABLE toneMapper) noexcept; + Builder& toneMapper(std::shared_ptr toneMapper) noexcept; /** * Selects the tone mapping operator to apply to the HDR color buffer as the last @@ -487,7 +490,118 @@ public: friend class FColorGrading; }; -protected: + /** Returns the quality level used to create this ColorGrading object. */ + QualityLevel getQuality() const noexcept; + + /** Returns the LUT format used to create this ColorGrading object. */ + LutFormat getLutFormat() const noexcept; + + /** Returns the LUT dimensions used to create this ColorGrading object. */ + uint8_t getLutDimensions() const noexcept; + + /** Returns the tone mapper used to create this ColorGrading object. */ + const ToneMapper& getToneMapper() const noexcept; + + /** Returns whether luminance scaling was enabled during creation. */ + bool isLuminanceScalingEnabled() const noexcept; + + /** Returns whether gamut mapping was enabled during creation. */ + bool isGamutMappingEnabled() const noexcept; + + /** Returns the exposure value used to create this ColorGrading object. */ + float getExposure() const noexcept; + + /** Returns the night adaptation value used to create this ColorGrading object. */ + float getNightAdaptation() const noexcept; + + /** Returns the white balance temperature used to create this ColorGrading object. */ + float getWhiteBalanceTemperature() const noexcept; + + /** Returns the white balance tint used to create this ColorGrading object. */ + float getWhiteBalanceTint() const noexcept; + + /** Returns the channel mixer output for the red channel. */ + math::float3 getChannelMixerOutRed() const noexcept; + + /** Returns the channel mixer output for the green channel. */ + math::float3 getChannelMixerOutGreen() const noexcept; + + /** Returns the channel mixer output for the blue channel. */ + math::float3 getChannelMixerOutBlue() const noexcept; + + /** Returns the shadows adjustment used to create this ColorGrading object. */ + math::float3 getShadows() const noexcept; + + /** Returns the midtones adjustment used to create this ColorGrading object. */ + math::float3 getMidtones() const noexcept; + + /** Returns the highlights adjustment used to create this ColorGrading object. */ + math::float3 getHighlights() const noexcept; + + /** Returns the shadow/midtones/highlights ranges used to create this ColorGrading object. */ + math::float4 getShadowMidtonesHighlightsRanges() const noexcept; + + /** Returns the slope adjustment used to create this ColorGrading object. */ + math::float3 getSlope() const noexcept; + + /** Returns the offset adjustment used to create this ColorGrading object. */ + math::float3 getOffset() const noexcept; + + /** Returns the power adjustment used to create this ColorGrading object. */ + math::float3 getPower() const noexcept; + + /** Returns the contrast value used to create this ColorGrading object. */ + float getContrast() const noexcept; + + /** Returns the vibrance value used to create this ColorGrading object. */ + float getVibrance() const noexcept; + + /** Returns the saturation value used to create this ColorGrading object. */ + float getSaturation() const noexcept; + + /** Returns the shadow gamma curve adjustment used to create this ColorGrading object. */ + math::float3 getCurvesShadowGamma() const noexcept; + + /** Returns the mid-point curve adjustment used to create this ColorGrading object. */ + math::float3 getCurvesMidPoint() const noexcept; + + /** Returns the highlight scale curve adjustment used to create this ColorGrading object. */ + math::float3 getCurvesHighlightScale() const noexcept; + + /** Returns the output color space used to create this ColorGrading object. */ + const color::ColorSpace& getOutputColorSpace() const noexcept; + +protected : + struct Settings { + LutFormat lutFormat = LutFormat::INTEGER; + uint8_t lutDimensions = 32; + std::shared_ptr toneMapper = std::make_shared(); + bool luminanceScaling = false; + bool gummapMapping = false; + float exposure = 0.0f; + float nightAdaptation = 0.0f; + float whiteBalanceTemperature = 0.0f; + float whiteBalanceTint = 0.0f; + math::float3 channelMixerOutRed{1.0f, 0.0f, 0.0f}; + math::float3 channelMixerOutGreen{0.0f, 1.0f, 0.0f}; + math::float3 channelMixerOutBlue{0.0f, 0.0f, 1.0f}; + math::float3 shadows{1.0f, 1.0f, 1.0f}; + math::float3 midtones{1.0f, 1.0f, 1.0f}; + math::float3 highlights{1.0f, 1.0f, 1.0f}; + math::float4 ShadowMidtonesHighlightsRanges{0.0f, 0.333f, 0.55f, 1.0f}; + math::float3 slope{1.0f}; + math::float3 offset{0.0f}; + math::float3 power{1.0f}; + float contrast = 1.0f; + float vibrance = 1.0f; + float saturation = 1.0f; + math::float3 curvesShadowGamma{1.0f, 1.0f, 1.0f}; + math::float3 curvesMidPoint{1.0f, 1.0f, 1.0f}; + math::float3 curvesHighlightScale{1.0f, 1.0f, 1.0f}; + color::ColorSpace colorSpace = color::Rec709 - color::sRGB - color::D65; + }; + Settings mSettings; + // prevent heap allocation ~ColorGrading() = default; }; diff --git a/filament/include/filament/ToneMapper.h b/filament/include/filament/ToneMapper.h index cff0576eef..67d3229ff3 100644 --- a/filament/include/filament/ToneMapper.h +++ b/filament/include/filament/ToneMapper.h @@ -54,6 +54,8 @@ namespace filament { struct UTILS_PUBLIC ToneMapper { ToneMapper() noexcept; virtual ~ToneMapper() noexcept; + ToneMapper(ToneMapper const&) noexcept; + ToneMapper& operator=(ToneMapper const&) noexcept = default; /** * Maps an open domain (or "scene referred" values) color value to display @@ -69,6 +71,13 @@ struct UTILS_PUBLIC ToneMapper { */ virtual math::float3 operator()(math::float3 c) const noexcept = 0; + /** + * Creates a copy of this tone mapper instance. + * + * @return A pointer to a new ToneMapper instance that is a copy of this instance. + */ + virtual ToneMapper* clone() const noexcept = 0; + /** * If true, then this function holds that f(x) = vec3(f(x.r), f(x.g), f(x.b)) * @@ -92,8 +101,10 @@ struct UTILS_PUBLIC ToneMapper { struct UTILS_PUBLIC LinearToneMapper final : public ToneMapper { LinearToneMapper() noexcept; ~LinearToneMapper() noexcept final; + LinearToneMapper(LinearToneMapper const&) noexcept; math::float3 operator()(math::float3 c) const noexcept override; + LinearToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return true; } bool isLDR() const noexcept override { return true; } }; @@ -106,8 +117,10 @@ struct UTILS_PUBLIC LinearToneMapper final : public ToneMapper { struct UTILS_PUBLIC ACESToneMapper final : public ToneMapper { ACESToneMapper() noexcept; ~ACESToneMapper() noexcept final; + ACESToneMapper(ACESToneMapper const&) noexcept; math::float3 operator()(math::float3 c) const noexcept override; + ACESToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return false; } bool isLDR() const noexcept override { return false; } }; @@ -121,8 +134,10 @@ struct UTILS_PUBLIC ACESToneMapper final : public ToneMapper { struct UTILS_PUBLIC ACESLegacyToneMapper final : public ToneMapper { ACESLegacyToneMapper() noexcept; ~ACESLegacyToneMapper() noexcept final; + ACESLegacyToneMapper(ACESLegacyToneMapper const&) noexcept; math::float3 operator()(math::float3 c) const noexcept override; + ACESLegacyToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return false; } bool isLDR() const noexcept override { return false; } }; @@ -136,8 +151,10 @@ struct UTILS_PUBLIC ACESLegacyToneMapper final : public ToneMapper { struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper { FilmicToneMapper() noexcept; ~FilmicToneMapper() noexcept final; + FilmicToneMapper(FilmicToneMapper const&) noexcept; math::float3 operator()(math::float3 x) const noexcept override; + FilmicToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return true; } bool isLDR() const noexcept override { return false; } }; @@ -150,8 +167,10 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper { struct UTILS_PUBLIC PBRNeutralToneMapper final : public ToneMapper { PBRNeutralToneMapper() noexcept; ~PBRNeutralToneMapper() noexcept final; + PBRNeutralToneMapper(PBRNeutralToneMapper const&) noexcept; math::float3 operator()(math::float3 x) const noexcept override; + virtual PBRNeutralToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return false; } bool isLDR() const noexcept override { return false; } }; @@ -173,8 +192,10 @@ struct UTILS_PUBLIC AgxToneMapper final : public ToneMapper { */ explicit AgxToneMapper(AgxLook look = AgxLook::NONE) noexcept; ~AgxToneMapper() noexcept final; + AgxToneMapper(AgxToneMapper const&) noexcept; math::float3 operator()(math::float3 x) const noexcept override; + virtual AgxToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return false; } bool isLDR() const noexcept override { return false; } @@ -215,12 +236,13 @@ struct UTILS_PUBLIC GenericToneMapper final : public ToneMapper { ) noexcept; ~GenericToneMapper() noexcept final; - GenericToneMapper(GenericToneMapper const&) = delete; - GenericToneMapper& operator=(GenericToneMapper const&) = delete; + GenericToneMapper(GenericToneMapper const& rhs) noexcept; + GenericToneMapper& operator=(GenericToneMapper const& rhs) noexcept; GenericToneMapper(GenericToneMapper&& rhs) noexcept; GenericToneMapper& operator=(GenericToneMapper&& rhs) noexcept; math::float3 operator()(math::float3 x) const noexcept override; + GenericToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return true; } bool isLDR() const noexcept override { return false; } @@ -283,8 +305,10 @@ private: struct UTILS_PUBLIC DisplayRangeToneMapper final : public ToneMapper { DisplayRangeToneMapper() noexcept; ~DisplayRangeToneMapper() noexcept override; + DisplayRangeToneMapper(DisplayRangeToneMapper const&) noexcept; math::float3 operator()(math::float3 c) const noexcept override; + DisplayRangeToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return false; } bool isLDR() const noexcept override { return false; } }; diff --git a/filament/src/ToneMapper.cpp b/filament/src/ToneMapper.cpp index 5c4203dae3..636c5fb84d 100644 --- a/filament/src/ToneMapper.cpp +++ b/filament/src/ToneMapper.cpp @@ -188,7 +188,8 @@ float3 ACES(float3 color, float brightness) noexcept { #define DEFAULT_CONSTRUCTORS(A) \ A::A() noexcept = default; \ - A::~A() noexcept = default; + A::~A() noexcept = default; \ + A::A(A const&) noexcept = default; DEFAULT_CONSTRUCTORS(ToneMapper) @@ -202,6 +203,10 @@ float3 LinearToneMapper::operator()(float3 const v) const noexcept { return saturate(v); } +LinearToneMapper* LinearToneMapper::clone() const noexcept { + return new LinearToneMapper(*this); +} + //------------------------------------------------------------------------------ // ACES tone mappers //------------------------------------------------------------------------------ @@ -212,12 +217,20 @@ float3 ACESToneMapper::operator()(float3 const c) const noexcept { return aces::ACES(c, 1.0f); } +ACESToneMapper* ACESToneMapper::clone() const noexcept { + return new ACESToneMapper(*this); +} + DEFAULT_CONSTRUCTORS(ACESLegacyToneMapper) float3 ACESLegacyToneMapper::operator()(float3 const c) const noexcept { return aces::ACES(c, 1.0f / 0.6f); } +ACESLegacyToneMapper* ACESLegacyToneMapper::clone() const noexcept { + return new ACESLegacyToneMapper(*this); +} + DEFAULT_CONSTRUCTORS(FilmicToneMapper) float3 FilmicToneMapper::operator()(float3 const x) const noexcept { @@ -230,6 +243,10 @@ float3 FilmicToneMapper::operator()(float3 const x) const noexcept { return (x * (a * x + b)) / (x * (c * x + d) + e); } +FilmicToneMapper* FilmicToneMapper::clone() const noexcept { + return new FilmicToneMapper(*this); +} + //------------------------------------------------------------------------------ // PBR Neutral tone mapper //------------------------------------------------------------------------------ @@ -256,12 +273,17 @@ float3 PBRNeutralToneMapper::operator()(float3 color) const noexcept { return mix(color, float3(newPeak), g); } +PBRNeutralToneMapper* PBRNeutralToneMapper::clone() const noexcept { + return new PBRNeutralToneMapper(*this); +} + //------------------------------------------------------------------------------ // AgX tone mapper //------------------------------------------------------------------------------ AgxToneMapper::AgxToneMapper(AgxLook const look) noexcept : look(look) {} AgxToneMapper::~AgxToneMapper() noexcept = default; +AgxToneMapper::AgxToneMapper(AgxToneMapper const& rhs) noexcept = default; // These matrices taken from Blender's implementation of AgX, which works with Rec.2020 primaries. // https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBaseRec2020.py @@ -356,6 +378,10 @@ float3 AgxToneMapper::operator()(float3 v) const noexcept { return v; } +AgxToneMapper* AgxToneMapper::clone() const noexcept { + return new AgxToneMapper(*this); +} + //------------------------------------------------------------------------------ // Display range tone mapper //------------------------------------------------------------------------------ @@ -394,6 +420,10 @@ float3 DisplayRangeToneMapper::operator()(float3 const c) const noexcept { return mix(debugColors[index], debugColors[index + 1], saturate(v - float(index))); } +DisplayRangeToneMapper* DisplayRangeToneMapper::clone() const noexcept { + return new DisplayRangeToneMapper(*this); +} + //------------------------------------------------------------------------------ // Generic tone mapper //------------------------------------------------------------------------------ @@ -454,7 +484,19 @@ GenericToneMapper::~GenericToneMapper() noexcept { delete mOptions; } -GenericToneMapper::GenericToneMapper(GenericToneMapper&& rhs) noexcept : mOptions(rhs.mOptions) { +GenericToneMapper::GenericToneMapper(GenericToneMapper const& rhs) noexcept { + mOptions = new Options(*rhs.mOptions); +} + +GenericToneMapper& GenericToneMapper::operator=(GenericToneMapper const& rhs) noexcept { + if (this != &rhs) { + delete mOptions; + mOptions = new Options(*rhs.mOptions); + } + return *this; +} + +GenericToneMapper::GenericToneMapper(GenericToneMapper&& rhs) noexcept : mOptions(rhs.mOptions) { rhs.mOptions = nullptr; } @@ -469,6 +511,10 @@ float3 GenericToneMapper::operator()(float3 x) const noexcept { return mOptions->outputScale * x / (x + mOptions->inputScale); } +GenericToneMapper* GenericToneMapper::clone() const noexcept { + return new GenericToneMapper(*this); +} + float GenericToneMapper::getContrast() const noexcept { return mOptions->contrast; } float GenericToneMapper::getMidGrayIn() const noexcept { return mOptions->midGrayIn; } float GenericToneMapper::getMidGrayOut() const noexcept { return mOptions->midGrayOut; } diff --git a/filament/src/details/ColorGrading.cpp b/filament/src/details/ColorGrading.cpp index 24df25346d..4c820b9fb4 100644 --- a/filament/src/details/ColorGrading.cpp +++ b/filament/src/details/ColorGrading.cpp @@ -49,7 +49,7 @@ using namespace backend; //------------------------------------------------------------------------------ struct ColorGrading::BuilderDetails { - const ToneMapper* toneMapper = nullptr; + std::shared_ptr toneMapper = nullptr; #if defined(__clang__) #pragma clang diagnostic push @@ -173,7 +173,7 @@ ColorGrading::Builder& ColorGrading::Builder::dimensions(uint8_t const dim) noex return *this; } -ColorGrading::Builder& ColorGrading::Builder::toneMapper(const ToneMapper* toneMapper) noexcept { +ColorGrading::Builder& ColorGrading::Builder::toneMapper(std::shared_ptr toneMapper) noexcept { mImpl->toneMapper = toneMapper; return *this; } @@ -271,6 +271,110 @@ ColorGrading::Builder& ColorGrading::Builder::outputColorSpace( return *this; } +ColorGrading::LutFormat ColorGrading::getLutFormat() const noexcept { + return mSettings.lutFormat; +} + +uint8_t ColorGrading::getLutDimensions() const noexcept { + return mSettings.lutDimensions; +} + +const ToneMapper& ColorGrading::getToneMapper() const noexcept { + return *mSettings.toneMapper; +} + +bool ColorGrading::isLuminanceScalingEnabled() const noexcept { + return mSettings.luminanceScaling; +} + +bool ColorGrading::isGamutMappingEnabled() const noexcept { + return mSettings.gummapMapping; +} + +float ColorGrading::getExposure() const noexcept { + return mSettings.exposure; +} + +float ColorGrading::getNightAdaptation() const noexcept { + return mSettings.nightAdaptation; +} + +float ColorGrading::getWhiteBalanceTemperature() const noexcept { + return mSettings.whiteBalanceTemperature; +} + +float ColorGrading::getWhiteBalanceTint() const noexcept { + return mSettings.whiteBalanceTint; +} + +math::float3 ColorGrading::getChannelMixerOutRed() const noexcept { + return mSettings.channelMixerOutRed; +} + +math::float3 ColorGrading::getChannelMixerOutGreen() const noexcept { + return mSettings.channelMixerOutGreen; +} + +math::float3 ColorGrading::getChannelMixerOutBlue() const noexcept { + return mSettings.channelMixerOutBlue; +} + +math::float3 ColorGrading::getShadows() const noexcept { + return mSettings.shadows; +} + +math::float3 ColorGrading::getMidtones() const noexcept { + return mSettings.midtones; +} + +math::float3 ColorGrading::getHighlights() const noexcept { + return mSettings.highlights; +} + +math::float4 ColorGrading::getShadowMidtonesHighlightsRanges() const noexcept { + return mSettings.ShadowMidtonesHighlightsRanges; +} + +math::float3 ColorGrading::getSlope() const noexcept { + return mSettings.slope; +} + +math::float3 ColorGrading::getOffset() const noexcept { + return mSettings.offset; +} + +math::float3 ColorGrading::getPower() const noexcept { + return mSettings.power; +} + +float ColorGrading::getContrast() const noexcept { + return mSettings.contrast; +} + +float ColorGrading::getVibrance() const noexcept { + return mSettings.vibrance; +} + +float ColorGrading::getSaturation() const noexcept { + return mSettings.saturation; +} + +math::float3 ColorGrading::getCurvesShadowGamma() const noexcept { + return mSettings.curvesShadowGamma; +} + +math::float3 ColorGrading::getCurvesMidPoint() const noexcept { + return mSettings.curvesMidPoint; +} + +math::float3 ColorGrading::getCurvesHighlightScale() const noexcept { + return mSettings.curvesHighlightScale; +} + +const color::ColorSpace& ColorGrading::getOutputColorSpace() const noexcept { + return mSettings.colorSpace; +} + #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -287,30 +391,25 @@ ColorGrading* ColorGrading::Builder::build(Engine& engine) { if (needToneMapper) { switch (mImpl->toneMapping) { case ToneMapping::LINEAR: - mImpl->toneMapper = new LinearToneMapper(); + mImpl->toneMapper = std::make_shared(); break; case ToneMapping::ACES_LEGACY: - mImpl->toneMapper = new ACESLegacyToneMapper(); + mImpl->toneMapper = std::make_shared(); break; case ToneMapping::ACES: - mImpl->toneMapper = new ACESToneMapper(); + mImpl->toneMapper = std::make_shared(); break; case ToneMapping::FILMIC: - mImpl->toneMapper = new FilmicToneMapper(); + mImpl->toneMapper = std::make_shared(); break; case ToneMapping::DISPLAY_RANGE: - mImpl->toneMapper = new DisplayRangeToneMapper(); + mImpl->toneMapper = std::make_shared(); break; } } FColorGrading* colorGrading = downcast(engine).createColorGrading(*this); - if (needToneMapper) { - delete mImpl->toneMapper; - mImpl->toneMapper = nullptr; - } - return colorGrading; } @@ -886,6 +985,9 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { data, lutElementCount * elementSize, format, type, [](void* buffer, size_t, void*) { free(buffer); } }); + + // Initialize settings from builder + initializeSettings(builder, mSettings); } FColorGrading::~FColorGrading() noexcept = default; @@ -895,4 +997,33 @@ void FColorGrading::terminate(FEngine& engine) { driver.destroyTexture(mLutHandle); } +void FColorGrading::initializeSettings(const Builder& builder, Settings& settings) noexcept { + settings.lutFormat = builder->format; + settings.lutDimensions = builder->dimension; + settings.toneMapper = builder->toneMapper; + settings.luminanceScaling = builder->luminanceScaling; + settings.gummapMapping = builder->gamutMapping; + settings.exposure = builder->exposure; + settings.nightAdaptation = builder->nightAdaptation; + settings.whiteBalanceTemperature = builder->whiteBalance.x; + settings.whiteBalanceTint = builder->whiteBalance.y; + settings.channelMixerOutRed = builder->outRed; + settings.channelMixerOutGreen = builder->outGreen; + settings.channelMixerOutBlue = builder->outBlue; + settings.shadows = builder->shadows; + settings.midtones = builder->midtones; + settings.highlights = builder->highlights; + settings.ShadowMidtonesHighlightsRanges = builder->tonalRanges; + settings.slope = builder->slope; + settings.offset = builder->offset; + settings.power = builder->power; + settings.contrast = builder->contrast; + settings.vibrance = builder->vibrance; + settings.saturation = builder->saturation; + settings.curvesShadowGamma = builder->shadowGamma; + settings.curvesMidPoint = builder->midPoint; + settings.curvesHighlightScale = builder->highlightScale; + settings.colorSpace = builder->outputColorSpace; +} + } //namespace filament diff --git a/filament/src/details/ColorGrading.h b/filament/src/details/ColorGrading.h index 5ac65c129f..da0a558d8d 100644 --- a/filament/src/details/ColorGrading.h +++ b/filament/src/details/ColorGrading.h @@ -51,6 +51,8 @@ private: uint32_t mDimension; bool mIsOneDimensional; bool mIsLDR; + + static void initializeSettings(const Builder& builder, Settings& settings) noexcept; }; FILAMENT_DOWNCAST(ColorGrading) diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index a1de4ee105..fc16ef2753 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -700,7 +700,8 @@ constexpr ToneMapper* createToneMapper(const ColorGradingSettings& settings) noe } ColorGrading* createColorGrading(const ColorGradingSettings& settings, Engine* engine) { - ToneMapper* toneMapper = createToneMapper(settings); + ToneMapper* toneMapperRaw = createToneMapper(settings); + std::shared_ptr toneMapper(toneMapperRaw); ColorGrading *colorGrading = ColorGrading::Builder() .quality(settings.quality) .exposure(settings.exposure) @@ -723,7 +724,6 @@ ColorGrading* createColorGrading(const ColorGradingSettings& settings, Engine* e .gamutMapping(settings.gamutMapping) .outputColorSpace(settings.colorspace) .build(*engine); - delete toneMapper; return colorGrading; } From 7ccbdb463325bd0ce9e46f1a42d6382fcfcd13ab Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Thu, 8 May 2025 09:06:44 -0700 Subject: [PATCH 28/34] Release Filament 1.59.5 --- NEW_RELEASE_NOTES.md | 2 -- README.md | 4 ++-- RELEASE_NOTES.md | 5 +++++ android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 9c24d8a078..4a1a9c7fa7 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 -- materials: remove dependence on per-view descset layout from filamat. [⚠️ **New Material Version**] -- `ColorGrading::Builder::toneMapper` now takes a `shared_ptr` diff --git a/README.md b/README.md index 4a2d0f1f90..ddd4d0db1c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.59.4' + implementation 'com.google.android.filament:filament-android:1.59.5' } ``` @@ -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.59.4' +pod 'Filament', '~> 1.59.5' ``` ## Documentation diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e8e64b7319..c1c2f1e531 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,11 @@ 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.60.0 + +- materials: remove dependence on per-view descset layout from filamat. [⚠️ **New Material Version**] +- `ColorGrading::Builder::toneMapper` now takes a `shared_ptr` + ## v1.59.5 diff --git a/android/gradle.properties b/android/gradle.properties index 937479e8d6..24ee5c5917 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.59.4 +VERSION_NAME=1.59.5 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 5a3964716f..aa84a9aacd 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.59.4" + spec.version = "1.59.5" 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.59.4/filament-v1.59.4-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.5/filament-v1.59.5-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/web/filament-js/package.json b/web/filament-js/package.json index c4a0309378..111edbd45a 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.59.4", + "version": "1.59.5", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From b68e2a95036ccf63ab9bc11d32d16689d5b956fe Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Thu, 8 May 2025 09:06:55 -0700 Subject: [PATCH 29/34] Bump version to 1.60.0 --- README.md | 4 ++-- android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ddd4d0db1c..dd57352cc5 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.59.5' + implementation 'com.google.android.filament:filament-android:1.60.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.59.5' +pod 'Filament', '~> 1.60.0' ``` ## Documentation diff --git a/android/gradle.properties b/android/gradle.properties index 24ee5c5917..bd0c8270e0 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.59.5 +VERSION_NAME=1.60.0 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index aa84a9aacd..0c0b721385 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.59.5" + spec.version = "1.60.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.59.5/filament-v1.59.5-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.60.0/filament-v1.60.0-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/web/filament-js/package.json b/web/filament-js/package.json index 111edbd45a..6700a39fac 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.59.5", + "version": "1.60.0", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From c701a19292c89b1ff92e65ebd93dbbec5f29ccfe Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Mon, 10 Feb 2025 09:48:52 -0800 Subject: [PATCH 30/34] Temporary workaround for PlatformMetal --- filament/backend/include/backend/Platform.h | 7 +++++++ filament/backend/src/metal/PlatformMetal.mm | 3 +++ filament/include/filament/Engine.h | 9 +++++++++ filament/src/details/Engine.cpp | 2 ++ 4 files changed, 21 insertions(+) diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index 55500c7d5a..36e8efc2cb 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -149,6 +149,13 @@ public: * - PlatformEGLAndroid */ bool assertNativeWindowIsValid = false; + + /** + * The action to take if a Drawable cannot be acquired. If true, the + * frame is aborted instead of panic. This is only supported for: + * - PlatformMetal + */ + bool metalDisablePanicOnDrawableFailure = false; }; Platform() noexcept; diff --git a/filament/backend/src/metal/PlatformMetal.mm b/filament/backend/src/metal/PlatformMetal.mm index 5fd44b4ec7..83995a6543 100644 --- a/filament/backend/src/metal/PlatformMetal.mm +++ b/filament/backend/src/metal/PlatformMetal.mm @@ -45,6 +45,9 @@ PlatformMetal::~PlatformMetal() noexcept { } Driver* PlatformMetal::createDriver(void* /*sharedContext*/, const Platform::DriverConfig& driverConfig) noexcept { + pImpl->mDrawableFailureBehavior = driverConfig.metalDisablePanicOnDrawableFailure + ? DrawableFailureBehavior::ABORT_FRAME + : DrawableFailureBehavior::PANIC; return MetalDriverFactory::create(this, driverConfig); } diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 30d2ca869d..d28f836b0e 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -317,6 +317,15 @@ public: */ size_t metalUploadBufferSizeBytes = 512 * 1024; + /** + * The action to take if a Drawable cannot be acquired. + * + * Each frame rendered requires a CAMetalDrawable texture, which is + * presented on-screen at the completion of each frame. These are + * limited and provided round-robin style by the system. + */ + bool metalDisablePanicOnDrawableFailure = false; + /** * Set to `true` to forcibly disable parallel shader compilation in the backend. * Currently only honored by the GL and Metal backends. diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index cb4a11346b..2da7a0c217 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -138,6 +138,7 @@ Engine* FEngine::create(Builder const& builder) { .forceGLES2Context = instance->getConfig().forceGLES2Context, .stereoscopicType = instance->getConfig().stereoscopicType, .assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid, + .metalDisablePanicOnDrawableFailure = instance->getConfig().metalDisablePanicOnDrawableFailure, }; instance->mDriver = platform->createDriver(sharedContext, driverConfig); @@ -733,6 +734,7 @@ int FEngine::loop() { .forceGLES2Context = mConfig.forceGLES2Context, .stereoscopicType = mConfig.stereoscopicType, .assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid, + .metalDisablePanicOnDrawableFailure = mConfig.metalDisablePanicOnDrawableFailure, }; mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig); From 3b9f4df1a8dd8896dec3e6d177f792f3372612b0 Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Mon, 12 May 2025 16:43:29 +0000 Subject: [PATCH 31/34] Update MATERIAL_VERSION to 60 --- libs/filabridge/include/filament/MaterialEnums.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index abe3b1ba7d..c45f8a0acf 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -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 = 59; +static constexpr size_t MATERIAL_VERSION = 60; /** * Supported shading models From 4536752c47f4805d2dca81cc3c02827a3e847812 Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Tue, 13 May 2025 11:07:42 -0700 Subject: [PATCH 32/34] Revert "Add getter functions for settings to build ColorGrading object (#8699)" This reverts commit f10d22656582671e1c587e01cb68a9869198a5e9. --- NEW_RELEASE_NOTES.md | 1 + RELEASE_NOTES.md | 1 - .../src/main/cpp/ColorGrading.cpp | 6 +- .../google/android/filament/ColorGrading.java | 6 +- filament/include/filament/ColorGrading.h | 124 +------------- filament/include/filament/ToneMapper.h | 28 +--- filament/src/ToneMapper.cpp | 50 +----- filament/src/details/ColorGrading.cpp | 155 ++---------------- filament/src/details/ColorGrading.h | 2 - libs/viewer/src/Settings.cpp | 4 +- 10 files changed, 29 insertions(+), 348 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 4a1a9c7fa7..c3161a7700 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -7,3 +7,4 @@ for next branch cut* header. appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut + diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c1c2f1e531..218c17174b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -10,7 +10,6 @@ Instead, if you are authoring a PR for the main branch, add your release note to ## v1.60.0 - materials: remove dependence on per-view descset layout from filamat. [⚠️ **New Material Version**] -- `ColorGrading::Builder::toneMapper` now takes a `shared_ptr` ## v1.59.5 diff --git a/android/filament-android/src/main/cpp/ColorGrading.cpp b/android/filament-android/src/main/cpp/ColorGrading.cpp index 34a07888c4..c2d3711975 100644 --- a/android/filament-android/src/main/cpp/ColorGrading.cpp +++ b/android/filament-android/src/main/cpp/ColorGrading.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -68,9 +67,8 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_ColorGrading_nBuilderToneMapper(JNIEnv*, jclass, jlong nativeBuilder, jlong toneMapper_) { ColorGrading::Builder* builder = (ColorGrading::Builder*) nativeBuilder; - ToneMapper* toneMapper = reinterpret_cast(toneMapper_); - std::shared_ptr toneMapperCopy(toneMapper->clone()); - builder->toneMapper(toneMapperCopy); + const ToneMapper* toneMapper = (const ToneMapper*) toneMapper_; + builder->toneMapper(toneMapper); } #pragma clang diagnostic push diff --git a/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java b/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java index 13aeb35f46..e0397b199b 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java +++ b/android/filament-android/src/main/java/com/google/android/filament/ColorGrading.java @@ -214,9 +214,9 @@ public class ColorGrading { * * The default tone mapping operator is {@link ToneMapper.ACESLegacy}. * - * The copy of the specified tone mapper is set to this builder. It is safe to delete - * the original tone mapper object while the copied one is held by the built ColorGrading - * object. + * The specified tone mapper must have a lifecycle that exceeds the lifetime of + * this builder. Since the build(Engine&) method is synchronous, it is safe to + * delete the tone mapper object after that finishes executing. * * @param toneMapper The tone mapping operator to apply to the HDR color buffer * diff --git a/filament/include/filament/ColorGrading.h b/filament/include/filament/ColorGrading.h index ac5276dccd..3e4916fbb2 100644 --- a/filament/include/filament/ColorGrading.h +++ b/filament/include/filament/ColorGrading.h @@ -21,17 +21,13 @@ #include #include -#include #include #include -#include -#include #include #include -#include namespace filament { @@ -205,14 +201,15 @@ public: * * The default tone mapping operator is ACESLegacyToneMapper. * - * The ownership of the specified tone mapper is shared with the builder and built - * ColorGrading object. + * The specified tone mapper must have a lifecycle that exceeds the lifetime of + * this builder. Since the build(Engine&) method is synchronous, it is safe to + * delete the tone mapper object after that finishes executing. * * @param toneMapper The tone mapping operator to apply to the HDR color buffer * * @return This Builder, for chaining calls */ - Builder& toneMapper(std::shared_ptr toneMapper) noexcept; + Builder& toneMapper(ToneMapper const* UTILS_NULLABLE toneMapper) noexcept; /** * Selects the tone mapping operator to apply to the HDR color buffer as the last @@ -490,118 +487,7 @@ public: friend class FColorGrading; }; - /** Returns the quality level used to create this ColorGrading object. */ - QualityLevel getQuality() const noexcept; - - /** Returns the LUT format used to create this ColorGrading object. */ - LutFormat getLutFormat() const noexcept; - - /** Returns the LUT dimensions used to create this ColorGrading object. */ - uint8_t getLutDimensions() const noexcept; - - /** Returns the tone mapper used to create this ColorGrading object. */ - const ToneMapper& getToneMapper() const noexcept; - - /** Returns whether luminance scaling was enabled during creation. */ - bool isLuminanceScalingEnabled() const noexcept; - - /** Returns whether gamut mapping was enabled during creation. */ - bool isGamutMappingEnabled() const noexcept; - - /** Returns the exposure value used to create this ColorGrading object. */ - float getExposure() const noexcept; - - /** Returns the night adaptation value used to create this ColorGrading object. */ - float getNightAdaptation() const noexcept; - - /** Returns the white balance temperature used to create this ColorGrading object. */ - float getWhiteBalanceTemperature() const noexcept; - - /** Returns the white balance tint used to create this ColorGrading object. */ - float getWhiteBalanceTint() const noexcept; - - /** Returns the channel mixer output for the red channel. */ - math::float3 getChannelMixerOutRed() const noexcept; - - /** Returns the channel mixer output for the green channel. */ - math::float3 getChannelMixerOutGreen() const noexcept; - - /** Returns the channel mixer output for the blue channel. */ - math::float3 getChannelMixerOutBlue() const noexcept; - - /** Returns the shadows adjustment used to create this ColorGrading object. */ - math::float3 getShadows() const noexcept; - - /** Returns the midtones adjustment used to create this ColorGrading object. */ - math::float3 getMidtones() const noexcept; - - /** Returns the highlights adjustment used to create this ColorGrading object. */ - math::float3 getHighlights() const noexcept; - - /** Returns the shadow/midtones/highlights ranges used to create this ColorGrading object. */ - math::float4 getShadowMidtonesHighlightsRanges() const noexcept; - - /** Returns the slope adjustment used to create this ColorGrading object. */ - math::float3 getSlope() const noexcept; - - /** Returns the offset adjustment used to create this ColorGrading object. */ - math::float3 getOffset() const noexcept; - - /** Returns the power adjustment used to create this ColorGrading object. */ - math::float3 getPower() const noexcept; - - /** Returns the contrast value used to create this ColorGrading object. */ - float getContrast() const noexcept; - - /** Returns the vibrance value used to create this ColorGrading object. */ - float getVibrance() const noexcept; - - /** Returns the saturation value used to create this ColorGrading object. */ - float getSaturation() const noexcept; - - /** Returns the shadow gamma curve adjustment used to create this ColorGrading object. */ - math::float3 getCurvesShadowGamma() const noexcept; - - /** Returns the mid-point curve adjustment used to create this ColorGrading object. */ - math::float3 getCurvesMidPoint() const noexcept; - - /** Returns the highlight scale curve adjustment used to create this ColorGrading object. */ - math::float3 getCurvesHighlightScale() const noexcept; - - /** Returns the output color space used to create this ColorGrading object. */ - const color::ColorSpace& getOutputColorSpace() const noexcept; - -protected : - struct Settings { - LutFormat lutFormat = LutFormat::INTEGER; - uint8_t lutDimensions = 32; - std::shared_ptr toneMapper = std::make_shared(); - bool luminanceScaling = false; - bool gummapMapping = false; - float exposure = 0.0f; - float nightAdaptation = 0.0f; - float whiteBalanceTemperature = 0.0f; - float whiteBalanceTint = 0.0f; - math::float3 channelMixerOutRed{1.0f, 0.0f, 0.0f}; - math::float3 channelMixerOutGreen{0.0f, 1.0f, 0.0f}; - math::float3 channelMixerOutBlue{0.0f, 0.0f, 1.0f}; - math::float3 shadows{1.0f, 1.0f, 1.0f}; - math::float3 midtones{1.0f, 1.0f, 1.0f}; - math::float3 highlights{1.0f, 1.0f, 1.0f}; - math::float4 ShadowMidtonesHighlightsRanges{0.0f, 0.333f, 0.55f, 1.0f}; - math::float3 slope{1.0f}; - math::float3 offset{0.0f}; - math::float3 power{1.0f}; - float contrast = 1.0f; - float vibrance = 1.0f; - float saturation = 1.0f; - math::float3 curvesShadowGamma{1.0f, 1.0f, 1.0f}; - math::float3 curvesMidPoint{1.0f, 1.0f, 1.0f}; - math::float3 curvesHighlightScale{1.0f, 1.0f, 1.0f}; - color::ColorSpace colorSpace = color::Rec709 - color::sRGB - color::D65; - }; - Settings mSettings; - +protected: // prevent heap allocation ~ColorGrading() = default; }; diff --git a/filament/include/filament/ToneMapper.h b/filament/include/filament/ToneMapper.h index 67d3229ff3..cff0576eef 100644 --- a/filament/include/filament/ToneMapper.h +++ b/filament/include/filament/ToneMapper.h @@ -54,8 +54,6 @@ namespace filament { struct UTILS_PUBLIC ToneMapper { ToneMapper() noexcept; virtual ~ToneMapper() noexcept; - ToneMapper(ToneMapper const&) noexcept; - ToneMapper& operator=(ToneMapper const&) noexcept = default; /** * Maps an open domain (or "scene referred" values) color value to display @@ -71,13 +69,6 @@ struct UTILS_PUBLIC ToneMapper { */ virtual math::float3 operator()(math::float3 c) const noexcept = 0; - /** - * Creates a copy of this tone mapper instance. - * - * @return A pointer to a new ToneMapper instance that is a copy of this instance. - */ - virtual ToneMapper* clone() const noexcept = 0; - /** * If true, then this function holds that f(x) = vec3(f(x.r), f(x.g), f(x.b)) * @@ -101,10 +92,8 @@ struct UTILS_PUBLIC ToneMapper { struct UTILS_PUBLIC LinearToneMapper final : public ToneMapper { LinearToneMapper() noexcept; ~LinearToneMapper() noexcept final; - LinearToneMapper(LinearToneMapper const&) noexcept; math::float3 operator()(math::float3 c) const noexcept override; - LinearToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return true; } bool isLDR() const noexcept override { return true; } }; @@ -117,10 +106,8 @@ struct UTILS_PUBLIC LinearToneMapper final : public ToneMapper { struct UTILS_PUBLIC ACESToneMapper final : public ToneMapper { ACESToneMapper() noexcept; ~ACESToneMapper() noexcept final; - ACESToneMapper(ACESToneMapper const&) noexcept; math::float3 operator()(math::float3 c) const noexcept override; - ACESToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return false; } bool isLDR() const noexcept override { return false; } }; @@ -134,10 +121,8 @@ struct UTILS_PUBLIC ACESToneMapper final : public ToneMapper { struct UTILS_PUBLIC ACESLegacyToneMapper final : public ToneMapper { ACESLegacyToneMapper() noexcept; ~ACESLegacyToneMapper() noexcept final; - ACESLegacyToneMapper(ACESLegacyToneMapper const&) noexcept; math::float3 operator()(math::float3 c) const noexcept override; - ACESLegacyToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return false; } bool isLDR() const noexcept override { return false; } }; @@ -151,10 +136,8 @@ struct UTILS_PUBLIC ACESLegacyToneMapper final : public ToneMapper { struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper { FilmicToneMapper() noexcept; ~FilmicToneMapper() noexcept final; - FilmicToneMapper(FilmicToneMapper const&) noexcept; math::float3 operator()(math::float3 x) const noexcept override; - FilmicToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return true; } bool isLDR() const noexcept override { return false; } }; @@ -167,10 +150,8 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper { struct UTILS_PUBLIC PBRNeutralToneMapper final : public ToneMapper { PBRNeutralToneMapper() noexcept; ~PBRNeutralToneMapper() noexcept final; - PBRNeutralToneMapper(PBRNeutralToneMapper const&) noexcept; math::float3 operator()(math::float3 x) const noexcept override; - virtual PBRNeutralToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return false; } bool isLDR() const noexcept override { return false; } }; @@ -192,10 +173,8 @@ struct UTILS_PUBLIC AgxToneMapper final : public ToneMapper { */ explicit AgxToneMapper(AgxLook look = AgxLook::NONE) noexcept; ~AgxToneMapper() noexcept final; - AgxToneMapper(AgxToneMapper const&) noexcept; math::float3 operator()(math::float3 x) const noexcept override; - virtual AgxToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return false; } bool isLDR() const noexcept override { return false; } @@ -236,13 +215,12 @@ struct UTILS_PUBLIC GenericToneMapper final : public ToneMapper { ) noexcept; ~GenericToneMapper() noexcept final; - GenericToneMapper(GenericToneMapper const& rhs) noexcept; - GenericToneMapper& operator=(GenericToneMapper const& rhs) noexcept; + GenericToneMapper(GenericToneMapper const&) = delete; + GenericToneMapper& operator=(GenericToneMapper const&) = delete; GenericToneMapper(GenericToneMapper&& rhs) noexcept; GenericToneMapper& operator=(GenericToneMapper&& rhs) noexcept; math::float3 operator()(math::float3 x) const noexcept override; - GenericToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return true; } bool isLDR() const noexcept override { return false; } @@ -305,10 +283,8 @@ private: struct UTILS_PUBLIC DisplayRangeToneMapper final : public ToneMapper { DisplayRangeToneMapper() noexcept; ~DisplayRangeToneMapper() noexcept override; - DisplayRangeToneMapper(DisplayRangeToneMapper const&) noexcept; math::float3 operator()(math::float3 c) const noexcept override; - DisplayRangeToneMapper* clone() const noexcept override; bool isOneDimensional() const noexcept override { return false; } bool isLDR() const noexcept override { return false; } }; diff --git a/filament/src/ToneMapper.cpp b/filament/src/ToneMapper.cpp index 636c5fb84d..5c4203dae3 100644 --- a/filament/src/ToneMapper.cpp +++ b/filament/src/ToneMapper.cpp @@ -188,8 +188,7 @@ float3 ACES(float3 color, float brightness) noexcept { #define DEFAULT_CONSTRUCTORS(A) \ A::A() noexcept = default; \ - A::~A() noexcept = default; \ - A::A(A const&) noexcept = default; + A::~A() noexcept = default; DEFAULT_CONSTRUCTORS(ToneMapper) @@ -203,10 +202,6 @@ float3 LinearToneMapper::operator()(float3 const v) const noexcept { return saturate(v); } -LinearToneMapper* LinearToneMapper::clone() const noexcept { - return new LinearToneMapper(*this); -} - //------------------------------------------------------------------------------ // ACES tone mappers //------------------------------------------------------------------------------ @@ -217,20 +212,12 @@ float3 ACESToneMapper::operator()(float3 const c) const noexcept { return aces::ACES(c, 1.0f); } -ACESToneMapper* ACESToneMapper::clone() const noexcept { - return new ACESToneMapper(*this); -} - DEFAULT_CONSTRUCTORS(ACESLegacyToneMapper) float3 ACESLegacyToneMapper::operator()(float3 const c) const noexcept { return aces::ACES(c, 1.0f / 0.6f); } -ACESLegacyToneMapper* ACESLegacyToneMapper::clone() const noexcept { - return new ACESLegacyToneMapper(*this); -} - DEFAULT_CONSTRUCTORS(FilmicToneMapper) float3 FilmicToneMapper::operator()(float3 const x) const noexcept { @@ -243,10 +230,6 @@ float3 FilmicToneMapper::operator()(float3 const x) const noexcept { return (x * (a * x + b)) / (x * (c * x + d) + e); } -FilmicToneMapper* FilmicToneMapper::clone() const noexcept { - return new FilmicToneMapper(*this); -} - //------------------------------------------------------------------------------ // PBR Neutral tone mapper //------------------------------------------------------------------------------ @@ -273,17 +256,12 @@ float3 PBRNeutralToneMapper::operator()(float3 color) const noexcept { return mix(color, float3(newPeak), g); } -PBRNeutralToneMapper* PBRNeutralToneMapper::clone() const noexcept { - return new PBRNeutralToneMapper(*this); -} - //------------------------------------------------------------------------------ // AgX tone mapper //------------------------------------------------------------------------------ AgxToneMapper::AgxToneMapper(AgxLook const look) noexcept : look(look) {} AgxToneMapper::~AgxToneMapper() noexcept = default; -AgxToneMapper::AgxToneMapper(AgxToneMapper const& rhs) noexcept = default; // These matrices taken from Blender's implementation of AgX, which works with Rec.2020 primaries. // https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBaseRec2020.py @@ -378,10 +356,6 @@ float3 AgxToneMapper::operator()(float3 v) const noexcept { return v; } -AgxToneMapper* AgxToneMapper::clone() const noexcept { - return new AgxToneMapper(*this); -} - //------------------------------------------------------------------------------ // Display range tone mapper //------------------------------------------------------------------------------ @@ -420,10 +394,6 @@ float3 DisplayRangeToneMapper::operator()(float3 const c) const noexcept { return mix(debugColors[index], debugColors[index + 1], saturate(v - float(index))); } -DisplayRangeToneMapper* DisplayRangeToneMapper::clone() const noexcept { - return new DisplayRangeToneMapper(*this); -} - //------------------------------------------------------------------------------ // Generic tone mapper //------------------------------------------------------------------------------ @@ -484,19 +454,7 @@ GenericToneMapper::~GenericToneMapper() noexcept { delete mOptions; } -GenericToneMapper::GenericToneMapper(GenericToneMapper const& rhs) noexcept { - mOptions = new Options(*rhs.mOptions); -} - -GenericToneMapper& GenericToneMapper::operator=(GenericToneMapper const& rhs) noexcept { - if (this != &rhs) { - delete mOptions; - mOptions = new Options(*rhs.mOptions); - } - return *this; -} - -GenericToneMapper::GenericToneMapper(GenericToneMapper&& rhs) noexcept : mOptions(rhs.mOptions) { +GenericToneMapper::GenericToneMapper(GenericToneMapper&& rhs) noexcept : mOptions(rhs.mOptions) { rhs.mOptions = nullptr; } @@ -511,10 +469,6 @@ float3 GenericToneMapper::operator()(float3 x) const noexcept { return mOptions->outputScale * x / (x + mOptions->inputScale); } -GenericToneMapper* GenericToneMapper::clone() const noexcept { - return new GenericToneMapper(*this); -} - float GenericToneMapper::getContrast() const noexcept { return mOptions->contrast; } float GenericToneMapper::getMidGrayIn() const noexcept { return mOptions->midGrayIn; } float GenericToneMapper::getMidGrayOut() const noexcept { return mOptions->midGrayOut; } diff --git a/filament/src/details/ColorGrading.cpp b/filament/src/details/ColorGrading.cpp index 4c820b9fb4..24df25346d 100644 --- a/filament/src/details/ColorGrading.cpp +++ b/filament/src/details/ColorGrading.cpp @@ -49,7 +49,7 @@ using namespace backend; //------------------------------------------------------------------------------ struct ColorGrading::BuilderDetails { - std::shared_ptr toneMapper = nullptr; + const ToneMapper* toneMapper = nullptr; #if defined(__clang__) #pragma clang diagnostic push @@ -173,7 +173,7 @@ ColorGrading::Builder& ColorGrading::Builder::dimensions(uint8_t const dim) noex return *this; } -ColorGrading::Builder& ColorGrading::Builder::toneMapper(std::shared_ptr toneMapper) noexcept { +ColorGrading::Builder& ColorGrading::Builder::toneMapper(const ToneMapper* toneMapper) noexcept { mImpl->toneMapper = toneMapper; return *this; } @@ -271,110 +271,6 @@ ColorGrading::Builder& ColorGrading::Builder::outputColorSpace( return *this; } -ColorGrading::LutFormat ColorGrading::getLutFormat() const noexcept { - return mSettings.lutFormat; -} - -uint8_t ColorGrading::getLutDimensions() const noexcept { - return mSettings.lutDimensions; -} - -const ToneMapper& ColorGrading::getToneMapper() const noexcept { - return *mSettings.toneMapper; -} - -bool ColorGrading::isLuminanceScalingEnabled() const noexcept { - return mSettings.luminanceScaling; -} - -bool ColorGrading::isGamutMappingEnabled() const noexcept { - return mSettings.gummapMapping; -} - -float ColorGrading::getExposure() const noexcept { - return mSettings.exposure; -} - -float ColorGrading::getNightAdaptation() const noexcept { - return mSettings.nightAdaptation; -} - -float ColorGrading::getWhiteBalanceTemperature() const noexcept { - return mSettings.whiteBalanceTemperature; -} - -float ColorGrading::getWhiteBalanceTint() const noexcept { - return mSettings.whiteBalanceTint; -} - -math::float3 ColorGrading::getChannelMixerOutRed() const noexcept { - return mSettings.channelMixerOutRed; -} - -math::float3 ColorGrading::getChannelMixerOutGreen() const noexcept { - return mSettings.channelMixerOutGreen; -} - -math::float3 ColorGrading::getChannelMixerOutBlue() const noexcept { - return mSettings.channelMixerOutBlue; -} - -math::float3 ColorGrading::getShadows() const noexcept { - return mSettings.shadows; -} - -math::float3 ColorGrading::getMidtones() const noexcept { - return mSettings.midtones; -} - -math::float3 ColorGrading::getHighlights() const noexcept { - return mSettings.highlights; -} - -math::float4 ColorGrading::getShadowMidtonesHighlightsRanges() const noexcept { - return mSettings.ShadowMidtonesHighlightsRanges; -} - -math::float3 ColorGrading::getSlope() const noexcept { - return mSettings.slope; -} - -math::float3 ColorGrading::getOffset() const noexcept { - return mSettings.offset; -} - -math::float3 ColorGrading::getPower() const noexcept { - return mSettings.power; -} - -float ColorGrading::getContrast() const noexcept { - return mSettings.contrast; -} - -float ColorGrading::getVibrance() const noexcept { - return mSettings.vibrance; -} - -float ColorGrading::getSaturation() const noexcept { - return mSettings.saturation; -} - -math::float3 ColorGrading::getCurvesShadowGamma() const noexcept { - return mSettings.curvesShadowGamma; -} - -math::float3 ColorGrading::getCurvesMidPoint() const noexcept { - return mSettings.curvesMidPoint; -} - -math::float3 ColorGrading::getCurvesHighlightScale() const noexcept { - return mSettings.curvesHighlightScale; -} - -const color::ColorSpace& ColorGrading::getOutputColorSpace() const noexcept { - return mSettings.colorSpace; -} - #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -391,25 +287,30 @@ ColorGrading* ColorGrading::Builder::build(Engine& engine) { if (needToneMapper) { switch (mImpl->toneMapping) { case ToneMapping::LINEAR: - mImpl->toneMapper = std::make_shared(); + mImpl->toneMapper = new LinearToneMapper(); break; case ToneMapping::ACES_LEGACY: - mImpl->toneMapper = std::make_shared(); + mImpl->toneMapper = new ACESLegacyToneMapper(); break; case ToneMapping::ACES: - mImpl->toneMapper = std::make_shared(); + mImpl->toneMapper = new ACESToneMapper(); break; case ToneMapping::FILMIC: - mImpl->toneMapper = std::make_shared(); + mImpl->toneMapper = new FilmicToneMapper(); break; case ToneMapping::DISPLAY_RANGE: - mImpl->toneMapper = std::make_shared(); + mImpl->toneMapper = new DisplayRangeToneMapper(); break; } } FColorGrading* colorGrading = downcast(engine).createColorGrading(*this); + if (needToneMapper) { + delete mImpl->toneMapper; + mImpl->toneMapper = nullptr; + } + return colorGrading; } @@ -985,9 +886,6 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) { data, lutElementCount * elementSize, format, type, [](void* buffer, size_t, void*) { free(buffer); } }); - - // Initialize settings from builder - initializeSettings(builder, mSettings); } FColorGrading::~FColorGrading() noexcept = default; @@ -997,33 +895,4 @@ void FColorGrading::terminate(FEngine& engine) { driver.destroyTexture(mLutHandle); } -void FColorGrading::initializeSettings(const Builder& builder, Settings& settings) noexcept { - settings.lutFormat = builder->format; - settings.lutDimensions = builder->dimension; - settings.toneMapper = builder->toneMapper; - settings.luminanceScaling = builder->luminanceScaling; - settings.gummapMapping = builder->gamutMapping; - settings.exposure = builder->exposure; - settings.nightAdaptation = builder->nightAdaptation; - settings.whiteBalanceTemperature = builder->whiteBalance.x; - settings.whiteBalanceTint = builder->whiteBalance.y; - settings.channelMixerOutRed = builder->outRed; - settings.channelMixerOutGreen = builder->outGreen; - settings.channelMixerOutBlue = builder->outBlue; - settings.shadows = builder->shadows; - settings.midtones = builder->midtones; - settings.highlights = builder->highlights; - settings.ShadowMidtonesHighlightsRanges = builder->tonalRanges; - settings.slope = builder->slope; - settings.offset = builder->offset; - settings.power = builder->power; - settings.contrast = builder->contrast; - settings.vibrance = builder->vibrance; - settings.saturation = builder->saturation; - settings.curvesShadowGamma = builder->shadowGamma; - settings.curvesMidPoint = builder->midPoint; - settings.curvesHighlightScale = builder->highlightScale; - settings.colorSpace = builder->outputColorSpace; -} - } //namespace filament diff --git a/filament/src/details/ColorGrading.h b/filament/src/details/ColorGrading.h index da0a558d8d..5ac65c129f 100644 --- a/filament/src/details/ColorGrading.h +++ b/filament/src/details/ColorGrading.h @@ -51,8 +51,6 @@ private: uint32_t mDimension; bool mIsOneDimensional; bool mIsLDR; - - static void initializeSettings(const Builder& builder, Settings& settings) noexcept; }; FILAMENT_DOWNCAST(ColorGrading) diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index fc16ef2753..a1de4ee105 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -700,8 +700,7 @@ constexpr ToneMapper* createToneMapper(const ColorGradingSettings& settings) noe } ColorGrading* createColorGrading(const ColorGradingSettings& settings, Engine* engine) { - ToneMapper* toneMapperRaw = createToneMapper(settings); - std::shared_ptr toneMapper(toneMapperRaw); + ToneMapper* toneMapper = createToneMapper(settings); ColorGrading *colorGrading = ColorGrading::Builder() .quality(settings.quality) .exposure(settings.exposure) @@ -724,6 +723,7 @@ ColorGrading* createColorGrading(const ColorGradingSettings& settings, Engine* e .gamutMapping(settings.gamutMapping) .outputColorSpace(settings.colorspace) .build(*engine); + delete toneMapper; return colorGrading; } From 596c17ecc0a7b31ae1f13281b9dae927dafd0a85 Mon Sep 17 00:00:00 2001 From: David Neto Date: Wed, 14 May 2025 05:33:49 +0900 Subject: [PATCH 33/34] Move calls to SPV remapper to another .cpp file (#8729) --- NEW_RELEASE_NOTES.md | 2 + filament/backend/test/ShaderGenerator.cpp | 1 - libs/filamat/CMakeLists.txt | 4 +- libs/filamat/src/GLSLPostProcessor.cpp | 24 ++-------- libs/filamat/src/GLSLPostProcessor.h | 1 + libs/filamat/src/SpirvRemapWrapper.cpp | 54 +++++++++++++++++++++++ libs/filamat/src/SpirvRemapWrapper.h | 30 +++++++++++++ 7 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 libs/filamat/src/SpirvRemapWrapper.cpp create mode 100644 libs/filamat/src/SpirvRemapWrapper.h diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index c3161a7700..2829dc2571 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -8,3 +8,5 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut +- matc non-functional change: Update GLSL postprocessor to + isolate calls to SPVRemap from calls to SPIRV-Cross. diff --git a/filament/backend/test/ShaderGenerator.cpp b/filament/backend/test/ShaderGenerator.cpp index e493c444af..39bc3f9036 100644 --- a/filament/backend/test/ShaderGenerator.cpp +++ b/filament/backend/test/ShaderGenerator.cpp @@ -17,7 +17,6 @@ #include "ShaderGenerator.h" #include -#include #include #include diff --git a/libs/filamat/CMakeLists.txt b/libs/filamat/CMakeLists.txt index 4004106046..3d790b0e77 100644 --- a/libs/filamat/CMakeLists.txt +++ b/libs/filamat/CMakeLists.txt @@ -56,6 +56,7 @@ set(PRIVATE_HDRS src/MetalArgumentBuffer.h src/ShaderMinifier.h src/SpirvFixup.h + src/SpirvRemapWrapper.h src/sca/ASTHelpers.h src/sca/GLSLTools.h src/sca/builtinResource.h) @@ -71,7 +72,8 @@ set(SRCS src/sca/GLSLTools.cpp src/GLSLPostProcessor.cpp src/ShaderMinifier.cpp - src/SpirvFixup.cpp) + src/SpirvFixup.cpp + src/SpirvRemapWrapper.cpp) # ================================================================================================== # Include and target definitions diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index 52d4a30dc0..34b1cac980 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -17,7 +17,6 @@ #include "GLSLPostProcessor.h" #include -#include #include #include @@ -289,24 +288,8 @@ GLSLPostProcessor::GLSLPostProcessor(MaterialBuilder::Optimization optimization, : mOptimization(optimization), mPrintShaders(flags & PRINT_SHADERS), mGenerateDebugInfo(flags & GENERATE_DEBUG_INFO) { - // SPIRV error handler registration needs to occur only once. To avoid a race we do it up here - // in the constructor, which gets invoked before MaterialBuilder kicks off jobs. - spv::spirvbin_t::registerErrorHandler([](const std::string& str) { - slog.e << str << io::endl; - }); - - // Similar to above, we need to do a no-op remap to init a static table in the remapper before - // the jobs start using remap(). - spv::spirvbin_t remapper(0); - // We need to provide at least a valid header to not crash. - SpirvBlob spirv { - 0x07230203,// MAGIC - 0, // VERSION - 0, // GENERATOR - 0, // BOUND - 0 // SCHEMA, must be 0 - }; - remapper.remap(spirv, 0); + // This should occur only once, to avoid races. + SpirvRemapWrapperSetUp(); } GLSLPostProcessor::~GLSLPostProcessor() = default; @@ -988,8 +971,7 @@ void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) } // Remove dead module-level objects: functions, types, vars - spv::spirvbin_t remapper(0); - remapper.remap(spirv, spv::spirvbin_base_t::DCE_ALL); + SpirvRemapWrapperRemap(spirv); } void GLSLPostProcessor::fixupClipDistance( diff --git a/libs/filamat/src/GLSLPostProcessor.h b/libs/filamat/src/GLSLPostProcessor.h index a93551a776..2a78c133bd 100644 --- a/libs/filamat/src/GLSLPostProcessor.h +++ b/libs/filamat/src/GLSLPostProcessor.h @@ -23,6 +23,7 @@ #include #include "ShaderMinifier.h" +#include "SpirvRemapWrapper.h" #include diff --git a/libs/filamat/src/SpirvRemapWrapper.cpp b/libs/filamat/src/SpirvRemapWrapper.cpp new file mode 100644 index 0000000000..95fe6ae085 --- /dev/null +++ b/libs/filamat/src/SpirvRemapWrapper.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 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 "SpirvRemapWrapper.h" + +#include +#include +#include + +#include + +namespace filamat { + +void SpirvRemapWrapperSetUp() { + // SPIRV error handler registration should occur only once. + // Construct this SpirvRemapWrapper object only once. + spv::spirvbin_t::registerErrorHandler([](const std::string& str) { + utils::slog.e << str << utils::io::endl; + }); + + // Similar to above, we need to do a no-op remap to init a static + // table in the remapper before the jobs start using remap(). + spv::spirvbin_t remapper(0); + // We need to provide at least a valid header to not crash. + std::vector spirv { + 0x07230203,// MAGIC + 0, // VERSION + 0, // GENERATOR + 0, // BOUND + 0 // SCHEMA, must be 0 + }; + remapper.remap(spirv, 0); +} + +void SpirvRemapWrapperRemap(std::vector& spirv) { + // Remove dead module-level objects: functions, types, vars + spv::spirvbin_t remapper(0); + remapper.remap(spirv, spv::spirvbin_base_t::DCE_ALL); +} + +} // namespace filamat diff --git a/libs/filamat/src/SpirvRemapWrapper.h b/libs/filamat/src/SpirvRemapWrapper.h new file mode 100644 index 0000000000..c03c0be728 --- /dev/null +++ b/libs/filamat/src/SpirvRemapWrapper.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef TNT_SPIRVREMAPWRAPPER_H +#define TNT_SPIRVREMAPWRAPPER_H + +#include +#include + +namespace filamat { + +void SpirvRemapWrapperSetUp(); +void SpirvRemapWrapperRemap(std::vector& spirv); + +} // namespace filamat + +#endif //TNT_SPIRVREMAPWRAPPER_H From eb8260ab2cfd5c336a3ef1274e71ce91014f59fa Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 13 May 2025 13:45:26 -0700 Subject: [PATCH 34/34] Update release note after cherry-pick --- NEW_RELEASE_NOTES.md | 2 -- RELEASE_NOTES.md | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 2829dc2571..c3161a7700 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -8,5 +8,3 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut -- matc non-functional change: Update GLSL postprocessor to - isolate calls to SPVRemap from calls to SPIRV-Cross. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 218c17174b..7d4e8a79af 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -10,6 +10,9 @@ Instead, if you are authoring a PR for the main branch, add your release note to ## v1.60.0 - materials: remove dependence on per-view descset layout from filamat. [⚠️ **New Material Version**] +- matc non-functional change: Update GLSL postprocessor to + isolate calls to SPVRemap from calls to SPIRV-Cross. + ## v1.59.5