Overhaul the cull mask internals for Lights, Decals, and Particle Colliders

Properly pair and unpair instances based on cull mask to avoid any unnecessary processing and to ensure that changing the cull_mask and layer_mask actually updates culling behavior
This commit is contained in:
clayjohn 2025-02-03 23:02:20 -08:00
parent 8f78e7510d
commit 305216f558
13 changed files with 67 additions and 14 deletions

View File

@ -209,7 +209,7 @@ void LightStorage::light_set_cull_mask(RID p_light, uint32_t p_mask) {
light->cull_mask = p_mask;
light->version++;
light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_CULL_MASK);
}
void LightStorage::light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) {

View File

@ -1319,6 +1319,13 @@ void ParticlesStorage::particles_collision_set_cull_mask(RID p_particles_collisi
ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
ERR_FAIL_NULL(particles_collision);
particles_collision->cull_mask = p_cull_mask;
particles_collision->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_CULL_MASK);
}
uint32_t ParticlesStorage::particles_collision_get_cull_mask(RID p_particles_collision) const {
ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
ERR_FAIL_NULL_V(particles_collision, 0);
return particles_collision->cull_mask;
}
void ParticlesStorage::particles_collision_set_sphere_radius(RID p_particles_collision, real_t p_radius) {

View File

@ -436,6 +436,7 @@ public:
GLuint particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const;
virtual uint32_t particles_collision_get_height_field_mask(RID p_particles_collision) const override;
virtual void particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) override;
virtual uint32_t particles_collision_get_cull_mask(RID p_particles_collision) const override;
_FORCE_INLINE_ Size2i particles_collision_get_heightfield_size(RID p_particles_collision) const {
ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);

View File

@ -115,6 +115,7 @@ public:
virtual bool particles_collision_is_heightfield(RID p_particles_collision) const override { return false; }
virtual uint32_t particles_collision_get_height_field_mask(RID p_particles_collision) const override { return 0; }
virtual void particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) override {}
virtual uint32_t particles_collision_get_cull_mask(RID p_particles_collision) const override { return 0; }
virtual RID particles_collision_instance_create(RID p_collision) override { return RID(); }
virtual void particles_collision_instance_free(RID p_rid) override {}

View File

