Skip to content

Commit

Permalink
implement ability to have multiple area lights
Browse files Browse the repository at this point in the history
  • Loading branch information
CookieBadger committed Feb 13, 2025
1 parent 7ca9fe2 commit b0b0cda
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 55 deletions.
3 changes: 3 additions & 0 deletions drivers/gles3/storage/light_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1599,6 +1599,9 @@ uint32_t LightStorage::area_shadow_atlas_update_light(RID p_atlas, RID p_light_i
return 0;
}

void LightStorage::area_shadow_atlas_update_active_lights(RID p_atlas, HashSet<RID> p_lights) {
}

void LightStorage::area_shadow_atlas_update(RID p_atlas) {
// nothing, same as shadow_atlas_update
}
Expand Down
1 change: 1 addition & 0 deletions drivers/gles3/storage/light_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,7 @@ class LightStorage : public RendererLightStorage {
virtual void area_shadow_atlas_set_size(RID p_atlas, int p_size, bool p_16_bits = true) override;
virtual void area_shadow_atlas_set_subdivision(RID p_atlas, int p_subdivision) override;
virtual void area_shadow_atlas_set_reprojection_ratio(RID p_atlas, int p_ratio) override;
virtual void area_shadow_atlas_update_active_lights(RID p_atlas, HashSet<RID> p_lights) override;
virtual uint32_t area_shadow_atlas_update_light(RID p_atlas, RID p_light_instance, float p_coverage, uint64_t p_light_version, bool p_is_dirty, uint32_t p_banding_buffer_offset) override;
virtual void area_shadow_set_banding_flags(Vector<uint8_t> p_buffer) override;
virtual void area_shadow_atlas_update(RID p_atlas) override;
Expand Down
1 change: 1 addition & 0 deletions servers/rendering/dummy/storage/light_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ class LightStorage : public RendererLightStorage {
virtual uint32_t area_shadow_atlas_update_light(RID p_atlas, RID p_light_instance, float p_coverage, uint64_t p_light_version, bool p_is_dirty, uint32_t p_banding_buffer_offset) override { return 0; };
virtual void area_shadow_atlas_update(RID p_atlas) override {};
virtual void area_shadow_reprojection_update(RID p_atlas, const Vector2 &p_reprojection_texture_size, RID p_depth_texture) override{};
virtual void area_shadow_atlas_update_active_lights(RID p_atlas, HashSet<RID> p_lights) override{};

virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = true) override {}
virtual int get_directional_light_shadow_size(RID p_light_intance) override { return 0; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1550,6 +1550,8 @@ void RenderForwardClustered::_pre_opaque_render(RenderDataRD *p_render_data, boo
RID light = p_render_data->render_shadows[p_render_data->area_shadows[i]].light;
RendererRD::LightStorage::AreaLightQuadTree *quad_tree = light_storage->light_instance_get_area_shadow_quad_tree(light);

// TODO: apply optimization to not render leafs when we can't expand further due to atlas constraints

// render banding tests for all area lights
RendererRD::LightStorage::AreaLightQuadTree::LeafIterator leaf_iterator = quad_tree->leaf_iterator();
while (leaf_iterator.has_next()) {
Expand All @@ -1563,8 +1565,6 @@ void RenderForwardClustered::_pre_opaque_render(RenderDataRD *p_render_data, boo
_render_area_shadow_banding_test(p_render_data, p_render_data->render_shadows[p_render_data->area_shadows[i]].light, reprojection_texture_size, lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, viewport_size, node->get_rect(), quad_tree->get_node_atlas_indices(node), node->get_banding_index());
}

quad_tree->initialize(); // TODO: this is not sound yet, because how do we guarantee, that when the next render pass is set up, the banding tests were actually completed, and aren't still on the cpu? (maybe barriers do so, but then they MUST be optional and not be put if no area lights are present.)

Vector<Vector2> positions = quad_tree->get_unique_points();
Vector<uint32_t> atlas_indices = quad_tree->get_point_atlas_indices();
Vector<float> weights = quad_tree->get_point_weights();
Expand Down
109 changes: 68 additions & 41 deletions servers/rendering/renderer_rd/storage_rd/light_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2826,16 +2826,17 @@ uint32_t LightStorage::area_shadow_atlas_update_light(RID p_atlas, RID p_light_i

AreaLightQuadTree *quad_tree = light_instance_get_area_shadow_quad_tree(p_light_instance);
uint32_t area_shadow_atlas_subdivision = area_shadow_atlas_get_subdivision(p_atlas); // TODO: for some reason, this returns 4 at initialization
int32_t occupied_slots = quad_tree->get_unique_points().size(); //shadow_atlas->occupied_maps; // TODO: logic needs to be more complex, than this. Take into account that stealing maps from higher depth maps should be ok.
int32_t free_slots = area_shadow_atlas_get_free_map_count(p_atlas);

const uint32_t max_samples = 289; // 17*17 // TODO: should be a constant somewhere
int32_t possible_samples = MIN(max_samples, area_shadow_atlas_subdivision * area_shadow_atlas_subdivision);
int32_t possible_samples = MIN(max_samples, free_slots);
// update buffers
HashSet<Vector2> pruned_samples;
HashMap<Vector2, uint32_t> removed_samples; // samples that should free their occupied shadow atlas slot
Vector<Vector2> added_samples; // samples that require new shadow atlas slots
Vector<Vector2> added_samples; // samples that require new shadow atlas slots

uint64_t tick = OS::get_singleton()->get_ticks_msec();

if (quad_tree->is_initialized()) { // banding was calculated once

int32_t points_delta = 0;
Expand All @@ -2858,9 +2859,9 @@ uint32_t LightStorage::area_shadow_atlas_update_light(RID p_atlas, RID p_light_i
AreaLightQuadTree::LeafIterator leaf_iterator = quad_tree->leaf_iterator();
while (leaf_iterator.has_next()) {
AreaLightQuadTree::SampleNode *node = leaf_iterator.next();
int32_t free_samples = possible_samples - occupied_slots - points_delta;
int32_t free_samples = possible_samples - points_delta;
if (get_banding_flag(node->get_banding_index()) && (free_samples - 5 >= 0 // optimization, as 5 is the maximum number of samples to be added
|| free_samples - (int32_t)quad_tree->get_number_of_new_points_on_expansion(node) >= 0)) {
|| free_samples - (int32_t)quad_tree->get_number_of_new_points_on_expansion(node) >= 0)) {
Vector<Vector2> samples = quad_tree->expand_node(node);
for (uint32_t i = 0; i < samples.size(); i++) {
if (!pruned_samples.has(samples[i])) {
Expand All @@ -2878,14 +2879,13 @@ uint32_t LightStorage::area_shadow_atlas_update_light(RID p_atlas, RID p_light_i

//_area_shadow_atlas_free_shadows(shadow_atlas, removed_samples); // TODO: mark them as unused, but in a way that they can be reenabled again if the same light wants it again.
for (HashMap<Vector2, uint32_t>::Iterator S = removed_samples.begin(); S; ++S) {
int shadow_count = shadow_atlas->shadows.size();
AreaShadowAtlas::Shadow *sarr = shadow_atlas->shadows.ptrw();
sarr[S->value].active = false;
}

} else {
// light is new, so we need slots for the first 4 samples
if (possible_samples - occupied_slots < 4) {
if (free_slots < 4) {
// tough luck, the shadow atlas is full. try next time.
return 0;
}
Expand All @@ -2909,53 +2909,56 @@ uint32_t LightStorage::area_shadow_atlas_update_light(RID p_atlas, RID p_light_i
// only the new sample points might need to be rendered

// check if any of them is still valid on the shadow atlas from a previous unsubdivision

for (int j = 0; j < shadow_count - 1; j++) {
uint64_t pass = 0;

if (sarr[j].owner.is_valid() && sarr[j].owner == p_light_instance && sarr[j].version == p_light_version) {
if (added_samples.has(sarr[j].light_sample_pos)) {
ERR_CONTINUE_MSG(sarr[j].active, "somehow there was still an active atlas slot for a 'new' sample");
quad_tree->set_atlas_index(sarr[j].light_sample_pos, j);
sarr[j].active = true;
added_samples.erase(sarr[j].light_sample_pos);
if (added_samples.size() != 0) {
for (int j = 0; j < shadow_count - 1; j++) {
uint64_t pass = 0;

if (sarr[j].owner.is_valid() && sarr[j].owner == p_light_instance && sarr[j].version == p_light_version) {
if (added_samples.has(sarr[j].light_sample_pos)) {
ERR_CONTINUE_MSG(sarr[j].active, "somehow there was still an active atlas slot for a 'new' sample");
quad_tree->set_atlas_index(sarr[j].light_sample_pos, j);
sarr[j].active = true;
added_samples.erase(sarr[j].light_sample_pos);
}
}
}
}
dirty_samples = added_samples;
}
if(dirty_samples.size() != 0) {
Vector<uint32_t> new_shadow_atlas_indices;
bool found_shadows = _area_shadow_atlas_find_shadows(p_atlas, shadow_atlas, tick, dirty_samples.size(), new_shadow_atlas_indices);
CRASH_COND(!found_shadows); // should never happen

Vector<uint32_t> new_shadow_atlas_indices;
bool found_shadows = _area_shadow_atlas_find_shadows(p_atlas, shadow_atlas, tick, dirty_samples.size(), new_shadow_atlas_indices);
CRASH_COND(!found_shadows); // should never happen, because we check above if enough slots are available
li->shadow_atlases.insert(p_atlas);

li->shadow_atlases.insert(p_atlas);
if (!shadow_atlas->shadow_owners.has(p_light_instance)) {
HashSet<uint32_t> *set = memnew(HashSet<uint32_t>);
shadow_atlas->shadow_owners.insert(p_light_instance, set); // TODO: needed? and does this even work without dynamic allocated memory?
}

if (!shadow_atlas->shadow_owners.has(p_light_instance)) {
HashSet<uint32_t> *set = memnew(HashSet<uint32_t>);
shadow_atlas->shadow_owners.insert(p_light_instance, set); // TODO: needed? and does this even work without dynamic allocated memory?
}
for (uint32_t i = 0; i < dirty_samples.size(); i++) {
AreaShadowAtlas::Shadow *sh = &sarr[new_shadow_atlas_indices[i]];

for (uint32_t i = 0; i < dirty_samples.size(); i++) {
AreaShadowAtlas::Shadow *sh = &sarr[new_shadow_atlas_indices[i]];
sh->active = true;
sh->alloc_tick = tick;
sh->owner = p_light_instance;
sh->version = p_light_version; // TODO: probably not needed

sh->active = true;
sh->alloc_tick = tick;
sh->owner = p_light_instance;
sh->version = p_light_version; // TODO: probably not needed

AreaShadowSample sample;
sample.atlas_index = new_shadow_atlas_indices[i];
sample.position_on_light = dirty_samples[i];
li->area_shadow_dirty_samples.push_back(sample);
quad_tree->set_atlas_index(dirty_samples[i], new_shadow_atlas_indices[i]);
AreaShadowSample sample;
sample.atlas_index = new_shadow_atlas_indices[i];
sample.position_on_light = dirty_samples[i];
li->area_shadow_dirty_samples.push_back(sample);
quad_tree->set_atlas_index(dirty_samples[i], new_shadow_atlas_indices[i]);

shadow_atlas->shadow_owners[p_light_instance]->insert(new_shadow_atlas_indices[i]);
shadow_atlas->shadow_owners[p_light_instance]->insert(new_shadow_atlas_indices[i]);
}
quad_tree->verify_atlas_indices();
}
quad_tree->verify_atlas_indices();

quad_tree->update_banding_buffer_indices(p_banding_buffer_offset);

quad_tree->initialize(); // TODO: this is not sound yet, because how do we guarantee, that when the next render pass is set up, the banding tests were actually completed, and aren't still on the cpu? (maybe barriers do so, but then they MUST be optional and not be put if no area lights are present.)

// TODO: check if you can remove it from here
Vector<Vector2> positions = quad_tree->get_unique_points();
Vector<uint32_t> atlas_indices = quad_tree->get_point_atlas_indices();
Expand All @@ -2968,6 +2971,28 @@ uint32_t LightStorage::area_shadow_atlas_update_light(RID p_atlas, RID p_light_i
return positions.size();
}

void LightStorage::area_shadow_atlas_update_active_lights(RID p_atlas, HashSet<RID> p_lights) {
AreaShadowAtlas *shadow_atlas = area_shadow_atlas_owner.get_or_null(p_atlas);
ERR_FAIL_NULL(shadow_atlas);

int shadow_count = shadow_atlas->shadows.size();
AreaShadowAtlas::Shadow *sarr = shadow_atlas->shadows.ptrw();
for (int j = 0; j < shadow_count - 1; j++) {
uint64_t pass = 0;

if (!sarr[j].owner.is_valid()) {
sarr[j].active = false;
sarr[j].owner = RID();
} else if (!p_lights.has(sarr[j].owner)) {
sarr[j].active = false;
AreaLightQuadTree *quad_tree = light_instance_get_area_shadow_quad_tree(sarr[j].owner);
if (quad_tree->is_initialized()) {
quad_tree->reset();
}
}
}
}

void LightStorage::_area_shadow_atlas_invalidate_shadow(RID p_atlas, AreaShadowAtlas *p_area_shadow_atlas, uint32_t p_shadow_idx) {
ERR_FAIL_INDEX(p_shadow_idx, p_area_shadow_atlas->shadows.size());
AreaShadowAtlas::Shadow *shadow = &p_area_shadow_atlas->shadows.write[p_shadow_idx];
Expand All @@ -2979,6 +3004,8 @@ void LightStorage::_area_shadow_atlas_invalidate_shadow(RID p_atlas, AreaShadowA
memdelete(p_area_shadow_atlas->shadow_owners[shadow->owner]); // delete hashset
p_area_shadow_atlas->shadow_owners.erase(shadow->owner);
sli->shadow_atlases.erase(p_atlas);
} else {
p_area_shadow_atlas->shadow_owners[shadow->owner]->erase(p_shadow_idx);
}

shadow->version = 0;
Expand Down
14 changes: 13 additions & 1 deletion servers/rendering/renderer_rd/storage_rd/light_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,6 @@ class LightStorage : public RendererLightStorage {
~AreaLightQuadTree() {
prune_node(&root);
}

private:

SampleNode root;
Expand Down Expand Up @@ -1698,8 +1697,21 @@ class LightStorage : public RendererLightStorage {
return atlas->reprojection_texture_size;
}

_FORCE_INLINE_ uint32_t area_shadow_atlas_get_free_map_count(RID p_atlas) {
AreaShadowAtlas *atlas = area_shadow_atlas_owner.get_or_null(p_atlas);
ERR_FAIL_NULL_V(atlas, 0);
uint32_t count = 0;
for (uint32_t i = 0; i < atlas->shadows.size(); i++) {
if (!atlas->shadows[i].active) {
count++;
}
}
return count;
}

virtual void area_shadow_atlas_update(RID p_atlas) override;
virtual void area_shadow_reprojection_update(RID p_atlas, const Vector2 &p_reprojection_texture_size, RID p_depth_texture) override;
virtual void area_shadow_atlas_update_active_lights(RID p_atlas, HashSet<RID> p_lights) override;

/* DIRECTIONAL SHADOW */

Expand Down
31 changes: 20 additions & 11 deletions servers/rendering/renderer_scene_cull.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2335,11 +2335,11 @@ bool RendererSceneCull::_light_instance_update_area_shadow(Instance *p_instance,
real_t z = -1; // TODO: verify that this is indeed the correct direction
Vector<Plane> planes;
planes.resize(6); // TODO: optimize this
planes.write[0] = light_transform.xform(Plane(Vector3(0, 0, z), radius + area_diagonal));
planes.write[1] = light_transform.xform(Plane(Vector3(1, 0, z).normalized(), radius + area_diagonal));
planes.write[2] = light_transform.xform(Plane(Vector3(-1, 0, z).normalized(), radius + area_diagonal));
planes.write[3] = light_transform.xform(Plane(Vector3(0, 1, z).normalized(), radius + area_diagonal));
planes.write[4] = light_transform.xform(Plane(Vector3(0, -1, z).normalized(), radius + area_diagonal));
planes.write[0] = light_transform.xform(Plane(Vector3(0, 0, z), radius + 2*area_diagonal));
planes.write[1] = light_transform.xform(Plane(Vector3(1, 0, z).normalized(), radius + 2*area_diagonal));
planes.write[2] = light_transform.xform(Plane(Vector3(-1, 0, z).normalized(), radius + 2*area_diagonal));
planes.write[3] = light_transform.xform(Plane(Vector3(0, 1, z).normalized(), radius + 2*area_diagonal));
planes.write[4] = light_transform.xform(Plane(Vector3(0, -1, z).normalized(), radius + 2*area_diagonal));
planes.write[5] = light_transform.xform(Plane(Vector3(0, 0, -z), 0));

instance_shadow_cull_result.clear();
Expand Down Expand Up @@ -3313,6 +3313,8 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c
}
}

HashSet<RID> active_area_lights;

// Positional Shadows
for (uint32_t i = 0; i < (uint32_t)scene_cull_result.lights.size(); i++) {
Instance *ins = scene_cull_result.lights[i];
Expand Down Expand Up @@ -3467,14 +3469,18 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c
if (max_shadows_used < MAX_UPDATE_SHADOWS) {

uint32_t sample_count = RSG::light_storage->area_shadow_atlas_update_light(p_area_shadow_atlas, light->instance, coverage, light->last_version, light->is_shadow_dirty(), area_shadow_banding_buffer_offset);
area_shadow_banding_buffer_offset += sample_count;

// returns true if either there is an animated material among the instances, or if the light wasn't yet updated due to the excessive number of lights
bool needs_update_next_frame = _light_instance_update_area_shadow(ins, p_camera_data->main_transform, p_camera_data->main_projection, p_camera_data->is_orthogonal, p_camera_data->vaspect, p_area_shadow_atlas, scenario, p_screen_mesh_lod_threshold, p_visible_layers);
if (sample_count != 0) {
area_shadow_banding_buffer_offset += sample_count;

// returns true if either there is an animated material among the instances, or if the light wasn't yet updated due to the excessive number of lights
bool needs_update_next_frame = _light_instance_update_area_shadow(ins, p_camera_data->main_transform, p_camera_data->main_projection, p_camera_data->is_orthogonal, p_camera_data->vaspect, p_area_shadow_atlas, scenario, p_screen_mesh_lod_threshold, p_visible_layers);

//if (redraw && max_area_shadows_used < MAX_UPDATE_AREA_SHADOWS) {
// // TODO
//}
active_area_lights.insert(light->instance);
//if (redraw && max_area_shadows_used < MAX_UPDATE_AREA_SHADOWS) {
// // TODO
//}
}
}

} else {
Expand All @@ -3494,6 +3500,9 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c
}
}
}

// deactivate all area shadow atlas slots that have not been used this frame
RSG::light_storage->area_shadow_atlas_update_active_lights(p_area_shadow_atlas, active_area_lights);
}

//render SDFGI
Expand Down
1 change: 1 addition & 0 deletions servers/rendering/storage/light_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class RendererLightStorage {
virtual uint32_t area_shadow_atlas_update_light(RID p_atlas, RID p_light_instance, float p_coverage, uint64_t p_light_version, bool p_is_dirty, uint32_t p_banding_buffer_offset) = 0;
virtual void area_shadow_atlas_update(RID p_atlas) = 0;
virtual void area_shadow_reprojection_update(RID p_atlas, const Vector2 &p_viewport_size, RID p_depth_texture) = 0;
virtual void area_shadow_atlas_update_active_lights(RID p_atlas, HashSet<RID> p_lights) = 0;

virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = true) = 0;
virtual int get_directional_light_shadow_size(RID p_light_intance) = 0;
Expand Down

0 comments on commit b0b0cda

Please sign in to comment.