more work towards working light-trees

- improve API of computeLightTree() which should 
  now be functional.

- pre-calculate the range of each light in
  screen-space, so we don’t have to do so
  over and over again in computeLightTree().

- define the GPU-side “node” for the light-tree,
  this is a place-holder until we really implement it.
This commit is contained in:
Mathias Agopian
2018-08-22 23:34:06 -07:00
committed by Romain Guy
parent f8c87f8f81
commit fc98c775c5
5 changed files with 100 additions and 55 deletions

View File

@@ -907,58 +907,55 @@ void Froxelizer::froxelizePointAndSpotLight(
}
}
/*
*
* lightTree output the light tree structure there (must be large enough to hold a complete tree)
* lightList list if lights
* lightData scene's light data SoA
* lightRecordsOffset offset in the record buffer where to find the light list
*/
void Froxelizer::computeLightTree(
const Froxelizer::LightRecord& lights,
const CameraInfo& camera,
const FScene::LightSoa& lightData) const noexcept {
// TODO: store in real array passed as argument
struct Node {
float min, max;
uint16_t next;
};
Node array[256];
// get light list for bitfield
RecordBufferType indices[CONFIG_MAX_LIGHT_COUNT];
RecordBufferType* last = indices;
lights.lights.forEachSetBit([&last](size_t l) {
*last++ = RecordBufferType(l);
});
LightTreeNode* lightTree,
utils::Slice<RecordBufferType> const& lightList,
const FScene::LightSoa& lightData,
size_t lightRecordsOffset) noexcept {
// number of lights in this record
size_t count = std::min(lightData.size() - 1u, size_t(last - indices));
const size_t count = lightList.size();
// the width of the tree is the next power-of-two (if not already a power of two)
size_t w = 1u << (log2i(count) + (utils::popcount(count) == 1 ? 0 : 1));
const size_t w = 1u << (log2i(count) + (utils::popcount(count) == 1 ? 0 : 1));
// height of the tree
size_t h = log2i(w) + 1u;
const size_t h = log2i(w) + 1u;
auto const* UTILS_RESTRICT spheres = lightData.data<FScene::POSITION_RADIUS>() + 1;
auto const* UTILS_RESTRICT zrange = lightData.data<FScene::SCREEN_SPACE_Z_RANGE>() + 1;
BinaryTreeArray::traverse(h,
[&array, &camera, spheres, indices, count]
[lightTree, lightRecordsOffset, zrange, indices = lightList.data(), count]
(size_t index, size_t col, size_t next) {
float min = 1.0;
float max = 0.0;
if (col < count) {
auto s = spheres[indices[col]];
float4 c = camera.view * s.xyz; // camera points towards the -z axis
float4 n = c + float4{ 0, 0, s.w, 0 };
float4 f = c - float4{ 0, 0, s.w, 0 };
n = camera.projection * n;
f = camera.projection * f;
min = (n.w > camera.zn) ? ((n.z / n.w + 1.0f) * 0.5f) : 0.0f;
max = (f.w < camera.zf) ? ((f.z / f.w + 1.0f) * 0.5f) : 1.0f;
}
array[index] = Node{ min, max, (uint16_t)next };
// indices[] cannot be accessed past 'col'
const float min = (col < count) ? zrange[indices[col]].x : 1.0f;
const float max = (col < count) ? zrange[indices[col]].y : 0.0f;
lightTree[index] = {
.min = min,
.max = max,
.next = uint16_t(next),
.offset = uint16_t(lightRecordsOffset + col),
.isLeaf = 1,
.count = 1,
.reserved = 0,
};
},
[&array](size_t index, size_t l, size_t r, size_t next) {
array[index] = Node{
std::min(array[l].min, array[r].min),
std::max(array[l].max, array[r].max),
(uint16_t)next };
[lightTree](size_t index, size_t l, size_t r, size_t next) {
lightTree[index] = {
.min = std::min(lightTree[l].min, lightTree[r].min),
.max = std::max(lightTree[l].max, lightTree[r].max),
.next = uint16_t(next),
.offset = 0,
.isLeaf = 0,
.count = 0,
.reserved = 0,
};
});
}

View File

@@ -133,11 +133,8 @@ void FScene::prepare(const math::mat4f& worldOriginTansform) {
float3 d = lcm.getLocalDirection(li);
// using the inverse-transpose handles non-uniform scaling
d = normalize(transpose(inverse(worldTransform.upperLeft())) * d);
// TODO: allow lightData.front() = { ... } syntax
lightData.elementAt<FScene::POSITION_RADIUS>(0) = {};
lightData.elementAt<FScene::DIRECTION>(0) = d;
lightData.elementAt<FScene::LIGHT_INSTANCE>(0) = li;
lightData.elementAt<FScene::VISIBILITY>(0) = {};
}
} else {
const float4 p = worldTransform * float4{ lcm.getLocalPosition(li), 1 };
@@ -148,7 +145,7 @@ void FScene::prepare(const math::mat4f& worldOriginTansform) {
d = normalize(transpose(inverse(worldTransform.upperLeft())) * d);
}
lightData.push_back_unsafe(
float4{ p.xyz, lcm.getRadius(li) }, d, li, {});
float4{ p.xyz, lcm.getRadius(li) }, d, li, {}, {});
}
}
}
@@ -202,6 +199,10 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren
// drop excess lights
lightData.resize(std::min(lightData.size(), CONFIG_MAX_LIGHT_COUNT + DIRECTIONAL_LIGHTS_COUNT));
// compute the light ranges (needed when building light trees)
float2* const zrange = lightData.data<FScene::SCREEN_SPACE_Z_RANGE>();
computeLightRanges(zrange, camera, spheres, lightData.size());
auto const* UTILS_RESTRICT directions = lightData.data<FScene::DIRECTION>();
auto const* UTILS_RESTRICT instances = lightData.data<FScene::LIGHT_INSTANCE>();
for (size_t i = DIRECTIONAL_LIGHTS_COUNT, c = lightData.size(); i < c; ++i) {
@@ -218,6 +219,38 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren
gpuLightData.commit(mEngine);
}
// These methods need to exist so clang honors the __restrict__ keyword, which in turn
// produces much better vectorization. The ALWAYS_INLINE keyword makes sure we actually don't
// pay the price of the call!
UTILS_ALWAYS_INLINE
void FScene::computeLightRanges(
float2* UTILS_RESTRICT const zrange,
CameraInfo const& UTILS_RESTRICT camera,
float4 const* UTILS_RESTRICT const spheres, size_t count) noexcept {
// without this clang seems to assume the src and dst might overlap even if they're
// restricted.
// we're guaranteed to have a multiple of 4 lights (at least)
count = uint32_t(count + 3u) & ~3u;
for (size_t i = 0 ; i < count; i++) {
// this loop gets vectorized x4
const float4 sphere = spheres[i];
const float4 center = camera.view * sphere.xyz; // camera points towards the -z axis
float4 n = center + float4{ 0, 0, sphere.w, 0 };
float4 f = center - float4{ 0, 0, sphere.w, 0 };
// project to clip space
n = camera.projection * n;
f = camera.projection * f;
// convert to NDC
const float min = (n.w > camera.zn) ? (n.z / n.w) : -1.0f;
const float max = (f.w < camera.zf) ? (f.z / f.w) : 1.0f;
// convert to screen space
zrange[i].x = (min + 1.0f) * 0.5f;
zrange[i].y = (max + 1.0f) * 0.5f;
}
}
void FScene::addEntity(Entity entity) {
mEntities.insert(entity);
}

