OverviewFilesCommitsIssuesDocsReleasesPeers

nvidia-rtx-godot commits / cef55bf

RT: stop double-freeing BLASes the RD dependency chain already reclaimed
The invalid-RID storm is acceleration structures, confirmed by the owner-kind the classifier now records: "DOUBLE FREE (freed before, was acceleration_structure)", hundreds per frame under dc-terrain streaming. blas_create registers the BLAS as a dependency of the vertex/index buffers it was built from (rendering_device.cpp:444 _add_dependency), and _free_dependencies (:201) frees every dependent when the parent goes: while (E->value.size()) { free_rid(*E->value.begin()); } So a streaming chunk dropping its mesh buffers takes its BLAS with it. The RT surface cache still holds the RID, and all five free sites gated on RID::is_valid(), which only tests for non-null and cannot see that the RD has already reclaimed the slot -- so the next cache refresh frees it again. Same class as the uniform-set trap in hyper-games #11 §4.2, on a different resource. cleanup_caches even documents the ownership rule ("BLAS resources are NOT freed here because they are owned by the RD dependency chain") while three sibling sites free them explicitly anyway. Adds RenderingDevice::acceleration_structure_is_valid (mirroring uniform_set_is_valid) and routes all five sites through one helper that asks the RD before freeing and clears the RID either way. Not yet verified at runtime -- needs a pt-bench run on a real window. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cef55bf3ecf66009ac5a5e9ae011ccff23e549cf
by plankatron <fleeky@gmail.com> · 2026-07-31T18:49:02.000Z

Changed files (3) +35 -17

servers/rendering/renderer_rd/forward_clustered/render_raytracing.cpp +23 -17
……@@ -254,6 +254,21 @@ uint64_t RenderRaytracing::mat_ubo_pool_get_address(uint32_t p_slot) const {
254254// Cache management
255255// ---------------------------------------------------------------------------
256256
257// A cached BLAS can be reclaimed out from under us. blas_create registers the BLAS
258// as a dependency of the vertex/index buffers it was built from
259// (rendering_device.cpp:444), and _free_dependencies frees every dependent when the
260// buffer goes, so a streaming mesh dropping its buffers takes the BLAS with it. The
261// cache entry still holds the RID, and RID::is_valid() only tests for non-null -- it
262// cannot see that the RD already reclaimed the slot -- so the next refresh frees it a
263// second time. That is the "Attempted to free invalid ID ... was acceleration_structure"
264// storm: hundreds per frame under dc-terrain streaming (hyper-games #11 §7.3).
265static void rt_free_blas_if_owned(RID &r_blas) {
266 if (r_blas.is_valid() && RD::get_singleton()->acceleration_structure_is_valid(r_blas)) {
267 RD::get_singleton()->free_rid(r_blas);
268 }
269 r_blas = RID();
270}
271
257272void RenderRaytracing::cleanup_caches() {
258273 // Free all cached surface data. BLAS resources are NOT freed here because
259274 // they are owned by the RD dependency chain.
……@@ -274,9 +289,7 @@ void RenderRaytracing::cleanup_caches() {
274289 for (KeyValue<uint64_t, RTDeformedCacheEntry> &kv : deformed_surface_cache) {
275290 RTDeformedCacheEntry &e = kv.value;
276291 if (e.ptr) {
277 if (e.ptr->blas.is_valid()) {
278 RD::get_singleton()->free_rid(e.ptr->blas);
279 }
292 rt_free_blas_if_owned(e.ptr->blas);
280293 memdelete(e.ptr);
281294 e.ptr = nullptr;
282295 }
……@@ -396,9 +409,7 @@ void RenderRaytracing::prepare_frame() {
396409 RTDeformedCacheEntry &e = kv.value;
397410 if (e.last_used_frame != 0 && current_frame - e.last_used_frame > DEFORMED_CACHE_TTL_FRAMES) {
398411 if (e.ptr) {
399 if (e.ptr->blas.is_valid()) {
400 RD::get_singleton()->free_rid(e.ptr->blas);
401 }
412 rt_free_blas_if_owned(e.ptr->blas);
402413 memdelete(e.ptr);
403414 e.ptr = nullptr;
404415 }
……@@ -476,10 +487,9 @@ RTSurfaceData *RenderRaytracing::process_surface(
476487 // Allocate or reuse entry
477488 if (!entry->ptr) {
478489 entry->ptr = memnew(RTSurfaceData);
479 } else if (entry->ptr->blas.is_valid()) {
480 // Free old BLAS before creating new one
481 RD::get_singleton()->free_rid(entry->ptr->blas);
482 entry->ptr->blas = RID();
490 } else {
491 // Free the old BLAS before creating a new one -- if the RD still has it.
492 rt_free_blas_if_owned(entry->ptr->blas);
483493 }
484494
485495 RTSurfaceData *surf_data = entry->ptr;
……@@ -567,9 +577,8 @@ RTSurfaceData *RenderRaytracing::process_deformed_surface(
567577
568578 if (!entry.ptr) {
569579 entry.ptr = memnew(RTSurfaceData);
570 } else if (entry.ptr->blas.is_valid()) {
571 RD::get_singleton()->free_rid(entry.ptr->blas);
572 entry.ptr->blas = RID();
580 } else {
581 rt_free_blas_if_owned(entry.ptr->blas);
573582 entry.blas_built_once = false;
574583 }
575584
……@@ -1082,10 +1091,7 @@ void RenderRaytracing::update_procedural_blas(RTProceduralState *p_state, LocalV
10821091
10831092 // Grow-only: only recreate the buffer when capacity is exceeded or count changed.
10841093 if (required_bytes > p_state->gpu_buffer_capacity || aabb_count != p_state->aabb_count) {
1085 if (p_state->blas.is_valid()) {
1086 RD::get_singleton()->free_rid(p_state->blas);
1087 p_state->blas = RID();
1088 }
1094 rt_free_blas_if_owned(p_state->blas);
10891095 if (p_state->gpu_buffer.is_valid()) {
10901096 RD::get_singleton()->free_rid(p_state->gpu_buffer);
10911097 }
servers/rendering/rendering_device.cpp +6 -0
……@@ -503,6 +503,12 @@ RID RenderingDevice::tlas_create(uint32_t p_max_instance_count, BitField<Acceler
503503 return id;
504504}
505505
506bool RenderingDevice::acceleration_structure_is_valid(RID p_acceleration_structure) {
507 _THREAD_SAFE_METHOD_
508
509 return acceleration_structure_owner.owns(p_acceleration_structure);
510}
511
506512Error RenderingDevice::blas_build(RID p_blas) {
507513 ERR_RENDER_THREAD_GUARD_V(ERR_UNAVAILABLE);
508514
servers/rendering/rendering_device.h +6 -0
……@@ -1404,6 +1404,12 @@ public:
14041404
14051405 RID blas_create(Span<AccelerationStructureGeometry> p_geometries, BitField<AccelerationStructureFlagBits> p_flags);
14061406 RID tlas_create(uint32_t p_max_instance_count, BitField<AccelerationStructureFlagBits> p_flags);
1407 // A BLAS is registered as a dependency of the vertex/index buffers it was built
1408 // from (blas_create -> _add_dependency), so freeing those buffers frees the BLAS
1409 // too. Anything caching a BLAS RID across frames must ask this before freeing it;
1410 // RID::is_valid() only tests for non-null and cannot see that the RD already
1411 // reclaimed it.
1412 bool acceleration_structure_is_valid(RID p_acceleration_structure);
14071413
14081414 typedef int64_t HitShaderBindingTableRange;
14091415