diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 04e07531de..9469d1845c 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -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: diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 3ca8fcf346..8ecc41809c 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -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, diff --git a/servers/rendering/dummy/rasterizer_scene_dummy.h b/servers/rendering/dummy/rasterizer_scene_dummy.h index ad255124f8..813065d16d 100644 --- a/servers/rendering/dummy/rasterizer_scene_dummy.h +++ b/servers/rendering/dummy/rasterizer_scene_dummy.h @@ -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 {} diff --git a/servers/rendering/renderer_geometry_instance.h b/servers/rendering/renderer_geometry_instance.h index eb61307221..cab67d4349 100644 --- a/servers/rendering/renderer_geometry_instance.h +++ b/servers/rendering/renderer_geometry_instance.h @@ -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; diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h index 10507db89c..1a2008f495 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h @@ -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); diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index ad57551a07..90af8a9fcb 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -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: diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index a5545417bf..6ab52be235 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -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; diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index d291a7137a..f8bed29177 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -1957,7 +1957,7 @@ void RendererSceneCull::_unpair_instance(Instance *p_instance) { InstanceGeometryData *geom = static_cast(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> 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(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> 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(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(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; } diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h index 34285f9c2c..3fa71d06fd 100644 --- a/servers/rendering/renderer_scene_render.h +++ b/servers/rendering/renderer_scene_render.h @@ -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;