From edfd06eaefa61b3bdb6f4a8d963d4cc5e53ff07c Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 22 May 2023 13:56:13 -0700 Subject: [PATCH] froxelizer doesn't use textures anymore (#6824) * froxelizer doesn't use textures anymore All data is stored in UBOs. Additionally, the buffer size is no longer hardcoded at compile time. This CL cuts in half the numbers of froxels to accommodate devices limited to 16KiB, * Use a specialization constant to adjust the size of froxel UBO This really only affects OpenGL in practice because metal supports 256MB minimum and only 3% of android devices support less than 32K, which is what we need. --- NEW_RELEASE_NOTES.md | 3 +- .../include/private/backend/DriverAPI.inc | 1 + filament/backend/src/metal/MetalDriver.mm | 4 + filament/backend/src/noop/NoopDriver.cpp | 4 + filament/backend/src/opengl/OpenGLDriver.cpp | 4 + filament/backend/src/vulkan/VulkanDriver.cpp | 7 ++ filament/src/Froxelizer.cpp | 88 +++++++------------ filament/src/Froxelizer.h | 37 +++++--- filament/src/PerViewUniforms.cpp | 3 +- filament/src/details/Material.cpp | 10 ++- filament/src/details/View.cpp | 3 + .../include/private/filament/EngineEnums.h | 8 +- .../include/private/filament/SibStructs.h | 13 ++- .../include/private/filament/UibStructs.h | 7 ++ libs/filamat/src/MaterialBuilder.cpp | 1 + libs/filamat/src/shaders/CodeGenerator.cpp | 25 +++++- libs/filamat/src/shaders/ShaderGenerator.cpp | 2 + libs/filamat/src/shaders/SibGenerator.cpp | 3 - libs/filamat/src/shaders/UibGenerator.cpp | 9 ++ libs/filamat/src/shaders/UibGenerator.h | 1 + shaders/src/light_punctual.fs | 18 ++-- 21 files changed, 155 insertions(+), 96 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 2759f60341..d75f3d7ea7 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -10,4 +10,5 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). - engine: a new feature to set a transform on the global-scale fog [⚠️ **Recompile materials**] - engine: large-scale fog can now be opted-out on a per-renderable basis -- matc: better accounting and validation of used samplers in user materials \ No newline at end of file +- engine: improve froxelizer resource efficiency [⚠️ **Recompile materials**] +- matc: better accounting and validation of used samplers in user materials diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index 12ed41d341..862b3f32ad 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -301,6 +301,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported) DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers) +DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize) DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams) DECL_DRIVER_API_SYNCHRONOUS_0(bool, canGenerateMipmaps) DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image) diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 806a508498..2535103818 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -750,6 +750,10 @@ uint8_t MetalDriver::getMaxDrawBuffers() { return std::min(mContext->maxColorRenderTargets, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT); } +size_t MetalDriver::getMaxUniformBufferSize() { + return 256 * 1024 * 1024; // TODO: return the actual size instead of hardcoding the minspec +} + void MetalDriver::updateIndexBuffer(Handle ibh, BufferDescriptor&& data, uint32_t byteOffset) { auto* ib = handle_cast(ibh); diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index 60fc70db41..ff83d9a1a2 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -193,6 +193,10 @@ uint8_t NoopDriver::getMaxDrawBuffers() { return MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; } +size_t NoopDriver::getMaxUniformBufferSize() { + return 16384u; +} + void NoopDriver::updateIndexBuffer(Handle ibh, BufferDescriptor&& p, uint32_t byteOffset) { scheduleDestroy(std::move(p)); diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index fc31b38fda..0a0f987b75 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -1898,6 +1898,10 @@ uint8_t OpenGLDriver::getMaxDrawBuffers() { return std::min(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, uint8_t(mContext.gets.max_draw_buffers)); } +size_t OpenGLDriver::getMaxUniformBufferSize() { + return mContext.gets.max_uniform_block_size; +} + // ------------------------------------------------------------------------------------------------ // Swap chains // ------------------------------------------------------------------------------------------------ diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index b6f8e9f90a..86cc7ff37e 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -830,6 +830,13 @@ uint8_t VulkanDriver::getMaxDrawBuffers() { return MRT::MIN_SUPPORTED_RENDER_TARGET_COUNT; // TODO: query real value } +size_t VulkanDriver::getMaxUniformBufferSize() { + // TODO: return the actual size instead of hardcoded value + // TODO: devices that return less than 32768 should be rejected. This represents only 3% + // of android devices. + return 32768; +} + void VulkanDriver::setVertexBufferObject(Handle vbh, uint32_t index, Handle boh) { auto& vb = *handle_cast(vbh); diff --git a/filament/src/Froxelizer.cpp b/filament/src/Froxelizer.cpp index e2b9cbee24..9673a329d4 100644 --- a/filament/src/Froxelizer.cpp +++ b/filament/src/Froxelizer.cpp @@ -49,32 +49,13 @@ static constexpr size_t FROXEL_SLICE_COUNT = 16; static constexpr float FROXEL_FIRST_SLICE_DEPTH = 5; static constexpr float FROXEL_LAST_SLICE_DISTANCE = 100; -// Max number of froxels limited by: -// - max texture size [min 2048] -// - chosen texture width [64] -// -// Also, increasing the number of froxels adds more pressure on the "record buffer" which stores -// the light indices per froxel. The record buffer is limited to min(16K[ubo], 64K[uint16]) entries, -// so with 8192 froxels, we can store 2 lights per froxels assuming they're all used. In practice, some -// froxels are not used, so we can store more. -constexpr size_t FROXEL_BUFFER_ENTRY_COUNT = 8192; - // The record buffer is limited by both the UBO size and our use of 16-bits indices. constexpr size_t RECORD_BUFFER_ENTRY_COUNT = CONFIG_MINSPEC_UBO_SIZE; // 16 KiB UBO minspec -// The Froxel buffer is set to FROXEL_BUFFER_WIDTH x n -// With n limited by the supported texture dimension, which is guaranteed to be at least 2048 -// in all version of GLES. -// Make sure this matches the same constants in shading_lit.fs -constexpr size_t FROXEL_BUFFER_WIDTH_SHIFT = 6u; -constexpr size_t FROXEL_BUFFER_WIDTH = 1u << FROXEL_BUFFER_WIDTH_SHIFT; -constexpr size_t FROXEL_BUFFER_WIDTH_MASK = FROXEL_BUFFER_WIDTH - 1u; -constexpr size_t FROXEL_BUFFER_HEIGHT = (FROXEL_BUFFER_ENTRY_COUNT + FROXEL_BUFFER_WIDTH_MASK) / FROXEL_BUFFER_WIDTH; - // Buffer needed for Froxelizer internal data structures (~256 KiB) constexpr size_t PER_FROXELDATA_ARENA_SIZE = sizeof(float4) * - (FROXEL_BUFFER_ENTRY_COUNT + - FROXEL_BUFFER_ENTRY_COUNT + 3 + + (FROXEL_BUFFER_MAX_ENTRY_COUNT + + FROXEL_BUFFER_MAX_ENTRY_COUNT + 3 + FROXEL_SLICE_COUNT / 4 + 1); // number of lights processed by one group (e.g. 32) @@ -97,7 +78,7 @@ static_assert(RECORD_BUFFER_ENTRY_COUNT <= CONFIG_MINSPEC_UBO_SIZE, "RecordBuffer cannot be larger than the UBO minspec (16KiB)"); struct Froxelizer::FroxelThreadData : - public std::array { + public std::array { }; Froxelizer::Froxelizer(FEngine& engine) @@ -105,22 +86,24 @@ Froxelizer::Froxelizer(FEngine& engine) mZLightNear(FROXEL_FIRST_SLICE_DEPTH), mZLightFar(FROXEL_LAST_SLICE_DISTANCE) { + static_assert(std::is_same_v, + "Record Buffer must use bytes"); + if (UTILS_UNLIKELY(engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)) { return; } DriverApi& driverApi = engine.getDriverApi(); - static_assert(std::is_same_v, - "Record Buffer must use bytes"); + mFroxelBufferEntryCount = std::min( + FROXEL_BUFFER_MAX_ENTRY_COUNT, + engine.getDriverApi().getMaxUniformBufferSize() / 4u); mRecordsBuffer = driverApi.createBufferObject(RECORD_BUFFER_ENTRY_COUNT, BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC); - mFroxelTexture = driverApi.createTexture(SamplerType::SAMPLER_2D, 1, - backend::TextureFormat::RG16UI, 1, - FROXEL_BUFFER_WIDTH, FROXEL_BUFFER_HEIGHT, 1, - TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE); + mFroxelsBuffer = driverApi.createBufferObject(getFroxelBufferEntryCount() * 16u, + BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC); } Froxelizer::~Froxelizer() { @@ -139,8 +122,8 @@ void Froxelizer::terminate(DriverApi& driverApi) noexcept { if (mRecordsBuffer) { driverApi.destroyBufferObject(mRecordsBuffer); } - if (mFroxelTexture) { - driverApi.destroyTexture(mFroxelTexture); + if (mFroxelsBuffer) { + driverApi.destroyBufferObject(mFroxelsBuffer); } } @@ -188,8 +171,8 @@ bool Froxelizer::prepare( // froxel buffer (~32 KiB) mFroxelBufferUser = { - driverApi.allocatePod(FROXEL_BUFFER_ENTRY_COUNT), - FROXEL_BUFFER_ENTRY_COUNT }; + driverApi.allocatePod(getFroxelBufferEntryCount()), + getFroxelBufferEntryCount() }; // record buffer (~16 KiB) mRecordBufferUser = { @@ -202,8 +185,8 @@ bool Froxelizer::prepare( // light records per froxel (~256 KiB) mLightRecords = { - arena.allocate(FROXEL_BUFFER_ENTRY_COUNT, CACHELINE_SIZE), - FROXEL_BUFFER_ENTRY_COUNT }; + arena.allocate(getFroxelBufferEntryCount(), CACHELINE_SIZE), + getFroxelBufferEntryCount() }; // froxel thread data (~256 KiB) mFroxelShardedData = { @@ -224,7 +207,7 @@ bool Froxelizer::prepare( void Froxelizer::computeFroxelLayout( uint2* dim, uint16_t* countX, uint16_t* countY, uint16_t* countZ, - filament::Viewport const& viewport) noexcept { + size_t froxelBufferEntryCount, filament::Viewport const& viewport) noexcept { auto roundTo8 = [](uint32_t v) { return (v + 7u) & ~7u; }; @@ -234,7 +217,7 @@ void Froxelizer::computeFroxelLayout( // calculate froxel dimension from FROXEL_BUFFER_ENTRY_COUNT_MAX and viewport // - Start from the maximum number of froxels we can use in the x-y plane size_t const froxelSliceCount = FROXEL_SLICE_COUNT; - size_t const froxelPlaneCount = FROXEL_BUFFER_ENTRY_COUNT / froxelSliceCount; + size_t const froxelPlaneCount = froxelBufferEntryCount / froxelSliceCount; // - compute the number of square froxels we need in width and height, rounded down // solving: | froxelCountX * froxelCountY == froxelPlaneCount // | froxelCountX / froxelCountY == width / height @@ -331,7 +314,8 @@ bool Froxelizer::update() noexcept { uint2 froxelDimension; uint16_t froxelCountX, froxelCountY, froxelCountZ; - computeFroxelLayout(&froxelDimension, &froxelCountX, &froxelCountY, &froxelCountZ, viewport); + computeFroxelLayout(&froxelDimension, &froxelCountX, &froxelCountY, &froxelCountZ, + getFroxelBufferEntryCount(), viewport); mFroxelDimension = froxelDimension; mClipToFroxelX = (0.5f * float(viewport.width)) / float(froxelDimension.x); @@ -344,7 +328,7 @@ bool Froxelizer::update() noexcept { << froxelDimension.x << "x" << froxelDimension.y << io::endl << "Froxel: " << froxelCountX << "x" << froxelCountY << "x" << froxelCountZ << " = " << (froxelCountX * froxelCountY * froxelCountZ) - << " (" << FROXEL_BUFFER_ENTRY_COUNT - froxelCountX * froxelCountY * froxelCountZ << " lost)" + << " (" << getFroxelBufferEntryCount() - froxelCountX * froxelCountY * froxelCountZ << " lost)" << io::endl; #endif @@ -504,11 +488,8 @@ std::pair Froxelizer::clipToIndices(float2 const& clip) const no void Froxelizer::commit(backend::DriverApi& driverApi) { // send data to GPU - driverApi.update3DImage(mFroxelTexture, 0, 0, 0, 0, - FROXEL_BUFFER_WIDTH, FROXEL_BUFFER_HEIGHT, 1, { - mFroxelBufferUser.begin(), mFroxelBufferUser.sizeInBytes(), - PixelBufferDescriptor::PixelDataFormat::RG_INTEGER, - PixelBufferDescriptor::PixelDataType::USHORT }); + driverApi.updateBufferObject(mFroxelsBuffer, + { mFroxelBufferUser.data(), getFroxelBufferEntryCount() * 16u }, 0); driverApi.updateBufferObject(mRecordsBuffer, { mRecordBufferUser.data(), RECORD_BUFFER_ENTRY_COUNT }, 0); @@ -536,11 +517,11 @@ void Froxelizer::froxelizeLights(FEngine& engine, mFroxelCountX * mFroxelCountY * mFroxelCountZ); for (auto const& entry : gpuFroxelEntries) { // go through every light for that froxel - for (size_t i = 0; i < entry.count; i++) { + for (size_t i = 0; i < entry.count(); i++) { // get the light index - assert_invariant(entry.offset + i < RECORD_BUFFER_ENTRY_COUNT); + assert_invariant(entry.offset() + i < RECORD_BUFFER_ENTRY_COUNT); - size_t const lightIndex = recordBufferUser[entry.offset + i]; + size_t const lightIndex = recordBufferUser[entry.offset() + i]; assert_invariant(lightIndex <= CONFIG_MAX_LIGHT_INDEX); // make sure it corresponds to an existing light @@ -624,7 +605,7 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept { SYSTRACE_CALL(); - Slice froxelThreadData = mFroxelShardedData; + Slice const froxelThreadData = mFroxelShardedData; // convert froxel data from N groups of M bits to LightRecord::bitset, so we can // easily compare adjacent froxels, for compaction. The conversion loops below get @@ -633,7 +614,7 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept { // this gets very well vectorized... utils::Slice records(mLightRecords); - for (size_t j = 0, jc = FROXEL_BUFFER_ENTRY_COUNT; j < jc; j++) { + for (size_t j = 0, jc = getFroxelBufferEntryCount(); j < jc; j++) { for (size_t i = 0; i < LightRecord::bitset::WORLD_COUNT; i++) { using container_type = LightRecord::bitset::container_type; constexpr size_t r = sizeof(container_type) / sizeof(LightGroupType); @@ -646,7 +627,7 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept { } LightRecord::bitset allLights{}; - for (size_t j = 0, jc = FROXEL_BUFFER_ENTRY_COUNT; j < jc; j++) { + for (size_t j = 0, jc = getFroxelBufferEntryCount(); j < jc; j++) { allLights |= records[j].lights; } @@ -683,11 +664,8 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept { // We have a limitation of 255 spot + 255 point lights per froxel. // note: initializer list for union cannot have more than one element - FroxelEntry entry{ - .offset = offset, - .count = (uint8_t)std::min(size_t(255), b.lights.count()), - }; - const size_t lightCount = entry.count; + FroxelEntry entry{ offset, uint8_t(std::min(size_t(255), b.lights.count())) }; + const size_t lightCount = entry.count(); if (UTILS_UNLIKELY(offset + lightCount >= RECORD_BUFFER_ENTRY_COUNT)) { #ifndef NDEBUG @@ -696,7 +674,7 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept { // note: instead of dropping froxels we could look for similar records we've already // filed up. do { - froxels[i] = { .offset = 0, .count = allLightsCount }; + froxels[i] = { 0u, allLightsCount }; if (records[i].lights.none()) { froxels[i].u32 = 0; } diff --git a/filament/src/Froxelizer.h b/filament/src/Froxelizer.h index 2a0636bd65..27885e24bc 100644 --- a/filament/src/Froxelizer.h +++ b/filament/src/Froxelizer.h @@ -35,6 +35,15 @@ namespace filament { +// Max number of froxels limited by: +// - max ubo size [min 16KiB] +// +// Also, increasing the number of froxels adds more pressure on the "record buffer" which stores +// the light indices per froxel. The record buffer is limited to min(16K[ubo], 64K[uint16]) entries, +// so with 8192 froxels, we can store 2 lights per froxels assuming they're all used. In practice, +// some froxels are not used, so we can store more. +constexpr size_t FROXEL_BUFFER_MAX_ENTRY_COUNT = 8192; + class FEngine; class FCamera; class FTexture; @@ -83,7 +92,9 @@ public: } // gpu buffer containing froxels. valid after construction. - backend::Handle getFroxelTexture() const noexcept { return mFroxelTexture; } + backend::Handle getFroxelBuffer() const noexcept { + return mFroxelsBuffer; + } void setOptions(float zLightNear, float zLightFar) noexcept; @@ -129,14 +140,11 @@ public: */ struct FroxelEntry { - union { - uint32_t u32 = 0; - struct { - uint16_t offset; - uint8_t count; - uint8_t reserved; - }; - }; + inline FroxelEntry(uint16_t offset, uint8_t count) noexcept + : u32((offset << 16) | count) { } + inline uint8_t count() const noexcept { return u32 & 0xFFu; } + inline uint16_t offset() const noexcept { return u32 >> 16u; } + uint32_t u32 = 0; }; // we can't change this easily because the shader expects 16 indices per uint4 @@ -150,6 +158,10 @@ public: using LightGroupType = uint32_t; private: + size_t getFroxelBufferEntryCount() const noexcept { + return mFroxelBufferEntryCount; + } + struct LightRecord { using bitset = utils::bitset; bitset lights; @@ -217,11 +229,14 @@ private: static void computeFroxelLayout( math::uint2* dim, uint16_t* countX, uint16_t* countY, uint16_t* countZ, - Viewport const& viewport) noexcept; + size_t froxelBufferEntryCount, Viewport const& viewport) noexcept; // internal state dependent on the viewport and needed for froxelizing LinearAllocatorArena mArena; // ~256 KiB + // 4096 froxels fits in a 16KiB buffer, the minimum guaranteed in GLES 3.x and Vulkan 1.1 + size_t mFroxelBufferEntryCount = 4096; + // allocations in the private froxel arena float* mDistancesZ = nullptr; math::float4* mPlanesX = nullptr; @@ -247,7 +262,7 @@ private: float mClipToFroxelX = 0.0f; float mClipToFroxelY = 0.0f; backend::BufferObjectHandle mRecordsBuffer; - backend::Handle mFroxelTexture; + backend::BufferObjectHandle mFroxelsBuffer; // needed for update() Viewport mViewport; diff --git a/filament/src/PerViewUniforms.cpp b/filament/src/PerViewUniforms.cpp index 7c92678821..c54af51a23 100644 --- a/filament/src/PerViewUniforms.cpp +++ b/filament/src/PerViewUniforms.cpp @@ -299,8 +299,7 @@ void PerViewUniforms::prepareAmbientLight(FEngine& engine, FIndirectLight const& void PerViewUniforms::prepareDynamicLights(Froxelizer& froxelizer) noexcept { auto& s = mUniforms.edit(); froxelizer.updateUniforms(s); - float f = froxelizer.getLightFar(); - mSamplers.setSampler(PerViewSib::FROXELS, { froxelizer.getFroxelTexture() }); + float const f = froxelizer.getLightFar(); s.lightFarAttenuationParams = 0.5f * float2{ 10.0f, 10.0f / (f * f) }; } diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 20279103c2..98b8b655d0 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -16,6 +16,7 @@ #include "details/Material.h" +#include "Froxelizer.h" #include "MaterialParser.h" #include "details/Engine.h" @@ -238,7 +239,11 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) int const maxInstanceCount = (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) ? 1 : CONFIG_MAX_INSTANCES; - const bool staticTextureWorkaround = + int const maxFroxelBufferHeight = std::min( + FROXEL_BUFFER_MAX_ENTRY_COUNT / 4, + engine.getDriverApi().getMaxUniformBufferSize() / 16u); + + bool const staticTextureWorkaround = engine.getDriverApi().isWorkaroundNeeded(Workaround::A8X_STATIC_TEXTURE_TARGET_ERROR); mSpecializationConstants.reserve(constants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS); @@ -248,6 +253,9 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_MAX_INSTANCES, (int)maxInstanceCount }); + mSpecializationConstants.push_back({ + +ReservedSpecializationConstants::CONFIG_FROXEL_BUFFER_HEIGHT, + (int)maxFroxelBufferHeight }); mSpecializationConstants.push_back({ +ReservedSpecializationConstants::CONFIG_STATIC_TEXTURE_TARGET_WORKAROUND, (bool)staticTextureWorkaround }); diff --git a/filament/src/details/View.cpp b/filament/src/details/View.cpp index eaec2d7958..ffc309cfe1 100644 --- a/filament/src/details/View.cpp +++ b/filament/src/details/View.cpp @@ -668,6 +668,9 @@ void FView::bindPerViewUniformsAndSamplers(FEngine::DriverApi& driver) const noe driver.bindUniformBuffer(+UniformBindingPoints::FROXEL_RECORDS, mFroxelizer.getRecordBuffer()); + + driver.bindUniformBuffer(+UniformBindingPoints::FROXELS, + mFroxelizer.getFroxelBuffer()); } void FView::computeVisibilityMasks( diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index 92dab2a920..ea587bd2a8 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -41,7 +41,8 @@ enum class UniformBindingPoints : uint8_t { LIGHTS = 4, // lights data array SHADOW = 5, // punctual shadow data FROXEL_RECORDS = 6, - PER_MATERIAL_INSTANCE = 7, // uniforms updates per material + FROXELS = 7, + PER_MATERIAL_INSTANCE = 8, // uniforms updates per material // Update utils::Enum::count<>() below when adding values here // These are limited by CONFIG_BINDING_COUNT (currently 10) }; @@ -59,7 +60,8 @@ enum class ReservedSpecializationConstants : uint8_t { BACKEND_FEATURE_LEVEL = 0, CONFIG_MAX_INSTANCES = 1, CONFIG_STATIC_TEXTURE_TARGET_WORKAROUND = 2, - CONFIG_SRGB_SWAPCHAIN_EMULATION = 3 // don't change (hardcoded in OpenGLDriver.cpp) + CONFIG_SRGB_SWAPCHAIN_EMULATION = 3, // don't change (hardcoded in OpenGLDriver.cpp) + CONFIG_FROXEL_BUFFER_HEIGHT = 4 }; // This value is limited by UBO size, ES3.0 only guarantees 16 KiB. @@ -124,7 +126,7 @@ template<> struct utils::EnableIntegerOperators : public std::true_type {}; template<> -inline constexpr size_t utils::Enum::count() { return 8; } +inline constexpr size_t utils::Enum::count() { return 9; } template<> inline constexpr size_t utils::Enum::count() { return 3; } diff --git a/libs/filabridge/include/private/filament/SibStructs.h b/libs/filabridge/include/private/filament/SibStructs.h index 2d4ca033e9..cd6f0767c8 100644 --- a/libs/filabridge/include/private/filament/SibStructs.h +++ b/libs/filabridge/include/private/filament/SibStructs.h @@ -25,14 +25,13 @@ namespace filament { struct PerViewSib { // indices of each sampler in this SamplerInterfaceBlock (see: getPerViewSib()) static constexpr size_t SHADOW_MAP = 0; // user defined (1024x1024) DEPTH, array - static constexpr size_t FROXELS = 1; // 64x2048, RG16 {index, count, reserved} - static constexpr size_t IBL_DFG_LUT = 2; // user defined (128x128), RGB16F - static constexpr size_t IBL_SPECULAR = 3; // user defined, user defined, CUBEMAP - static constexpr size_t SSAO = 4; // variable, RGB8 {AO, [depth]} - static constexpr size_t SSR = 5; // variable, RGB_11_11_10, mipmapped - static constexpr size_t STRUCTURE = 6; // variable, DEPTH + static constexpr size_t IBL_DFG_LUT = 1; // user defined (128x128), RGB16F + static constexpr size_t IBL_SPECULAR = 2; // user defined, user defined, CUBEMAP + static constexpr size_t SSAO = 3; // variable, RGB8 {AO, [depth]} + static constexpr size_t SSR = 4; // variable, RGB_11_11_10, mipmapped + static constexpr size_t STRUCTURE = 5; // variable, DEPTH - static constexpr size_t SAMPLER_COUNT = 7; + static constexpr size_t SAMPLER_COUNT = 6; }; struct PerRenderPrimitiveMorphingSib { diff --git a/libs/filabridge/include/private/filament/UibStructs.h b/libs/filabridge/include/private/filament/UibStructs.h index 3c650cdad4..01ad45a9c4 100644 --- a/libs/filabridge/include/private/filament/UibStructs.h +++ b/libs/filabridge/include/private/filament/UibStructs.h @@ -311,6 +311,13 @@ struct FroxelRecordUib { // NOLINT(cppcoreguidelines-pro-type-member-init) static_assert(sizeof(FroxelRecordUib) == 16384, "FroxelRecordUib should be exactly 16KiB"); +struct FroxelsUib { // NOLINT(cppcoreguidelines-pro-type-member-init) + static constexpr std::string_view _name{ "FroxelsUniforms" }; + math::uint4 records[1024]; +}; +static_assert(sizeof(FroxelsUib) == 16384, + "FroxelsUib should be exactly 16KiB"); + // ------------------------------------------------------------------------------------------------ // MARK: - diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 4737277865..3b11d746e0 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -1433,6 +1433,7 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& { LightsUib::_name, UniformBindingPoints::LIGHTS }, { ShadowUib::_name, UniformBindingPoints::SHADOW }, { FroxelRecordUib::_name, UniformBindingPoints::FROXEL_RECORDS }, + { FroxelsUib::_name, UniformBindingPoints::FROXELS }, { PerRenderableBoneUib::_name, UniformBindingPoints::PER_RENDERABLE_BONES }, { PerRenderableMorphingUib::_name, UniformBindingPoints::PER_RENDERABLE_MORPHING }, { info.uib.getName(), UniformBindingPoints::PER_MATERIAL_INSTANCE } diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index 76338efd74..5a58a356a1 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -194,15 +194,32 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade if (mTargetApi == TargetApi::VULKAN) { // Note: This is a hack for a hack. - // Vulkan doesn't fully support sizing arrays within a block with specialization constants, - // and since we only need to do this for a hack for WebGL, it's fine to replace it with - // regular constant here. - // We *could* leave it as a specialization constant, but this triggers a crashing bug with + // + // Vulkan doesn't support sizing arrays within a block with specialization constants, + // as per this paragraph of the ARB_spir_v specification: + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_gl_spirv.txt + // + // Arrays inside a block may be sized with a specialization constant, + // but the block will have a static layout. Changing the specialized size will + // not re-layout the block. In the absence of explicit offsets, the layout will be + // based on the default size of the array. + // + // CONFIG_MAX_INSTANCES is only needed for WebGL, so we can replace it with a constant. + // CONFIG_FROXEL_BUFFER_HEIGHT can be hardcoded to 2048 because only 3% of Android devices + // only support 16KiB buffer or less (1024 lines). + // + // We *could* leave these as a specialization constant, but this triggers a crashing bug with // some Adreno drivers on Android. see: https://github.com/google/filament/issues/6444 + // out << "const int CONFIG_MAX_INSTANCES = " << (int)CONFIG_MAX_INSTANCES << ";\n"; + out << "const int CONFIG_FROXEL_BUFFER_HEIGHT = 2048;\n"; } else { generateSpecializationConstant(out, "CONFIG_MAX_INSTANCES", +ReservedSpecializationConstants::CONFIG_MAX_INSTANCES, (int)CONFIG_MAX_INSTANCES); + + // the default of 1024 (16KiB) is needed for 32% of Android devices + generateSpecializationConstant(out, "CONFIG_FROXEL_BUFFER_HEIGHT", + +ReservedSpecializationConstants::CONFIG_FROXEL_BUFFER_HEIGHT, 1024); } // Workaround a Metal pipeline compilation error with the message: diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index cd97209fba..d83fd45ef3 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -537,6 +537,8 @@ std::string ShaderGenerator::createFragmentProgram(ShaderModel shaderModel, if (variant.hasDynamicLighting()) { cg.generateUniforms(fs, ShaderStage::FRAGMENT, UniformBindingPoints::FROXEL_RECORDS, UibGenerator::getFroxelRecordUib()); + cg.generateUniforms(fs, ShaderStage::FRAGMENT, + UniformBindingPoints::FROXELS, UibGenerator::getFroxelsUib()); } cg.generateUniforms(fs, ShaderStage::FRAGMENT, diff --git a/libs/filamat/src/shaders/SibGenerator.cpp b/libs/filamat/src/shaders/SibGenerator.cpp index 18472a3ff3..43ba0eca82 100644 --- a/libs/filamat/src/shaders/SibGenerator.cpp +++ b/libs/filamat/src/shaders/SibGenerator.cpp @@ -44,7 +44,6 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(Variant variant) noexce .name("Light") .stageFlags(backend::ShaderStageFlags::FRAGMENT) .add( {{ "shadowMap", Type::SAMPLER_2D_ARRAY, Format::SHADOW, Precision::MEDIUM }, - { "froxels", Type::SAMPLER_2D, Format::UINT, Precision::MEDIUM }, { "iblDFG", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM }, { "iblSpecular", Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM }, { "ssao", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::MEDIUM }, @@ -57,7 +56,6 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(Variant variant) noexce .name("Light") .stageFlags(backend::ShaderStageFlags::FRAGMENT) .add( {{ "shadowMap", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH }, - { "froxels", Type::SAMPLER_2D, Format::UINT, Precision::MEDIUM }, { "iblDFG", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM }, { "iblSpecular", Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM }, { "ssao", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::MEDIUM }, @@ -73,7 +71,6 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(Variant variant) noexce { "unused1" }, { "unused2" }, { "unused3" }, - { "unused4" }, { "ssr", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM }, { "structure", Type::SAMPLER_2D, Format::FLOAT, Precision::HIGH }} ) diff --git a/libs/filamat/src/shaders/UibGenerator.cpp b/libs/filamat/src/shaders/UibGenerator.cpp index c033f99211..a5f8ba2f42 100644 --- a/libs/filamat/src/shaders/UibGenerator.cpp +++ b/libs/filamat/src/shaders/UibGenerator.cpp @@ -217,4 +217,13 @@ BufferInterfaceBlock const& UibGenerator::getFroxelRecordUib() noexcept { return uib; } +BufferInterfaceBlock const& UibGenerator::getFroxelsUib() noexcept { + static BufferInterfaceBlock const uib = BufferInterfaceBlock::Builder() + .name(FroxelsUib::_name) + .add({{ "records", 1024, BufferInterfaceBlock::Type::UINT4, Precision::HIGH, {}, + {}, {}, "CONFIG_FROXEL_BUFFER_HEIGHT"}}) + .build(); + return uib; +} + } // namespace filament diff --git a/libs/filamat/src/shaders/UibGenerator.h b/libs/filamat/src/shaders/UibGenerator.h index 4be877f148..a39c44c123 100644 --- a/libs/filamat/src/shaders/UibGenerator.h +++ b/libs/filamat/src/shaders/UibGenerator.h @@ -30,6 +30,7 @@ public: static BufferInterfaceBlock const& getPerRenderableBonesUib() noexcept; static BufferInterfaceBlock const& getPerRenderableMorphingUib() noexcept; static BufferInterfaceBlock const& getFroxelRecordUib() noexcept; + static BufferInterfaceBlock const& getFroxelsUib() noexcept; // When adding an UBO here, make sure to also update // MaterialBuilder::writeCommonChunks() if needed }; diff --git a/shaders/src/light_punctual.fs b/shaders/src/light_punctual.fs index de99a5e5f5..3de5161358 100644 --- a/shaders/src/light_punctual.fs +++ b/shaders/src/light_punctual.fs @@ -46,8 +46,7 @@ uvec3 getFroxelCoords(const highp vec3 fragCoords) { /** * Computes the froxel index of the fragment at the specified coordinates. * The froxel index is computed from the 3D coordinates of the froxel in the - * froxel grid and later used to fetch from the froxel data texture - * (light_froxels). + * froxel grid and later used to fetch from the froxel buffer. */ uint getFroxelIndex(const highp vec3 fragCoords) { uvec3 froxelCoord = getFroxelCoords(fragCoords); @@ -65,15 +64,16 @@ ivec2 getFroxelTexCoord(uint froxelIndex) { /** * Returns the froxel data for the given froxel index. The data is fetched - * from the light_froxels texture. + * from FroxelsUniforms UBO. */ -FroxelParams getFroxelParams(uint froxelIndex) { - ivec2 texCoord = getFroxelTexCoord(froxelIndex); - uvec2 entry = texelFetch(light_froxels, texCoord, 0).rg; - +FroxelParams getFroxelParams(const uint froxelIndex) { + uint w = froxelIndex >> 2u; + uint c = froxelIndex & 0x3u; + highp uvec4 d = froxelsUniforms.records[w]; + highp uint f = d[c]; FroxelParams froxel; - froxel.recordOffset = entry.r; - froxel.count = entry.g & 0xFFu; + froxel.recordOffset = f >> 16u; + froxel.count = f & 0xFFu; return froxel; }