@ -1120,9 +1120,6 @@ void main() {
uvec2 decal_indices = instances.data[draw_call.instance_index].decals;
for (uint i = 0; i < sc_decals(); i++) {
uint decal_index = (i > 3) ? ((decal_indices.y >> ((i - 4) * 8)) & 0xFF) : ((decal_indices.x >> (i * 8)) & 0xFF);
if (!bool(decals.data[decal_index].mask & instances.data[draw_call.instance_index].layer_mask)) {
continue; //not masked
}
vec3 uv_local = (decals.data[decal_index].xform * vec4(vertex, 1.0)).xyz;
if (any(lessThan(uv_local, vec3(0.0, -1.0, 0.0))) || any(greaterThan(uv_local, vec3(1.0)))) {

View File

@ -268,7 +268,7 @@ void LightStorage::light_set_cull_mask(RID p_light, uint32_t p_mask) {
light->cull_mask = p_mask;
light->version++;
light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_CULL_MASK);
}
void LightStorage::light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) {

View File

@ -1850,6 +1850,13 @@ void ParticlesStorage::particles_collision_set_cull_mask(RID p_particles_collisi
ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
ERR_FAIL_NULL(particles_collision);
particles_collision->cull_mask = p_cull_mask;
particles_collision->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_CULL_MASK);
}
uint32_t ParticlesStorage::particles_collision_get_cull_mask(RID p_particles_collision) const {
ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
ERR_FAIL_NULL_V(particles_collision, 0);
return particles_collision->cull_mask;
}
uint32_t ParticlesStorage::particles_collision_get_height_field_mask(RID p_particles_collision) const {

View File

@ -583,6 +583,7 @@ public:
RID particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const;
virtual uint32_t particles_collision_get_height_field_mask(RID p_particles_collision) const override;
virtual void particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) override;
virtual uint32_t particles_collision_get_cull_mask(RID p_particles_collision) const override;
Dependency *particles_collision_get_dependency(RID p_particles) const;

View File

@ -2744,7 +2744,7 @@ void TextureStorage::decal_set_cull_mask(RID p_decal, uint32_t p_layers) {
Decal *decal = decal_owner.get_or_null(p_decal);
ERR_FAIL_NULL(decal);
decal->cull_mask = p_layers;
decal->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_DECAL);
decal->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_CULL_MASK);
}
void TextureStorage::decal_set_distance_fade(RID p_decal, bool p_enabled, float p_begin, float p_length) {

View File

@ -178,6 +178,11 @@ void RendererSceneCull::_instance_pair(Instance *p_A, Instance *p_B) {
InstanceLightData *light = static_cast<InstanceLightData *>(B->base_data);
InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(A->base_data);
if (!(light->cull_mask & A->layer_mask)) {
// Early return if the object's layer mask doesn't match the light's cull mask.
return;
}
geom->lights.insert(B);
light->geometries.insert(A);
@ -222,6 +227,11 @@ void RendererSceneCull::_instance_pair(Instance *p_A, Instance *p_B) {
InstanceDecalData *decal = static_cast<InstanceDecalData *>(B->base_data);
InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(A->base_data);
if (!(decal->cull_mask & A->layer_mask)) {
// Early return if the object's layer mask doesn't match the decal's cull mask.
return;
}
geom->decals.insert(B);
decal->geometries.insert(A);
@ -267,7 +277,10 @@ void RendererSceneCull::_instance_pair(Instance *p_A, Instance *p_B) {
voxel_gi->lights.insert(A);
} else if (B->base_type == RS::INSTANCE_PARTICLES_COLLISION && A->base_type == RS::INSTANCE_PARTICLES) {
InstanceParticlesCollisionData *collision = static_cast<InstanceParticlesCollisionData *>(B->base_data);
RSG::particles_storage->particles_add_collision(A->base, collision->instance);
if ((collision->cull_mask & A->layer_mask)) {
RSG::particles_storage->particles_add_collision(A->base, collision->instance);
}
}
}
@ -285,6 +298,11 @@ void RendererSceneCull::_instance_unpair(Instance *p_A, Instance *p_B) {
InstanceLightData *light = static_cast<InstanceLightData *>(B->base_data);
InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(A->base_data);
if (!(light->cull_mask & A->layer_mask)) {
// Early return if the object's layer mask doesn't match the light's cull mask.
return;
}
geom->lights.erase(B);
light->geometries.erase(A);
@ -339,6 +357,11 @@ void RendererSceneCull::_instance_unpair(Instance *p_A, Instance *p_B) {
InstanceDecalData *decal = static_cast<InstanceDecalData *>(B->base_data);
InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(A->base_data);
if (!(decal->cull_mask & A->layer_mask)) {
// Early return if the object's layer mask doesn't match the decal's cull mask.
return;
}
geom->decals.erase(B);
decal->geometries.erase(A);
@ -383,7 +406,10 @@ void RendererSceneCull::_instance_unpair(Instance *p_A, Instance *p_B) {
voxel_gi->lights.erase(A);
} else if (B->base_type == RS::INSTANCE_PARTICLES_COLLISION && A->base_type == RS::INSTANCE_PARTICLES) {
InstanceParticlesCollisionData *collision = static_cast<InstanceParticlesCollisionData *>(B->base_data);
RSG::particles_storage->particles_remove_collision(A->base, collision->instance);
if ((collision->cull_mask & A->layer_mask)) {
RSG::particles_storage->particles_remove_collision(A->base, collision->instance);
}
}
}
@ -888,6 +914,14 @@ void RendererSceneCull::instance_set_layer_mask(RID p_instance, uint32_t p_mask)
return;
}
// Particles always need to be unpaired. Geometry may need to be unpaired, but only if lights or decals use pairing.
// Needs to happen before layer mask changes so we can avoid attempting to unpair something that was never paired.
if (instance->base_type == RS::INSTANCE_PARTICLES ||
(((geometry_instance_pair_mask & (1 << RS::INSTANCE_LIGHT)) || (geometry_instance_pair_mask & (1 << RS::INSTANCE_DECAL))) && ((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK))) {
_unpair_instance(instance);
singleton->_instance_queue_update(instance, false, false);
}
instance->layer_mask = p_mask;
if (instance->scenario && instance->array_index >= 0) {
instance->scenario->instance_data[instance->array_index].layer_mask = p_mask;
@ -1592,6 +1626,7 @@ void RendererSceneCull::_update_instance(Instance *p_instance) const {
if (light->max_sdfgi_cascade != max_sdfgi_cascade) {
light->max_sdfgi_cascade = max_sdfgi_cascade; //should most likely make sdfgi dirty in scenario
}
light->cull_mask = RSG::light_storage->light_get_cull_mask(p_instance->base);
} else if (p_instance->base_type == RS::INSTANCE_REFLECTION_PROBE) {
InstanceReflectionProbeData *reflection_probe = static_cast<InstanceReflectionProbeData *>(p_instance->base_data);
@ -1605,6 +1640,7 @@ void RendererSceneCull::_update_instance(Instance *p_instance) const {
InstanceDecalData *decal = static_cast<InstanceDecalData *>(p_instance->base_data);
RSG::texture_storage->decal_instance_set_transform(decal->instance, *instance_xform);
decal->cull_mask = RSG::texture_storage->decal_get_cull_mask(p_instance->base);
} else if (p_instance->base_type == RS::INSTANCE_LIGHTMAP) {
InstanceLightmapData *lightmap = static_cast<InstanceLightmapData *>(p_instance->base_data);
@ -1623,6 +1659,7 @@ void RendererSceneCull::_update_instance(Instance *p_instance) const {
heightfield_particle_colliders_update_list.insert(p_instance);
}
RSG::particles_storage->particles_collision_instance_set_transform(collision->instance, *instance_xform);
collision->cull_mask = RSG::particles_storage->particles_collision_get_cull_mask(p_instance->base);
} else if (p_instance->base_type == RS::INSTANCE_FOG_VOLUME) {
InstanceFogVolumeData *volume = static_cast<InstanceFogVolumeData *>(p_instance->base_data);
scene_render->fog_volume_instance_set_transform(volume->instance, *instance_xform);
@ -1818,7 +1855,6 @@ void RendererSceneCull::_update_instance(Instance *p_instance) const {
pair.pair_allocator = &pair_allocator;
pair.pair_pass = pair_pass;
pair.pair_mask = 0;
pair.cull_mask = 0xFFFFFFFF;
if ((1 << p_instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) {
pair.pair_mask |= 1 << RS::INSTANCE_LIGHT;
@ -1840,7 +1876,6 @@ void RendererSceneCull::_update_instance(Instance *p_instance) const {
pair.pair_mask |= (1 << RS::INSTANCE_VOXEL_GI);
pair.bvh2 = &p_instance->scenario->indexers[Scenario::INDEXER_VOLUMES];
}
pair.cull_mask = RSG::light_storage->light_get_cull_mask(p_instance->base);
} else if (p_instance->base_type == RS::INSTANCE_LIGHTMAP) {
pair.pair_mask = RS::INSTANCE_GEOMETRY_MASK;
pair.bvh = &p_instance->scenario->indexers[Scenario::INDEXER_GEOMETRY];
@ -1850,7 +1885,6 @@ void RendererSceneCull::_update_instance(Instance *p_instance) const {
} else if (geometry_instance_pair_mask & (1 << RS::INSTANCE_DECAL) && (p_instance->base_type == RS::INSTANCE_DECAL)) {
pair.pair_mask = RS::INSTANCE_GEOMETRY_MASK;
pair.bvh = &p_instance->scenario->indexers[Scenario::INDEXER_GEOMETRY];
pair.cull_mask = RSG::texture_storage->decal_get_cull_mask(p_instance->base);
} else if (p_instance->base_type == RS::INSTANCE_PARTICLES_COLLISION) {
pair.pair_mask = (1 << RS::INSTANCE_PARTICLES);
pair.bvh = &p_instance->scenario->indexers[Scenario::INDEXER_GEOMETRY];

View File

@ -508,7 +508,8 @@ public:
case Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE: {
singleton->_instance_queue_update(instance, true, true);
} break;
case Dependency::DEPENDENCY_CHANGED_LIGHT_SOFT_SHADOW_AND_PROJECTOR: {
case Dependency::DEPENDENCY_CHANGED_LIGHT_SOFT_SHADOW_AND_PROJECTOR:
case Dependency::DEPENDENCY_CHANGED_CULL_MASK: {
//requires repairing
if (instance->indexer_id.is_valid()) {
singleton->_unpair_instance(instance);
@ -655,6 +656,7 @@ public:
struct InstanceDecalData : public InstanceBaseData {
Instance *owner = nullptr;
RID instance;
uint32_t cull_mask = 0xFFFFFFFF;
HashSet<Instance *> geometries;
@ -666,6 +668,7 @@ public:
struct InstanceParticlesCollisionData : public InstanceBaseData {
RID instance;
uint32_t cull_mask = 0xFFFFFFFF;
};
struct InstanceFogVolumeData : public InstanceBaseData {
@ -699,6 +702,7 @@ public:
RS::LightBakeMode bake_mode;
uint32_t max_sdfgi_cascade = 2;
uint32_t cull_mask = 0xFFFFFFFF;
private:
// Instead of a single dirty flag, we maintain a count
@ -817,12 +821,11 @@ public:
DynamicBVH *bvh2 = nullptr; //some may need to cull in two
uint32_t pair_mask;
uint64_t pair_pass;
uint32_t cull_mask = 0xFFFFFFFF; // Needed for decals and lights in the mobile and compatibility renderers.
_FORCE_INLINE_ bool operator()(void *p_data) {
Instance *p_instance = (Instance *)p_data;
if (instance != p_instance && instance->transformed_aabb.intersects(p_instance->transformed_aabb) && (pair_mask & (1 << p_instance->base_type)) && (cull_mask & p_instance->layer_mask)) {
if (instance != p_instance && instance->transformed_aabb.intersects(p_instance->transformed_aabb) && (pair_mask & (1 << p_instance->base_type))) {
//test is more coarse in indexer
p_instance->pair_check = pair_pass;
InstancePair *pair = pair_allocator->alloc();

View File

@ -121,6 +121,7 @@ public:
virtual bool particles_collision_is_heightfield(RID p_particles_collision) const = 0;
virtual uint32_t particles_collision_get_height_field_mask(RID p_particles_collision) const = 0;
virtual void particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) = 0;
virtual uint32_t particles_collision_get_cull_mask(RID p_particles_collision) const = 0;
//used from 2D and 3D
virtual RID particles_collision_instance_create(RID p_collision) = 0;

View File

@ -50,6 +50,7 @@ public:
DEPENDENCY_CHANGED_LIGHT,
DEPENDENCY_CHANGED_LIGHT_SOFT_SHADOW_AND_PROJECTOR,
DEPENDENCY_CHANGED_REFLECTION_PROBE,
DEPENDENCY_CHANGED_CULL_MASK,
};
void changed_notify(DependencyChangedNotification p_notification);