diff options
author | Mark Stead <Scumbag> | 2021-03-08 20:19:35 +0300 |
---|---|---|
committer | Clément Foucault <foucault.clem@gmail.com> | 2021-03-08 20:51:48 +0300 |
commit | a45af290f37dfcf7702c5141e642b253a94c7857 (patch) | |
tree | 21af5090b4ad9e1af377f3ae7852f9928b16281a /source/blender/draw/engines | |
parent | 84a4f2ae68d40830111ae41b25d76b165e37d611 (diff) |
Fix T86357: EEVEE: Shadows: Casters have exponential performance degradation with many objects
When you have many distinct objects, in an Eevee render then the shadow caster gets exponentially slower as the number of (distinct) objects increase.
This is because of the way that frontbuffer->bbox (EEVEE_BoundBox array) and the associated frontbuffer->update bitmap are resized.
Currently the resizing is done by reserving space for SH_CASTER_ALLOC_CHUNK (32) objects at a time.
When the number of objects is large, then the MEM_reallocN() gets progressively slower because it must memcpy the entire bbox/bitmap data to the new memory chunk.
And there will be a lot of *memcpy* operations for a large scene.
(Obviously there are a significant number of memory allocations/deallocations too - though this would be linear performance.)
I've switched to doubling the frontbuffer->alloc_count (buffer capacity) instead of adding SH_CASTER_ALLOC_CHUNK (32). As I understand this is the only way to eliminate exponential slowdown. Just increasing the size of SH_CASTER_ALLOC_CHUNK would still result in exponential slowdown eventually.
In other changes, the "+ 1" in this expression is not necessary.
if (id + 1 >= frontbuffer->alloc_count)
The buffer is 0-based. So when the buffer is initially allocated then id values from bbox[0] to bbox[31] are valid. Hence when frontbuffer->count == frontbuffer->alloc_count, is when the resizing should be triggered.
As it stands the "+ 1" results in resizing the buffer, when there is still capacity for one more object in the buffer.
I've changed the initial buffer allocation to use MEM_mallocN() instead of MEM_callocN(). The difference is that malloc() doesn't memset buffer (with zeros) when allocated. I've checked the code where new bbox records are created, and it does not rely on the buffer being initialised with zeros.
Anyway, isn't calloc() safer than using malloc()? Well no, it's actually the opposite in this case. Every time the buffer size is increased, it is done using realloc(), and this does not zero-out the uniniitialised portion of the buffer. So the code would break if it was modified to assume that the buffer contains zeros. Hence I believe initialising the buffer using calloc() could be misleading to a new developer.
Won't this result in increased memory usage? Yes, if you have millions of objects in your scene, then you are potentially using up-to twice the memory for the shadow caster. (However if you have millions of objects in your scene you're probably finding the Eevee render times a slow.)
Note that once the render gets going the frontbuffer bbox/bitmap will be shrunk to a multiple of SH_CASTER_ALLOC_CHUNK (32), therefore releasing the overallocation of memory.
As observed in Visual Studio - this appears to be prior to peak memory usage anyway.
Note this shrinking is executed in EEVEE_shadows_update() - during the first render sample pass. If necessary you could consider shrinking the buffer immediately after the EEVEE_shadows_caster_register() has done it's work. (Note however it appears you would need to add that function call is multiple places.)
Anyway as per the bug report I raised, I observed a 5% increase in peak-memory. And I'm unclear whether this difference in memory is due to me running the debug build. (It could be that there is no difference because of the shrinking.)
I couldn't figure out how the shadow caster backbuffer works. I see that EEVEE_shadows_init() has an explicit command to swap the front/back buffers. However this is done only when the buffers are first initialised and there is nothing in there yet. In my testing, the backbuffer->count was always zero, EEVEE_shadows_update() never did anything with the backbuffer.
Finally this problem is most evident when using Geometry Nodes or a Particle System to instantiate many objects. Objects created through say the array modifier do not cause any issues because it is considered one object by the shadow caster.
Reviewed By: #eevee_viewport, fclem
Differential Revision: https://developer.blender.org/D10631
Diffstat (limited to 'source/blender/draw/engines')
-rw-r--r-- | source/blender/draw/engines/eevee/eevee_shadows.c | 8 |
1 files changed, 5 insertions, 3 deletions
diff --git a/source/blender/draw/engines/eevee/eevee_shadows.c b/source/blender/draw/engines/eevee/eevee_shadows.c index f6fe9a76c70..1f27d2dce34 100644 --- a/source/blender/draw/engines/eevee/eevee_shadows.c +++ b/source/blender/draw/engines/eevee/eevee_shadows.c @@ -53,7 +53,7 @@ void EEVEE_shadows_init(EEVEE_ViewLayerData *sldata) sldata->shadow_ubo = GPU_uniformbuf_create_ex(shadow_ubo_size, NULL, "evShadow"); for (int i = 0; i < 2; i++) { - sldata->shcasters_buffers[i].bbox = MEM_callocN( + sldata->shcasters_buffers[i].bbox = MEM_mallocN( sizeof(EEVEE_BoundBox) * SH_CASTER_ALLOC_CHUNK, __func__); sldata->shcasters_buffers[i].update = BLI_BITMAP_NEW(SH_CASTER_ALLOC_CHUNK, __func__); sldata->shcasters_buffers[i].alloc_count = SH_CASTER_ALLOC_CHUNK; @@ -133,8 +133,9 @@ void EEVEE_shadows_caster_register(EEVEE_ViewLayerData *sldata, Object *ob) int id = frontbuffer->count; /* Make sure shadow_casters is big enough. */ - if (id + 1 >= frontbuffer->alloc_count) { - frontbuffer->alloc_count += SH_CASTER_ALLOC_CHUNK; + if (id >= frontbuffer->alloc_count) { + /* Double capacity to prevent exponential slowdown. */ + frontbuffer->alloc_count *= 2; frontbuffer->bbox = MEM_reallocN(frontbuffer->bbox, sizeof(EEVEE_BoundBox) * frontbuffer->alloc_count); BLI_BITMAP_RESIZE(frontbuffer->update, frontbuffer->alloc_count); @@ -173,6 +174,7 @@ void EEVEE_shadows_caster_register(EEVEE_ViewLayerData *sldata, Object *ob) } EEVEE_BoundBox *aabb = &frontbuffer->bbox[id]; + /* Note that *aabb has not been initialised yet. */ add_v3_v3v3(aabb->center, min, max); mul_v3_fl(aabb->center, 0.5f); sub_v3_v3v3(aabb->halfdim, aabb->center, max); |