froxel: make froxel buffer size consistent (#8437)

The definition of the froxel buffer sizes were spread across
multiple places and for different use cases (including
allocating the buffer, setting to a descriptor set, and adjusting
the relevant spec constant). In this commit, we try to unify
the size definitions into Froxelizer::getFroxelBufferByteCount().

We also remove the vulkan-only special case for froxel buffer
UBO. This case was introduced because spec constant couldn't be
used to size arrays for some drivers in the past, but we introduced a
workaround that replaced spec constants with constants.
This commit is contained in:
Powei Feng
2025-02-13 15:32:05 -08:00
committed by GitHub
parent 18529facef
commit b68e9fef03
7 changed files with 34 additions and 45 deletions

View File

@@ -116,6 +116,12 @@ static bool fuzzyEqual(mat4f const& UTILS_RESTRICT l, mat4f const& UTILS_RESTRIC
return result == 0;
}
size_t Froxelizer::getFroxelBufferByteCount(FEngine::DriverApi& driverApi) noexcept {
// Make sure that targetSize is 16-byte aligned so that it'll fit properly into an array of
// uvec4.
size_t const targetSize = (driverApi.getMaxUniformBufferSize() / 16) * 16;
return std::min(FROXEL_BUFFER_MAX_ENTRY_COUNT * sizeof(FroxelEntry), targetSize);
}
Froxelizer::Froxelizer(FEngine& engine)
: mArena("froxel", PER_FROXELDATA_ARENA_SIZE),
@@ -131,14 +137,14 @@ Froxelizer::Froxelizer(FEngine& engine)
return;
}
mFroxelBufferEntryCount = std::min(
FROXEL_BUFFER_MAX_ENTRY_COUNT,
engine.getDriverApi().getMaxUniformBufferSize() / 16u);
size_t const froxelBufferByteCount = getFroxelBufferByteCount(engine.getDriverApi());
mFroxelBufferEntryCount = froxelBufferByteCount / sizeof(FroxelEntry);
mRecordsBuffer = driverApi.createBufferObject(RECORD_BUFFER_ENTRY_COUNT,
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC);
mFroxelsBuffer = driverApi.createBufferObject(getFroxelBufferEntryCount() * 16u,
mFroxelsBuffer = driverApi.createBufferObject(
froxelBufferByteCount,
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC);
}
@@ -527,7 +533,7 @@ std::pair<size_t, size_t> Froxelizer::clipToIndices(float2 const& clip) const no
void Froxelizer::commit(DriverApi& driverApi) {
// send data to GPU
driverApi.updateBufferObject(mFroxelsBuffer,
{ mFroxelBufferUser.data(), getFroxelBufferEntryCount() * 16u }, 0);
{ mFroxelBufferUser.data(), getFroxelBufferEntryCount() * sizeof(FroxelEntry) }, 0);
driverApi.updateBufferObject(mRecordsBuffer,
{ mRecordBufferUser.data(), RECORD_BUFFER_ENTRY_COUNT }, 0);

View File

@@ -35,15 +35,16 @@
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.
// The number of froxel buffer entries is determined by max UBO size (see
// getFroxelBufferByteCount()). We also introduce the limit below because 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. In practice some froxels are not
// used, so we can store more.
constexpr size_t FROXEL_BUFFER_MAX_ENTRY_COUNT = 8192;
// Froxel buffer UBO is an array of uvec4. Make sure that the buffer is properly aligned.
static_assert(FROXEL_BUFFER_MAX_ENTRY_COUNT % 4 == 0u);
class FEngine;
class FCamera;
class FTexture;
@@ -146,6 +147,7 @@ public:
inline uint16_t offset() const noexcept { return u32 >> 16u; }
uint32_t u32 = 0;
};
static_assert(sizeof(FroxelEntry) == 4u);
// we can't change this easily because the shader expects 16 indices per uint4
using RecordBufferType = uint8_t;
@@ -157,6 +159,8 @@ public:
// with 256 lights this implies 8 jobs (256 / 32) for froxelization.
using LightGroupType = uint32_t;
static size_t getFroxelBufferByteCount(FEngine::DriverApi& driverApi) noexcept;
private:
size_t getFroxelBufferEntryCount() const noexcept {
return mFroxelBufferEntryCount;

View File

@@ -980,9 +980,9 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& b
int const maxInstanceCount = (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)
? 1 : CONFIG_MAX_INSTANCES;
int const maxFroxelBufferHeight = int(std::min(
FROXEL_BUFFER_MAX_ENTRY_COUNT / 4,
engine.getDriverApi().getMaxUniformBufferSize() / 16u));
// The 16u below denotes the 16 bytes in a uvec4, which is how the froxel buffer is stored.
int const maxFroxelBufferHeight =
int(Froxelizer::getFroxelBufferByteCount(engine.getDriverApi()) / 16u);
bool const staticTextureWorkaround =
engine.getDriverApi().isWorkaroundNeeded(Workaround::METAL_STATIC_TEXTURE_TARGET_ERROR);

View File

@@ -126,6 +126,7 @@ FView::FView(FEngine& engine)
mDefaultColorGrading = mColorGrading = engine.getDefaultColorGrading();
mColorPassDescriptorSet.init(
engine,
mLightUbh,
mFroxelizer.getRecordBuffer(),
mFroxelizer.getFroxelBuffer());

View File

@@ -126,6 +126,7 @@ ColorPassDescriptorSet::ColorPassDescriptorSet(FEngine& engine,
}
void ColorPassDescriptorSet::init(
FEngine& engine,
BufferObjectHandle lights,
BufferObjectHandle recordBuffer,
BufferObjectHandle froxelBuffer) noexcept {
@@ -135,7 +136,7 @@ void ColorPassDescriptorSet::init(
descriptorSet.setBuffer(+PerViewBindingPoints::RECORD_BUFFER,
recordBuffer, 0, sizeof(FroxelRecordUib));
descriptorSet.setBuffer(+PerViewBindingPoints::FROXEL_BUFFER,
froxelBuffer, 0, sizeof(FroxelsUib));
froxelBuffer, 0, Froxelizer::getFroxelBufferByteCount(engine.getDriverApi()));
}
}
@@ -573,4 +574,3 @@ void ColorPassDescriptorSet::setBuffer(descriptor_binding_t const binding,
}
} // namespace filament

View File

@@ -79,6 +79,7 @@ public:
TypedUniformBuffer<PerViewUib>& uniforms) noexcept;
void init(
FEngine& engine,
backend::BufferObjectHandle lights,
backend::BufferObjectHandle recordBuffer,
backend::BufferObjectHandle froxelBuffer) noexcept;

View File

@@ -295,35 +295,12 @@ utils::io::sstream& CodeGenerator::generateCommonProlog(utils::io::sstream& out,
generateSpecializationConstant(out, "BACKEND_FEATURE_LEVEL",
+ReservedSpecializationConstants::BACKEND_FEATURE_LEVEL, 1);
if (mTargetApi == TargetApi::VULKAN) {
// Note: This is a hack for a hack.
//
// 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);
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);
}
// the default of 1024 (16KiB) is needed for 32% of Android devices
generateSpecializationConstant(out, "CONFIG_FROXEL_BUFFER_HEIGHT",
+ReservedSpecializationConstants::CONFIG_FROXEL_BUFFER_HEIGHT, 1024);
// directional shadowmap visualization
generateSpecializationConstant(out, "CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP",