Merge pull request #107234 from jon1solution/select-near-lights-gles3-mobile

Select relevant 3D lights per mesh on GLES3 and Mobile renderers
This commit is contained in:
Thaddeus Crews
2026-02-03 08:13:57 -06:00
9 changed files with 180 additions and 44 deletions

View File

@@ -72,27 +72,39 @@ uint32_t RasterizerSceneGLES3::geometry_instance_get_pair_mask() {
return ((1 << RS::INSTANCE_LIGHT) | (1 << RS::INSTANCE_REFLECTION_PROBE));
}
void RasterizerSceneGLES3::GeometryInstanceGLES3::pair_light_instances(const RID *p_light_instances, uint32_t p_light_instance_count) {
GLES3::Config *config = GLES3::Config::get_singleton();
uint32_t RasterizerSceneGLES3::get_max_lights_total() {
return (uint32_t)GLES3::Config::get_singleton()->max_renderable_lights;
}
uint32_t RasterizerSceneGLES3::get_max_lights_per_mesh() {
return (uint32_t)GLES3::Config::get_singleton()->max_lights_per_object;
}
void RasterizerSceneGLES3::GeometryInstanceGLES3::clear_light_instances() {
paired_omni_light_count = 0;
paired_spot_light_count = 0;
paired_omni_lights.clear();
paired_spot_lights.clear();
}
for (uint32_t i = 0; i < p_light_instance_count; i++) {
RS::LightType type = GLES3::LightStorage::get_singleton()->light_instance_get_type(p_light_instances[i]);
switch (type) {
void RasterizerSceneGLES3::GeometryInstanceGLES3::pair_light_instance(
const RID p_light_instance, RS::LightType light_type, uint32_t placement_idx) {
if (placement_idx < GLES3::Config::get_singleton()->max_lights_per_object) {
switch (light_type) {
case RS::LIGHT_OMNI: {
if (paired_omni_light_count < (uint32_t)config->max_lights_per_object) {
paired_omni_lights.push_back(p_light_instances[i]);
paired_omni_light_count++;
if (placement_idx >= paired_omni_light_count) {
paired_omni_lights.push_back(p_light_instance);
++paired_omni_light_count;
} else {
paired_omni_lights[placement_idx] = p_light_instance;
}
} break;
case RS::LIGHT_SPOT: {
if (paired_spot_light_count < (uint32_t)config->max_lights_per_object) {
paired_spot_lights.push_back(p_light_instances[i]);
paired_spot_light_count++;
if (placement_idx >= paired_spot_light_count) {
paired_spot_lights.push_back(p_light_instance);
++paired_spot_light_count;
} else {
paired_spot_lights[placement_idx] = p_light_instance;
}
} break;
default:

View File

@@ -353,7 +353,8 @@ private:
virtual void set_use_lightmap(RID p_lightmap_instance, const Rect2 &p_lightmap_uv_scale, int p_lightmap_slice_index) override;
virtual void set_lightmap_capture(const Color *p_sh9) override;
virtual void pair_light_instances(const RID *p_light_instances, uint32_t p_light_instance_count) override;
virtual void clear_light_instances() override;
virtual void pair_light_instance(const RID p_light_instance, RS::LightType light_type, uint32_t placement_idx) override;
virtual void pair_reflection_probe_instances(const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count) override;
virtual void pair_decal_instances(const RID *p_decal_instances, uint32_t p_decal_instance_count) override {}
virtual void pair_voxel_gi_instances(const RID *p_voxel_gi_instances, uint32_t p_voxel_gi_instance_count) override {}
@@ -361,6 +362,9 @@ private:
virtual void set_softshadow_projector_pairing(bool p_softshadow, bool p_projector) override {}
};
virtual uint32_t get_max_lights_total() override;
virtual uint32_t get_max_lights_per_mesh() override;
enum {
INSTANCE_DATA_FLAGS_DYNAMIC = 1 << 3,
INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 4,

View File

@@ -66,7 +66,8 @@ public:
virtual Transform3D get_transform() override { return Transform3D(); }
virtual AABB get_aabb() override { return AABB(); }
virtual void pair_light_instances(const RID *p_light_instances, uint32_t p_light_instance_count) override {}
virtual void clear_light_instances() override {}
virtual void pair_light_instance(const RID p_light_instance, RS::LightType light_type, uint32_t placement_idx) override {}
virtual void pair_reflection_probe_instances(const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count) override {}
virtual void pair_decal_instances(const RID *p_decal_instances, uint32_t p_decal_instance_count) override {}
virtual void pair_voxel_gi_instances(const RID *p_voxel_gi_instances, uint32_t p_voxel_gi_instance_count) override {}
@@ -95,6 +96,9 @@ public:
uint32_t geometry_instance_get_pair_mask() override { return 0; }
virtual uint32_t get_max_lights_total() override { return 0; }
virtual uint32_t get_max_lights_per_mesh() override { return 0; }
/* PIPELINES */
virtual void mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) override {}

View File

@@ -66,7 +66,8 @@ public:
virtual Transform3D get_transform() = 0;
virtual AABB get_aabb() = 0;
virtual void pair_light_instances(const RID *p_light_instances, uint32_t p_light_instance_count) = 0;
virtual void clear_light_instances() = 0;
virtual void pair_light_instance(const RID p_light_instance, RS::LightType light_type, uint32_t placement_idx) = 0;
virtual void pair_reflection_probe_instances(const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count) = 0;
virtual void pair_decal_instances(const RID *p_decal_instances, uint32_t p_decal_instance_count) = 0;
virtual void pair_voxel_gi_instances(const RID *p_voxel_gi_instances, uint32_t p_voxel_gi_instance_count) = 0;

View File

@@ -589,7 +589,8 @@ private:
virtual void set_use_lightmap(RID p_lightmap_instance, const Rect2 &p_lightmap_uv_scale, int p_lightmap_slice_index) override;
virtual void set_lightmap_capture(const Color *p_sh9) override;
virtual void pair_light_instances(const RID *p_light_instances, uint32_t p_light_instance_count) override {}
virtual void clear_light_instances() override {}
virtual void pair_light_instance(const RID p_light_instance, RS::LightType light_type, uint32_t placement_idx) override {}
virtual void pair_reflection_probe_instances(const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count) override {}
virtual void pair_decal_instances(const RID *p_decal_instances, uint32_t p_decal_instance_count) override {}
virtual void pair_voxel_gi_instances(const RID *p_voxel_gi_instances, uint32_t p_voxel_gi_instance_count) override;
@@ -597,6 +598,10 @@ private:
virtual void set_softshadow_projector_pairing(bool p_softshadow, bool p_projector) override;
};
// These are not used in the Forward+ path, it has different light clustering tech.
virtual uint32_t get_max_lights_total() override { return 0; }
virtual uint32_t get_max_lights_per_mesh() override { return 0; }
static void _geometry_instance_dependency_changed(Dependency::DependencyChangedNotification p_notification, DependencyTracker *p_tracker);
static void _geometry_instance_dependency_deleted(const RID &p_dependency, DependencyTracker *p_tracker);

View File

@@ -2653,23 +2653,34 @@ uint32_t RenderForwardMobile::geometry_instance_get_pair_mask() {
return ((1 << RS::INSTANCE_LIGHT) + (1 << RS::INSTANCE_REFLECTION_PROBE) + (1 << RS::INSTANCE_DECAL));
}
void RenderForwardMobile::GeometryInstanceForwardMobile::pair_light_instances(const RID *p_light_instances, uint32_t p_light_instance_count) {
uint32_t RenderForwardMobile::get_max_lights_total() {
return (uint32_t)get_singleton()->get_max_elements();
}
uint32_t RenderForwardMobile::get_max_lights_per_mesh() {
return (uint32_t)MAX_RDL_CULL;
}
void RenderForwardMobile::GeometryInstanceForwardMobile::clear_light_instances() {
omni_light_count = 0;
spot_light_count = 0;
}
for (uint32_t i = 0; i < p_light_instance_count; i++) {
RS::LightType type = RendererRD::LightStorage::get_singleton()->light_instance_get_type(p_light_instances[i]);
switch (type) {
void RenderForwardMobile::GeometryInstanceForwardMobile::pair_light_instance(
const RID p_light_instance, RS::LightType light_type, uint32_t placement_idx) {
if (placement_idx < (uint32_t)MAX_RDL_CULL) {
RendererRD::ForwardID light_id = RendererRD::LightStorage::get_singleton()->light_instance_get_forward_id(p_light_instance);
switch (light_type) {
case RS::LIGHT_OMNI: {
if (omni_light_count < (uint32_t)MAX_RDL_CULL) {
omni_lights[omni_light_count] = RendererRD::LightStorage::get_singleton()->light_instance_get_forward_id(p_light_instances[i]);
omni_light_count++;
omni_lights[placement_idx] = light_id;
if (placement_idx >= omni_light_count) {
omni_light_count = placement_idx + 1;
}
} break;
case RS::LIGHT_SPOT: {
if (spot_light_count < (uint32_t)MAX_RDL_CULL) {
spot_lights[spot_light_count] = RendererRD::LightStorage::get_singleton()->light_instance_get_forward_id(p_light_instances[i]);
spot_light_count++;
spot_lights[placement_idx] = light_id;
if (placement_idx >= spot_light_count) {
spot_light_count = placement_idx + 1;
}
} break;
default:

View File

@@ -566,7 +566,8 @@ protected:
virtual void set_use_lightmap(RID p_lightmap_instance, const Rect2 &p_lightmap_uv_scale, int p_lightmap_slice_index) override;
virtual void set_lightmap_capture(const Color *p_sh9) override;
virtual void pair_light_instances(const RID *p_light_instances, uint32_t p_light_instance_count) override;
virtual void clear_light_instances() override;
virtual void pair_light_instance(const RID p_light_instance, RS::LightType light_type, uint32_t placement_idx) override;
virtual void pair_reflection_probe_instances(const RID *p_reflection_probe_instances, uint32_t p_reflection_probe_instance_count) override;
virtual void pair_decal_instances(const RID *p_decal_instances, uint32_t p_decal_instance_count) override;
virtual void pair_voxel_gi_instances(const RID *p_voxel_gi_instances, uint32_t p_voxel_gi_instance_count) override {}
@@ -574,6 +575,9 @@ protected:
virtual void set_softshadow_projector_pairing(bool p_softshadow, bool p_projector) override;
};
virtual uint32_t get_max_lights_total() override;
virtual uint32_t get_max_lights_per_mesh() override;
/* Rendering */
virtual void _render_scene(RenderDataRD *p_render_data, const Color &p_default_bg_color) override;

View File

@@ -1957,7 +1957,7 @@ void RendererSceneCull::_unpair_instance(Instance *p_instance) {
InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(p_instance->base_data);
ERR_FAIL_NULL(geom->geometry_instance);
geom->geometry_instance->pair_light_instances(nullptr, 0);
geom->geometry_instance->clear_light_instances();
geom->geometry_instance->pair_reflection_probe_instances(nullptr, 0);
geom->geometry_instance->pair_decal_instances(nullptr, 0);
geom->geometry_instance->pair_voxel_gi_instances(nullptr, 0);
@@ -2823,6 +2823,14 @@ void RendererSceneCull::_scene_cull(CullData &cull_data, InstanceCullResult &cul
RID instance_pair_buffer[MAX_INSTANCE_PAIRS];
// Minimize allocations when picking the most relevant lights per mesh.
// We need to track the score and current index of the best N lights.
thread_local LocalVector<Pair<float, uint32_t>> omni_score_idx, spot_score_idx;
omni_score_idx.clear();
spot_score_idx.clear();
uint32_t max_lights_per_mesh = scene_render->get_max_lights_per_mesh();
uint32_t max_lights_total = scene_render->get_max_lights_total();
Transform3D inv_cam_transform = cull_data.cam_transform.inverse();
float z_near = cull_data.camera_matrix->get_z_near();
bool is_orthogonal = cull_data.camera_matrix->is_orthogonal();
@@ -2934,26 +2942,109 @@ void RendererSceneCull::_scene_cull(CullData &cull_data, InstanceCullResult &cul
if (geometry_instance_pair_mask & (1 << RS::INSTANCE_LIGHT) && (idata.flags & InstanceData::FLAG_GEOM_LIGHTING_DIRTY)) {
InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(idata.instance->base_data);
uint32_t idx = 0;
ERR_FAIL_NULL(geom->geometry_instance);
// Clear any existing light instances for this mesh and find the max count per-mesh, and total (per-scene).
geom->geometry_instance->clear_light_instances();
if ((max_lights_per_mesh > 0) && (max_lights_total > 0)) {
// For the top N lights, track the score and the index into the internal light storage array.
uint32_t total_omni_count = 0, total_spot_count = 0;
bool omni_needs_heap = true, spot_needs_heap = true;
uint32_t omni_count = 0, spot_count = 0;
omni_score_idx.clear();
spot_score_idx.clear();
SortArray<Pair<float, uint32_t>> heapify; // SortArray has heap functions, but no local storage.
// Iterate over the lights (possibly > max_renderable_lights), keeping the closest to the mesh center.
Vector3 mesh_center = idata.instance->transformed_aabb.get_center();
for (const Instance *E : geom->lights) {
RS::LightType light_type = RSG::light_storage->light_get_type(E->base);
if (((RS::LIGHT_OMNI == light_type) && (total_omni_count++ < max_lights_total)) ||
((RS::LIGHT_SPOT == light_type) && (total_spot_count++ < max_lights_total))) {
// Perform culling.
if (!(RSG::light_storage->light_get_cull_mask(E->base) & idata.layer_mask)) {
continue;
}
if ((RSG::light_storage->light_get_bake_mode(E->base) == RS::LIGHT_BAKE_STATIC) && idata.instance->lightmap) {
continue;
}
for (const Instance *E : geom->lights) {
InstanceLightData *light = static_cast<InstanceLightData *>(E->base_data);
if (!(RSG::light_storage->light_get_cull_mask(E->base) & idata.layer_mask)) {
continue;
}
if ((RSG::light_storage->light_get_bake_mode(E->base) == RS::LIGHT_BAKE_STATIC) && idata.instance->lightmap) {
continue;
}
instance_pair_buffer[idx++] = light->instance;
if (idx == MAX_INSTANCE_PAIRS) {
break;
InstanceLightData *light = static_cast<InstanceLightData *>(E->base_data);
// Large scores are worse, so linear with distance, inverse with energy and range.
Vector3 light_center = E->transformed_aabb.get_center();
float light_range_energy =
RSG::light_storage->light_get_param(E->base, RS::LightParam::LIGHT_PARAM_RANGE) *
RSG::light_storage->light_get_param(E->base, RS::LightParam::LIGHT_PARAM_ENERGY);
float light_inst_score = mesh_center.distance_to(light_center) / MAX(0.01f, light_range_energy);
// Of the N lights (on a per-light-type basis, Omni or Spot) keep only the M "best" lights.
// If N <= M, we can simply store the lights, but once we exceed M, we need check each new
// light and see if it's score is better than the worst light stored to date. If the new
// light is better, we can replace the current worst light with the new one. In order to
// efficiently track our currently worst light we use a "max heap". This loosely orders
// the elements in an array as a binary-tree structure, and has the properties that finding
// the worst score element is O(1) (it will always be stored in element [0]), and removing
// the old max and inserting a new value is O(log M).
#define VERIFY_RELEVANT_LIGHT_HEAP 0
#if VERIFY_RELEVANT_LIGHT_HEAP
WARN_PRINT_ONCE("VERIFY_RELEVANT_LIGHT_HEAP is True");
#endif
switch (light_type) {
case RS::LIGHT_OMNI: {
if (omni_count < max_lights_per_mesh) {
// We have room to just add it, and track the score and where it goes.
omni_score_idx.push_back(Pair(light_inst_score, omni_count));
geom->geometry_instance->pair_light_instance(light->instance, light_type, omni_count++);
} else {
if (omni_needs_heap) {
// We need to make this a heap one time.
heapify.make_heap(0, omni_count, &omni_score_idx[0]);
omni_needs_heap = false;
}
if (light_inst_score < omni_score_idx[0].first) {
#if VERIFY_RELEVANT_LIGHT_HEAP
// The [0] element should have the max score.
for (uint32_t vi = 1; vi < max_lights_per_mesh; ++vi) {
if (omni_score_idx[vi].first > omni_score_idx[0].first) {
ERR_PRINT_ONCE("Relevant Omni Light Heap Error");
}
}
#endif
uint32_t replace_index = omni_score_idx[0].second;
geom->geometry_instance->pair_light_instance(light->instance, light_type, replace_index);
heapify.adjust_heap(0, 0, omni_count, Pair(light_inst_score, replace_index), &omni_score_idx[0]);
}
}
} break;
case RS::LIGHT_SPOT: {
if (spot_count < max_lights_per_mesh) {
// We have room to just add it, and track the score and where it goes.
spot_score_idx.push_back(Pair(light_inst_score, spot_count));
geom->geometry_instance->pair_light_instance(light->instance, light_type, spot_count++);
} else {
if (spot_needs_heap) {
// We need to make this a heap one time.
heapify.make_heap(0, spot_count, &spot_score_idx[0]);
spot_needs_heap = false;
}
if (light_inst_score < spot_score_idx[0].first) {
#if VERIFY_RELEVANT_LIGHT_HEAP
// The [0] element should have the max score.
for (uint32_t vi = 1; vi < max_lights_per_mesh; ++vi) {
if (spot_score_idx[vi].first > spot_score_idx[0].first) {
ERR_PRINT_ONCE("Relevant Spot Light Heap Error");
}
}
#endif
uint32_t replace_index = spot_score_idx[0].second;
geom->geometry_instance->pair_light_instance(light->instance, light_type, replace_index);
heapify.adjust_heap(0, 0, spot_count, Pair(light_inst_score, replace_index), &spot_score_idx[0]);
}
}
} break;
default:
break;
}
}
}
}
ERR_FAIL_NULL(geom->geometry_instance);
geom->geometry_instance->pair_light_instances(instance_pair_buffer, idx);
idata.flags &= ~InstanceData::FLAG_GEOM_LIGHTING_DIRTY;
}

View File

@@ -56,6 +56,10 @@ public:
virtual void geometry_instance_free(RenderGeometryInstance *p_geometry_instance) = 0;
virtual uint32_t geometry_instance_get_pair_mask() = 0;
/* Lights matched up to Geometry Instances */
virtual uint32_t get_max_lights_total() = 0;
virtual uint32_t get_max_lights_per_mesh() = 0;
/* PIPELINES */
virtual void mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) = 0;