View File

@@ -180,6 +180,18 @@ private:
float radius;
};
struct LightTreeNode {
float min; // lights z-range min
float max; // lights z-range max
uint16_t next; // next node when range test fails
uint16_t offset; // offset in record buffer
uint8_t isLeaf;
uint8_t count; // light count in record buffer
uint16_t reserved;
};
// The first entry always encodes the type of light, i.e. point/spot
using FroxelThreadData = std::array<LightGroupType, FROXEL_BUFFER_ENTRY_COUNT_MAX + 1>;
@@ -192,14 +204,12 @@ private:
void froxelizeAssignRecordsCompress() noexcept;
void froxelizePointAndSpotLight(
FroxelThreadData& froxelThread, size_t bit,
void froxelizePointAndSpotLight(FroxelThreadData& froxelThread, size_t bit,
math::mat4f const& projection, const LightParams& light) const noexcept;
void computeLightTree(
LightRecord const& lights,
const CameraInfo& camera,
const FScene::LightSoa& lightData) const noexcept;
static void computeLightTree(LightTreeNode* lightTree,
utils::Slice<RecordBufferType> const& lightList,
const FScene::LightSoa& lightData, size_t lightRecordsOffset) noexcept;
uint16_t getFroxelIndex(size_t ix, size_t iy, size_t iz) const noexcept {
return uint16_t(ix + (iy * mFroxelCountX) + (iz * mFroxelCountX * mFroxelCountY));

View File

@@ -145,14 +145,16 @@ public:
POSITION_RADIUS,
DIRECTION,
LIGHT_INSTANCE,
VISIBILITY
VISIBILITY,
SCREEN_SPACE_Z_RANGE
};
using LightSoa = utils::StructureOfArrays<
math::float4,
math::float3,
FLightManager::Instance,
Culler::result_type
Culler::result_type,
math::float2
>;
LightSoa const& getLightData() const noexcept { return mLightData; }
@@ -161,6 +163,9 @@ public:
void updateUBOs(utils::Range<uint32_t> visibleRenderables) const noexcept;
private:
static inline void computeLightRanges(math::float2* zrange,
CameraInfo const& camera, const math::float4* spheres, size_t count) noexcept;
FEngine& mEngine;
FSkybox const* mSkybox = nullptr;
FIndirectLight const* mIndirectLight = nullptr;

View File

@@ -517,8 +517,8 @@ TEST(FilamentTest, FroxelData) {
LightManager::Instance instance = engine->getLightManager().getInstance(e);
FScene::LightSoa lights;
lights.push_back({}, {}, {}, {}); // first one is always skipped
lights.push_back(float4{ 0, 0, -5, 1 }, {}, instance, 1);
lights.push_back({}, {}, {}, {}, {}); // first one is always skipped
lights.push_back(float4{ 0, 0, -5, 1 }, {}, instance, 1, {});
{
froxelData.froxelizeLights(*engine, {}, lights);