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.
This commit is contained in:
@@ -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
|
||||
- engine: improve froxelizer resource efficiency [⚠️ **Recompile materials**]
|
||||
- matc: better accounting and validation of used samplers in user materials
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<HwIndexBuffer> ibh, BufferDescriptor&& data,
|
||||
uint32_t byteOffset) {
|
||||
auto* ib = handle_cast<MetalIndexBuffer>(ibh);
|
||||
|
||||
@@ -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<HwIndexBuffer> ibh, BufferDescriptor&& p,
|
||||
uint32_t byteOffset) {
|
||||
scheduleDestroy(std::move(p));
|
||||
|
||||
@@ -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
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -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<HwVertexBuffer> vbh, uint32_t index,
|
||||
Handle<HwBufferObject> boh) {
|
||||
auto& vb = *handle_cast<VulkanVertexBuffer*>(vbh);
|
||||
|
||||
+33
-55
@@ -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<LightGroupType, FROXEL_BUFFER_ENTRY_COUNT> {
|
||||
public std::array<LightGroupType, FROXEL_BUFFER_MAX_ENTRY_COUNT> {
|
||||
};
|
||||
|
||||
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<RecordBufferType, uint8_t>,
|
||||
"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<RecordBufferType, uint8_t>,
|
||||
"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<FroxelEntry>(FROXEL_BUFFER_ENTRY_COUNT),
|
||||
FROXEL_BUFFER_ENTRY_COUNT };
|
||||
driverApi.allocatePod<FroxelEntry>(getFroxelBufferEntryCount()),
|
||||
getFroxelBufferEntryCount() };
|
||||
|
||||
// record buffer (~16 KiB)
|
||||
mRecordBufferUser = {
|
||||
@@ -202,8 +185,8 @@ bool Froxelizer::prepare(
|
||||
|
||||
// light records per froxel (~256 KiB)
|
||||
mLightRecords = {
|
||||
arena.allocate<LightRecord>(FROXEL_BUFFER_ENTRY_COUNT, CACHELINE_SIZE),
|
||||
FROXEL_BUFFER_ENTRY_COUNT };
|
||||
arena.allocate<LightRecord>(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<size_t, size_t> 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> froxelThreadData = mFroxelShardedData;
|
||||
Slice<FroxelThreadData> 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<LightRecord> 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;
|
||||
}
|
||||
|
||||
+26
-11
@@ -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<backend::HwTexture> getFroxelTexture() const noexcept { return mFroxelTexture; }
|
||||
backend::Handle<backend::HwBufferObject> 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<uint64_t, (CONFIG_MAX_LIGHT_COUNT + 63) / 64>;
|
||||
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<backend::HwTexture> mFroxelTexture;
|
||||
backend::BufferObjectHandle mFroxelsBuffer;
|
||||
|
||||
// needed for update()
|
||||
Viewport mViewport;
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<filament::ReservedSpecializationConstants> : public std::true_type {};
|
||||
|
||||
template<>
|
||||
inline constexpr size_t utils::Enum::count<filament::UniformBindingPoints>() { return 8; }
|
||||
inline constexpr size_t utils::Enum::count<filament::UniformBindingPoints>() { return 9; }
|
||||
template<>
|
||||
inline constexpr size_t utils::Enum::count<filament::SamplerBindingPoints>() { return 3; }
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: -
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }}
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user