option to reorder the froxel info for better cpu cache utilization

This changes the layout of the froxels data
structure in the GPU so that the major axis
is the Z axis instead of X. The idea is that
within a given screen-space tile, we have a high
probability to access froxels at the same x/y
but different z.

This is turned off right now, as it requires
a change that breaks existing compiled materials.
This commit is contained in:
Mathias Agopian
2018-08-09 16:42:55 -07:00
committed by Mathias Agopian
parent a597127d59
commit da87405b68
4 changed files with 54 additions and 21 deletions

View File

@@ -43,8 +43,21 @@ using namespace driver;
namespace details {
/*
* This enables froxels to be rectangular which allows us to use a but more froxel
* with the same amount of memory in the GPU.
* This requires backward compatibility breaking changes in the shaders.
*/
static constexpr bool SUPPORTS_NON_SQUARE_FROXELS = false;
/*
* This changes the layout of the froxel info on the GPU such that it is more cache friendly,
* i.e. the major axis is Z instead of X, because in a given froxel there is more chance to
* hit another froxel at the same x,y coordinate.
* This requires backward compatibility breaking changes in the shaders.
*/
static constexpr bool SUPPORTS_REMAPPED_FROXELS = false;
// 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.
@@ -153,10 +166,9 @@ bool Froxelizer::prepare(
*/
// froxel buffer (~32 KiB)
const uint32_t maxFroxelCount = FROXEL_BUFFER_WIDTH * FROXEL_BUFFER_HEIGHT;
mFroxelBufferUser = {
driverApi.allocatePod<FroxelEntry>(maxFroxelCount, CACHELINE_SIZE),
maxFroxelCount };
driverApi.allocatePod<FroxelEntry>(FROXEL_BUFFER_ENTRY_COUNT_MAX, CACHELINE_SIZE),
FROXEL_BUFFER_ENTRY_COUNT_MAX };
// record buffer (~64 KiB)
mRecordBufferUser = {
@@ -184,9 +196,9 @@ bool Froxelizer::prepare(
assert(mFroxelShardedData.begin());
#ifndef NDEBUG
memset(mFroxelBufferUser.data(), 0x55, mFroxelBufferUser.sizeInBytes());
memset(mRecordBufferUser.data(), 0xEB, mRecordBufferUser.sizeInBytes());
memset(mFroxelShardedData.data(), 0xAA, mFroxelShardedData.sizeInBytes());
memset(mFroxelBufferUser.data(), 0x55, mFroxelBufferUser.sizeInBytes());
memset(mRecordBufferUser.data(), 0xEB, mRecordBufferUser.sizeInBytes());
memset(mFroxelShardedData.data(), 0xFD, mFroxelShardedData.sizeInBytes());
#endif
return uniformsNeedUpdating;
@@ -266,6 +278,7 @@ bool Froxelizer::update() noexcept {
mFroxelCountZ = froxelCountZ;
// froxel count must fit on 16 bits
const uint16_t froxelCount = uint16_t(froxelCountX * froxelCountY * froxelCountZ);
mFroxelCount = froxelCount;
if (mDistancesZ) {
// this is a LinearAllocator arena, use rewind() instead of free (which is a no op).
@@ -307,8 +320,15 @@ bool Froxelizer::update() noexcept {
mParamsZ[1] = 0; // updated when camera changes
mParamsZ[2] = -mLinearizer;
mParamsZ[3] = mFroxelCountZ;
mParamsF[0] = uint32_t(mFroxelCountX);
mParamsF[1] = uint32_t(mFroxelCountX * mFroxelCountY);
if (SUPPORTS_REMAPPED_FROXELS) {
mParamsF.x = uint32_t(mFroxelCountZ);
mParamsF.y = uint32_t(mFroxelCountX * mFroxelCountZ);
mParamsF.z = 1;
} else {
mParamsF[0] = 1;
mParamsF[1] = uint32_t(mFroxelCountX);
mParamsF[2] = uint32_t(mFroxelCountX * mFroxelCountY);
}
}
if (UTILS_UNLIKELY(mDirtyFlags & (PROJECTION_CHANGED | VIEWPORT_CHANGED))) {
@@ -523,8 +543,10 @@ void Froxelizer::froxelizeLights(FEngine& engine,
#ifndef NDEBUG
if (lightData.size()) {
// go through every froxel
auto const& gpuFroxelEntries(mFroxelBufferUser);
auto const& recordBufferUser(mRecordBufferUser);
auto gpuFroxelEntries(mFroxelBufferUser);
gpuFroxelEntries.set(gpuFroxelEntries.begin(),
mFroxelCountX * mFroxelCountY * mFroxelCountZ);
for (auto const& entry : gpuFroxelEntries) {
// go through every lights for that froxel
for (size_t i = 0; i < entry.pointLightCount + entry.spotLightCount; i++) {
@@ -639,14 +661,22 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept {
}
uint16_t offset = 0;
Slice<FroxelEntry> gpuFroxelEntries(mFroxelBufferUser);
FroxelEntry* const UTILS_RESTRICT froxels = gpuFroxelEntries.data();
RecordBufferType* UTILS_RESTRICT froxelRecords = mRecordBufferUser.data();
FroxelEntry* const UTILS_RESTRICT froxels = mFroxelBufferUser.data();
auto remap = [stride = size_t(mFroxelCountX * mFroxelCountY)](size_t i) {
if (SUPPORTS_REMAPPED_FROXELS) {
// TODO: with the non-square froxel change these would be mask ops instead of divide.
i = (i % stride) * FEngine::CONFIG_FROXEL_SLICE_COUNT + (i / stride);
}
return i;
};
RecordBufferType* const UTILS_RESTRICT froxelRecords = mRecordBufferUser.data();
// how many froxel record entries were reused (for debugging)
UTILS_UNUSED size_t reused = FROXEL_BUFFER_ENTRY_COUNT_MAX;
for (size_t i = 0, c = records.size(); i < c;) {
for (size_t i = 0, c = getFroxelCount(); i < c;) {
#ifndef NDEBUG
reused--;
#endif
@@ -665,8 +695,8 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept {
#endif
// note: instead of dropping froxels we could look for similar records we've already
// filed up.
do { // this compiles to memset()
froxels[i++].u32 = 0;
do { // this compiles to memset() when remap() is identity
froxels[remap(i++)].u32 = 0;
} while(i < c);
goto out_of_memory;
}
@@ -697,7 +727,7 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept {
// note: we can't use partition_point() here because we're not sorted
do {
froxels[i++].u32 = entry.u32;
froxels[remap(i++)].u32 = entry.u32;
} while(i < c && records[i].lights == b.lights);
}
out_of_memory:

View File

@@ -129,14 +129,14 @@ public:
math::float4 sun; // cos(sunAngle), sin(sunAngle), 1/(sunAngle*HALO_SIZE-sunAngle), HALO_EXP
math::float3 lightDirection;
float padding1;
uint32_t fParamsX; // stride-x
math::float3 shadowBias; // constant bias, normal bias, unused
float oneOverFroxelDimensionY;
math::float4 zParams; // froxel Z parameters
math::uint2 fParams; // froxelCountX, froxelCountX * froxelCountY
math::uint2 fParams; // stride-y, stride-z
math::float2 origin; // viewport left, viewport bottom
float oneOverFroxelDimensionX;

View File

@@ -117,6 +117,7 @@ public:
size_t getFroxelCountX() const noexcept { return mFroxelCountX; }
size_t getFroxelCountY() const noexcept { return mFroxelCountY; }
size_t getFroxelCountZ() const noexcept { return mFroxelCountZ; }
size_t getFroxelCount() const noexcept { return mFroxelCount; }
// update Records and Froxels texture with lights data. this is thread-safe.
void froxelizeLights(FEngine& engine, math::mat4f const& viewMatrix,
@@ -124,7 +125,8 @@ public:
void updateUniforms(UniformBuffer& u) {
u.setUniform(offsetof(FEngine::PerViewUib, zParams), mParamsZ);
u.setUniform(offsetof(FEngine::PerViewUib, fParams), mParamsF);
u.setUniform(offsetof(FEngine::PerViewUib, fParams), mParamsF.yz);
u.setUniform(offsetof(FEngine::PerViewUib, fParamsX), mParamsF.x);
u.setUniform(offsetof(FEngine::PerViewUib, oneOverFroxelDimensionX), mOneOverDimension.x);
u.setUniform(offsetof(FEngine::PerViewUib, oneOverFroxelDimensionY), mOneOverDimension.y);
}
@@ -224,6 +226,7 @@ private:
uint16_t mFroxelCountX = 0;
uint16_t mFroxelCountY = 0;
uint16_t mFroxelCountZ = 0;
uint16_t mFroxelCount = 0;
math::uint2 mFroxelDimension = {};
math::mat4f mProjection;
@@ -238,7 +241,7 @@ private:
// needed for update()
Viewport mViewport;
math::float4 mParamsZ = {};
math::uint4 mParamsF = {};
math::uint3 mParamsF = {};
float mNear = 0.0f; // camera near
float mZLightFar = FEngine::CONFIG_Z_LIGHT_FAR;
float mZLightNear = FEngine::CONFIG_Z_LIGHT_NEAR; // light near (first slice)

View File

@@ -43,7 +43,7 @@ UniformInterfaceBlock& UibGenerator::getPerViewUib() noexcept {
.add("lightColorIntensity", 1, UniformInterfaceBlock::Type::FLOAT4)
.add("sun", 1, UniformInterfaceBlock::Type::FLOAT4)
.add("lightDirection", 1, UniformInterfaceBlock::Type::FLOAT3)
.add("padding1", 1, UniformInterfaceBlock::Type::FLOAT)
.add("fParamsX", 1, UniformInterfaceBlock::Type::UINT)
// shadow
.add("shadowBias", 1, UniformInterfaceBlock::Type::FLOAT3)
.add("oneOverFroxelDimensionY", 1, UniformInterfaceBlock::Type::FLOAT)