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; }