diff --git a/filament/src/Froxelizer.cpp b/filament/src/Froxelizer.cpp index 124048c57c..478472c435 100644 --- a/filament/src/Froxelizer.cpp +++ b/filament/src/Froxelizer.cpp @@ -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 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() + 1; + auto const* UTILS_RESTRICT zrange = lightData.data() + 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, + }; }); } diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index 0c49cd1d24..f0b9dfc40c 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -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(0) = {}; lightData.elementAt(0) = d; lightData.elementAt(0) = li; - lightData.elementAt(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(); + computeLightRanges(zrange, camera, spheres, lightData.size()); + auto const* UTILS_RESTRICT directions = lightData.data(); auto const* UTILS_RESTRICT instances = lightData.data(); 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); } diff --git a/filament/src/details/Froxelizer.h b/filament/src/details/Froxelizer.h index aae54e68b0..4e612a7699 100644 --- a/filament/src/details/Froxelizer.h +++ b/filament/src/details/Froxelizer.h @@ -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; @@ -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 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)); diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index 59a5fa1175..84347128ed 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -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 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; diff --git a/filament/test/filament_test.cpp b/filament/test/filament_test.cpp index 81365d6404..73b96f74a5 100644 --- a/filament/test/filament_test.cpp +++ b/filament/test/filament_test.cpp @@ -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);