Ensure that only visible paired lights are used
This is a longstanding issue in both the Mobile and GL Compatibility renderer. Meshes pair with all lights that touch them, and then at draw time, we send all paired lights indices to the shader (even if that light isn't visible). The problem is that non-visible lights aren't uploaded to the GPU and don't have an index. So we end up using a bogus index
This commit is contained in:
parent
fd33c7b32f
commit
623a050269
|
@ -1254,9 +1254,14 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const
|
||||||
inst->light_passes.clear();
|
inst->light_passes.clear();
|
||||||
inst->spot_light_gl_cache.clear();
|
inst->spot_light_gl_cache.clear();
|
||||||
inst->omni_light_gl_cache.clear();
|
inst->omni_light_gl_cache.clear();
|
||||||
|
uint64_t current_frame = RSG::rasterizer->get_frame_number();
|
||||||
|
|
||||||
if (inst->paired_omni_light_count) {
|
if (inst->paired_omni_light_count) {
|
||||||
for (uint32_t j = 0; j < inst->paired_omni_light_count; j++) {
|
for (uint32_t j = 0; j < inst->paired_omni_light_count; j++) {
|
||||||
RID light_instance = inst->paired_omni_lights[j];
|
RID light_instance = inst->paired_omni_lights[j];
|
||||||
|
if (GLES3::LightStorage::get_singleton()->light_instance_get_render_pass(light_instance) != current_frame) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
RID light = GLES3::LightStorage::get_singleton()->light_instance_get_base_light(light_instance);
|
RID light = GLES3::LightStorage::get_singleton()->light_instance_get_base_light(light_instance);
|
||||||
int32_t shadow_id = GLES3::LightStorage::get_singleton()->light_instance_get_shadow_id(light_instance);
|
int32_t shadow_id = GLES3::LightStorage::get_singleton()->light_instance_get_shadow_id(light_instance);
|
||||||
|
|
||||||
|
@ -1277,6 +1282,9 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const
|
||||||
if (inst->paired_spot_light_count) {
|
if (inst->paired_spot_light_count) {
|
||||||
for (uint32_t j = 0; j < inst->paired_spot_light_count; j++) {
|
for (uint32_t j = 0; j < inst->paired_spot_light_count; j++) {
|
||||||
RID light_instance = inst->paired_spot_lights[j];
|
RID light_instance = inst->paired_spot_lights[j];
|
||||||
|
if (GLES3::LightStorage::get_singleton()->light_instance_get_render_pass(light_instance) != current_frame) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
RID light = GLES3::LightStorage::get_singleton()->light_instance_get_base_light(light_instance);
|
RID light = GLES3::LightStorage::get_singleton()->light_instance_get_base_light(light_instance);
|
||||||
int32_t shadow_id = GLES3::LightStorage::get_singleton()->light_instance_get_shadow_id(light_instance);
|
int32_t shadow_id = GLES3::LightStorage::get_singleton()->light_instance_get_shadow_id(light_instance);
|
||||||
|
|
||||||
|
@ -1712,6 +1720,8 @@ void RasterizerSceneGLES3::_setup_lights(const RenderDataGLES3 *p_render_data, b
|
||||||
r_spot_light_count++;
|
r_spot_light_count++;
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li->last_pass = RSG::rasterizer->get_frame_number();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r_omni_light_count) {
|
if (r_omni_light_count) {
|
||||||
|
|
|
@ -53,6 +53,7 @@ RendererRD::ForwardID RenderForwardMobile::ForwardIDStorageMobile::allocate_forw
|
||||||
index = forward_id_allocators[p_type].allocations.size();
|
index = forward_id_allocators[p_type].allocations.size();
|
||||||
forward_id_allocators[p_type].allocations.push_back(true);
|
forward_id_allocators[p_type].allocations.push_back(true);
|
||||||
forward_id_allocators[p_type].map.push_back(0xFF);
|
forward_id_allocators[p_type].map.push_back(0xFF);
|
||||||
|
forward_id_allocators[p_type].last_pass.push_back(0);
|
||||||
} else {
|
} else {
|
||||||
forward_id_allocators[p_type].allocations[index] = true;
|
forward_id_allocators[p_type].allocations[index] = true;
|
||||||
}
|
}
|
||||||
|
@ -64,44 +65,72 @@ void RenderForwardMobile::ForwardIDStorageMobile::free_forward_id(RendererRD::Fo
|
||||||
forward_id_allocators[p_type].allocations[p_id] = false;
|
forward_id_allocators[p_type].allocations[p_id] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderForwardMobile::ForwardIDStorageMobile::map_forward_id(RendererRD::ForwardIDType p_type, RendererRD::ForwardID p_id, uint32_t p_index) {
|
void RenderForwardMobile::ForwardIDStorageMobile::map_forward_id(RendererRD::ForwardIDType p_type, RendererRD::ForwardID p_id, uint32_t p_index, uint64_t p_last_pass) {
|
||||||
forward_id_allocators[p_type].map[p_id] = p_index;
|
forward_id_allocators[p_type].map[p_id] = p_index;
|
||||||
|
forward_id_allocators[p_type].last_pass[p_id] = p_last_pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderForwardMobile::fill_push_constant_instance_indices(SceneState::InstanceData *p_instance_data, const GeometryInstanceForwardMobile *p_instance) {
|
void RenderForwardMobile::fill_push_constant_instance_indices(SceneState::InstanceData *p_instance_data, const GeometryInstanceForwardMobile *p_instance) {
|
||||||
// First zero out our indices.
|
uint64_t current_frame = RSG::rasterizer->get_frame_number();
|
||||||
|
|
||||||
p_instance_data->omni_lights[0] = 0xFFFFFFFF;
|
p_instance_data->omni_lights[0] = 0xFFFFFFFF;
|
||||||
p_instance_data->omni_lights[1] = 0xFFFFFFFF;
|
p_instance_data->omni_lights[1] = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
uint32_t idx = 0;
|
||||||
|
for (uint32_t i = 0; i < p_instance->omni_light_count; i++) {
|
||||||
|
uint32_t ofs = idx < 4 ? 0 : 1;
|
||||||
|
uint32_t shift = (idx & 0x3) << 3;
|
||||||
|
uint32_t mask = ~(0xFF << shift);
|
||||||
|
|
||||||
|
if (forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_OMNI_LIGHT].last_pass[p_instance->omni_lights[i]] == current_frame) {
|
||||||
|
p_instance_data->omni_lights[ofs] &= mask;
|
||||||
|
p_instance_data->omni_lights[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_OMNI_LIGHT].map[p_instance->omni_lights[i]]) << shift;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p_instance_data->spot_lights[0] = 0xFFFFFFFF;
|
p_instance_data->spot_lights[0] = 0xFFFFFFFF;
|
||||||
p_instance_data->spot_lights[1] = 0xFFFFFFFF;
|
p_instance_data->spot_lights[1] = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
idx = 0;
|
||||||
|
for (uint32_t i = 0; i < p_instance->spot_light_count; i++) {
|
||||||
|
uint32_t ofs = idx < 4 ? 0 : 1;
|
||||||
|
uint32_t shift = (idx & 0x3) << 3;
|
||||||
|
uint32_t mask = ~(0xFF << shift);
|
||||||
|
if (forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_SPOT_LIGHT].last_pass[p_instance->spot_lights[i]] == current_frame) {
|
||||||
|
p_instance_data->spot_lights[ofs] &= mask;
|
||||||
|
p_instance_data->spot_lights[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_SPOT_LIGHT].map[p_instance->spot_lights[i]]) << shift;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p_instance_data->decals[0] = 0xFFFFFFFF;
|
p_instance_data->decals[0] = 0xFFFFFFFF;
|
||||||
p_instance_data->decals[1] = 0xFFFFFFFF;
|
p_instance_data->decals[1] = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
idx = 0;
|
||||||
|
for (uint32_t i = 0; i < p_instance->decals_count; i++) {
|
||||||
|
uint32_t ofs = idx < 4 ? 0 : 1;
|
||||||
|
uint32_t shift = (idx & 0x3) << 3;
|
||||||
|
uint32_t mask = ~(0xFF << shift);
|
||||||
|
if (forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_DECAL].last_pass[p_instance->decals[i]] == current_frame) {
|
||||||
|
p_instance_data->decals[ofs] &= mask;
|
||||||
|
p_instance_data->decals[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_DECAL].map[p_instance->decals[i]]) << shift;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p_instance_data->reflection_probes[0] = 0xFFFFFFFF;
|
p_instance_data->reflection_probes[0] = 0xFFFFFFFF;
|
||||||
p_instance_data->reflection_probes[1] = 0xFFFFFFFF;
|
p_instance_data->reflection_probes[1] = 0xFFFFFFFF;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < MAX_RDL_CULL; i++) {
|
idx = 0;
|
||||||
uint32_t ofs = i < 4 ? 0 : 1;
|
for (uint32_t i = 0; i < p_instance->reflection_probe_count; i++) {
|
||||||
uint32_t shift = (i & 0x3) << 3;
|
uint32_t ofs = idx < 4 ? 0 : 1;
|
||||||
|
uint32_t shift = (idx & 0x3) << 3;
|
||||||
uint32_t mask = ~(0xFF << shift);
|
uint32_t mask = ~(0xFF << shift);
|
||||||
if (i < p_instance->omni_light_count) {
|
if (forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_REFLECTION_PROBE].last_pass[p_instance->reflection_probes[i]] == current_frame) {
|
||||||
p_instance_data->omni_lights[ofs] &= mask;
|
|
||||||
p_instance_data->omni_lights[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_OMNI_LIGHT].map[p_instance->omni_lights[i]]) << shift;
|
|
||||||
}
|
|
||||||
if (i < p_instance->spot_light_count) {
|
|
||||||
p_instance_data->spot_lights[ofs] &= mask;
|
|
||||||
p_instance_data->spot_lights[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_SPOT_LIGHT].map[p_instance->spot_lights[i]]) << shift;
|
|
||||||
}
|
|
||||||
if (i < p_instance->decals_count) {
|
|
||||||
p_instance_data->decals[ofs] &= mask;
|
|
||||||
p_instance_data->decals[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_DECAL].map[p_instance->decals[i]]) << shift;
|
|
||||||
}
|
|
||||||
if (i < p_instance->reflection_probe_count) {
|
|
||||||
p_instance_data->reflection_probes[ofs] &= mask;
|
p_instance_data->reflection_probes[ofs] &= mask;
|
||||||
p_instance_data->reflection_probes[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_REFLECTION_PROBE].map[p_instance->reflection_probes[i]]) << shift;
|
p_instance_data->reflection_probes[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_REFLECTION_PROBE].map[p_instance->reflection_probes[i]]) << shift;
|
||||||
|
idx++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -539,7 +568,6 @@ void RenderForwardMobile::_setup_lightmaps(const RenderDataRD *p_render_data, co
|
||||||
|
|
||||||
void RenderForwardMobile::_pre_opaque_render(RenderDataRD *p_render_data) {
|
void RenderForwardMobile::_pre_opaque_render(RenderDataRD *p_render_data) {
|
||||||
RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton();
|
RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton();
|
||||||
RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
|
|
||||||
|
|
||||||
p_render_data->cube_shadows.clear();
|
p_render_data->cube_shadows.clear();
|
||||||
p_render_data->shadows.clear();
|
p_render_data->shadows.clear();
|
||||||
|
@ -598,28 +626,11 @@ void RenderForwardMobile::_pre_opaque_render(RenderDataRD *p_render_data) {
|
||||||
|
|
||||||
//full barrier here, we need raster, transfer and compute and it depends from the previous work
|
//full barrier here, we need raster, transfer and compute and it depends from the previous work
|
||||||
RD::get_singleton()->barrier(RD::BARRIER_MASK_ALL_BARRIERS, RD::BARRIER_MASK_ALL_BARRIERS);
|
RD::get_singleton()->barrier(RD::BARRIER_MASK_ALL_BARRIERS, RD::BARRIER_MASK_ALL_BARRIERS);
|
||||||
|
|
||||||
bool using_shadows = true;
|
|
||||||
|
|
||||||
if (p_render_data->reflection_probe.is_valid()) {
|
|
||||||
if (!RSG::light_storage->reflection_probe_renders_shadows(light_storage->reflection_probe_instance_get_probe(p_render_data->reflection_probe))) {
|
|
||||||
using_shadows = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//do not render reflections when rendering a reflection probe
|
|
||||||
light_storage->update_reflection_probe_buffer(p_render_data, *p_render_data->reflection_probes, p_render_data->scene_data->cam_transform.affine_inverse(), p_render_data->environment);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t directional_light_count = 0;
|
|
||||||
uint32_t positional_light_count = 0;
|
|
||||||
light_storage->update_light_buffers(p_render_data, *p_render_data->lights, p_render_data->scene_data->cam_transform, p_render_data->shadow_atlas, using_shadows, directional_light_count, positional_light_count, p_render_data->directional_light_soft_shadows);
|
|
||||||
texture_storage->update_decal_buffer(*p_render_data->decals, p_render_data->scene_data->cam_transform);
|
|
||||||
|
|
||||||
p_render_data->directional_light_count = directional_light_count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color &p_default_bg_color) {
|
void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color &p_default_bg_color) {
|
||||||
RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton();
|
RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton();
|
||||||
|
RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
|
||||||
|
|
||||||
ERR_FAIL_NULL(p_render_data);
|
ERR_FAIL_NULL(p_render_data);
|
||||||
|
|
||||||
|
@ -668,6 +679,25 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
|
||||||
bool using_subpass_transparent = true;
|
bool using_subpass_transparent = true;
|
||||||
bool using_subpass_post_process = true;
|
bool using_subpass_post_process = true;
|
||||||
|
|
||||||
|
bool using_shadows = true;
|
||||||
|
|
||||||
|
if (p_render_data->reflection_probe.is_valid()) {
|
||||||
|
if (!RSG::light_storage->reflection_probe_renders_shadows(light_storage->reflection_probe_instance_get_probe(p_render_data->reflection_probe))) {
|
||||||
|
using_shadows = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//do not render reflections when rendering a reflection probe
|
||||||
|
light_storage->update_reflection_probe_buffer(p_render_data, *p_render_data->reflection_probes, p_render_data->scene_data->cam_transform.affine_inverse(), p_render_data->environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update light and decal buffer first so we know what lights and decals are safe to pair with.
|
||||||
|
uint32_t directional_light_count = 0;
|
||||||
|
uint32_t positional_light_count = 0;
|
||||||
|
light_storage->update_light_buffers(p_render_data, *p_render_data->lights, p_render_data->scene_data->cam_transform, p_render_data->shadow_atlas, using_shadows, directional_light_count, positional_light_count, p_render_data->directional_light_soft_shadows);
|
||||||
|
texture_storage->update_decal_buffer(*p_render_data->decals, p_render_data->scene_data->cam_transform);
|
||||||
|
|
||||||
|
p_render_data->directional_light_count = directional_light_count;
|
||||||
|
|
||||||
// fill our render lists early so we can find out if we use various features
|
// fill our render lists early so we can find out if we use various features
|
||||||
_fill_render_list(RENDER_LIST_OPAQUE, p_render_data, PASS_MODE_COLOR);
|
_fill_render_list(RENDER_LIST_OPAQUE, p_render_data, PASS_MODE_COLOR);
|
||||||
render_list[RENDER_LIST_OPAQUE].sort_by_key();
|
render_list[RENDER_LIST_OPAQUE].sort_by_key();
|
||||||
|
|
|
@ -535,6 +535,7 @@ protected:
|
||||||
struct ForwardIDAllocator {
|
struct ForwardIDAllocator {
|
||||||
LocalVector<bool> allocations;
|
LocalVector<bool> allocations;
|
||||||
LocalVector<uint8_t> map;
|
LocalVector<uint8_t> map;
|
||||||
|
LocalVector<uint64_t> last_pass;
|
||||||
};
|
};
|
||||||
|
|
||||||
ForwardIDAllocator forward_id_allocators[RendererRD::FORWARD_ID_MAX];
|
ForwardIDAllocator forward_id_allocators[RendererRD::FORWARD_ID_MAX];
|
||||||
|
@ -542,7 +543,7 @@ protected:
|
||||||
public:
|
public:
|
||||||
virtual RendererRD::ForwardID allocate_forward_id(RendererRD::ForwardIDType p_type) override;
|
virtual RendererRD::ForwardID allocate_forward_id(RendererRD::ForwardIDType p_type) override;
|
||||||
virtual void free_forward_id(RendererRD::ForwardIDType p_type, RendererRD::ForwardID p_id) override;
|
virtual void free_forward_id(RendererRD::ForwardIDType p_type, RendererRD::ForwardID p_id) override;
|
||||||
virtual void map_forward_id(RendererRD::ForwardIDType p_type, RendererRD::ForwardID p_id, uint32_t p_index) override;
|
virtual void map_forward_id(RendererRD::ForwardIDType p_type, RendererRD::ForwardID p_id, uint32_t p_index, uint64_t p_last_pass) override;
|
||||||
virtual bool uses_forward_ids() const override { return true; }
|
virtual bool uses_forward_ids() const override { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ public:
|
||||||
|
|
||||||
virtual RendererRD::ForwardID allocate_forward_id(RendererRD::ForwardIDType p_type) { return -1; }
|
virtual RendererRD::ForwardID allocate_forward_id(RendererRD::ForwardIDType p_type) { return -1; }
|
||||||
virtual void free_forward_id(RendererRD::ForwardIDType p_type, RendererRD::ForwardID p_id) {}
|
virtual void free_forward_id(RendererRD::ForwardIDType p_type, RendererRD::ForwardID p_id) {}
|
||||||
virtual void map_forward_id(RendererRD::ForwardIDType p_type, RendererRD::ForwardID p_id, uint32_t p_index) {}
|
virtual void map_forward_id(RendererRD::ForwardIDType p_type, RendererRD::ForwardID p_id, uint32_t p_index, uint64_t p_last_pass) {}
|
||||||
virtual bool uses_forward_ids() const { return false; }
|
virtual bool uses_forward_ids() const { return false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -793,7 +793,7 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged
|
||||||
real_t distance = (i < omni_light_count) ? omni_light_sort[index].depth : spot_light_sort[index].depth;
|
real_t distance = (i < omni_light_count) ? omni_light_sort[index].depth : spot_light_sort[index].depth;
|
||||||
|
|
||||||
if (using_forward_ids) {
|
if (using_forward_ids) {
|
||||||
forward_id_storage->map_forward_id(type == RS::LIGHT_OMNI ? RendererRD::FORWARD_ID_TYPE_OMNI_LIGHT : RendererRD::FORWARD_ID_TYPE_SPOT_LIGHT, light_instance->forward_id, index);
|
forward_id_storage->map_forward_id(type == RS::LIGHT_OMNI ? RendererRD::FORWARD_ID_TYPE_OMNI_LIGHT : RendererRD::FORWARD_ID_TYPE_SPOT_LIGHT, light_instance->forward_id, index, light_instance->last_pass);
|
||||||
}
|
}
|
||||||
|
|
||||||
Transform3D light_transform = light_instance->transform;
|
Transform3D light_transform = light_instance->transform;
|
||||||
|
@ -1670,7 +1670,7 @@ void LightStorage::update_reflection_probe_buffer(RenderDataRD *p_render_data, c
|
||||||
ReflectionProbeInstance *rpi = reflection_sort[i].probe_instance;
|
ReflectionProbeInstance *rpi = reflection_sort[i].probe_instance;
|
||||||
|
|
||||||
if (using_forward_ids) {
|
if (using_forward_ids) {
|
||||||
forward_id_storage->map_forward_id(FORWARD_ID_TYPE_REFLECTION_PROBE, rpi->forward_id, i);
|
forward_id_storage->map_forward_id(FORWARD_ID_TYPE_REFLECTION_PROBE, rpi->forward_id, i, rpi->last_pass);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReflectionProbe *probe = reflection_probe_owner.get_or_null(rpi->probe);
|
ReflectionProbe *probe = reflection_probe_owner.get_or_null(rpi->probe);
|
||||||
|
|
|
@ -2862,7 +2862,7 @@ void TextureStorage::update_decal_buffer(const PagedArray<RID> &p_decals, const
|
||||||
Decal *decal = decal_sort[i].decal;
|
Decal *decal = decal_sort[i].decal;
|
||||||
|
|
||||||
if (using_forward_ids) {
|
if (using_forward_ids) {
|
||||||
forward_id_storage->map_forward_id(FORWARD_ID_TYPE_DECAL, decal_instance->forward_id, i);
|
forward_id_storage->map_forward_id(FORWARD_ID_TYPE_DECAL, decal_instance->forward_id, i, RSG::rasterizer->get_frame_number());
|
||||||
}
|
}
|
||||||
|
|
||||||
decal_instance->cull_mask = decal->cull_mask;
|
decal_instance->cull_mask = decal->cull_mask;
|
||||||
|
|
Loading…
Reference in New Issue