Skip to content

RenderingDevice: Add null checks when retrieving uniform sets#114073

Merged
Repiteo merged 1 commit into
godotengine:masterfrom
akien-mga:rd-uniform-set-check
Dec 17, 2025
Merged

RenderingDevice: Add null checks when retrieving uniform sets#114073
Repiteo merged 1 commit into
godotengine:masterfrom
akien-mga:rd-uniform-set-check

Conversation

@akien-mga

@akien-mga akien-mga commented Dec 16, 2025

Copy link
Copy Markdown
Member

This may mitigate a crash seen in the wild in Rift Riff on Android, most likely trading it for a single-frame rendering bug (which is better than crashing on user devices).

It doesn't solve the underlying issue which seems to be a race condition where a uniform set RID gets has been freed while still being reported as owned by the RID_Owner.

Stack trace from Rift Riff:

backtrace:
  #00  pc 0x0000000003369f3c  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (RenderingDevice::_uniform_set_update_shared(RenderingDevice::UniformSet*)+76) (BuildId: 51ca91226c11fafc)
  #01  pc 0x0000000003375458  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (RenderingDevice::draw_list_draw(long, bool, unsigned int, unsigned int)+4839) (BuildId: 51ca91226c11fafc)
  #02  pc 0x000000000354e814  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (RendererCanvasRenderRD::_render_batch(long, RendererCanvasRenderRD::CanvasShaderData*, long, RendererCanvasRender::Light*, RendererCanvasRenderRD::Batch const*, RenderingMethod::RenderInfo*)+3050) (BuildId: 51ca91226c11fafc)
  #03  pc 0x0000000003540b7c  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (RendererCanvasRenderRD::_render_batch_items(RendererCanvasRenderRD::RenderTarget, int, Transform2D const&, RendererCanvasRender::Light*, bool&, bool, RenderingMethod::RenderInfo*)+2280) (BuildId: 51ca91226c11fafc)
  #04  pc 0x000000000353ffa4  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (RendererCanvasRenderRD::canvas_render_items(RID, RendererCanvasRender::Item*, Color const&, RendererCanvasRender::Light*, RendererCanvasRender::Light*, Transform2D const&, RenderingServer::CanvasItemTextureFilter, RenderingServer::CanvasItemTextureRepeat, bool, bool&, RenderingMethod::RenderInfo*)+906) (BuildId: 51ca91226c11fafc)
  #05  pc 0x0000000003460828  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (RendererCanvasCull::_render_canvas_item_tree(RID, RendererCanvasCull::Canvas::ChildItem*, int, Transform2D const&, Rect2 const&, Color const&, RendererCanvasRender::Light*, RendererCanvasRender::Light*, RenderingServer::CanvasItemTextureFilter, RenderingServer::CanvasItemTextureRepeat, bool, unsigned int, RenderingMethod::RenderInfo*)+104) (BuildId: 51ca91226c11fafc)
  #06  pc 0x00000000034623b0  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (RendererCanvasCull::render_canvas(RID, RendererCanvasCull::Canvas*, Transform2D const&, RendererCanvasRender::Light*, RendererCanvasRender::Light*, Rect2 const&, RenderingServer::CanvasItemTextureFilter, RenderingServer::CanvasItemTextureRepeat, bool, bool, unsigned int, RenderingMethod::RenderInfo*)+498) (BuildId: 51ca91226c11fafc)
  #07  pc 0x000000000347d100  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (RendererViewport::_draw_viewport(RendererViewport::Viewport*)+685) (BuildId: 51ca91226c11fafc)
  #08  pc 0x000000000347ddcc  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (RendererViewport::draw_viewports(bool)+885) (BuildId: 51ca91226c11fafc)
  #09  pc 0x0000000003415100  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (RenderingServerDefault::_draw(bool, double)+94) (BuildId: 51ca91226c11fafc)
  #10  pc 0x000000000116abac  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (Main::iteration()+4829) (BuildId: 51ca91226c11fafc)
  #11  pc 0x0000000001105ae4  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (OS_Android::main_loop_iterate(bool*)+364) (BuildId: 51ca91226c11fafc)
  #12  pc 0x000000000111bc6c  /data/app/~~YAGMvfnvDWYPjrX_I08qyA==/com.adriaandejongh.riftriff-aS9-Onpv7Ow5TubZ2-HSxg==/split_config.arm64_v8a.apk!libgodot_android.so (Java_org_godotengine_godot_GodotLib_step+312) (BuildId: 51ca91226c11fafc)
  #13  pc 0x0000000000d9d1a8  /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (art_jni_trampoline+104)
  #14  pc 0x0000000002002908  /memfd:jit-cache (org.godotengine.godot.vulkan.VkRenderer.onVkDrawFrame+40)
  #15  pc 0x0000000002003f3c  /memfd:jit-cache (org.godotengine.godot.vulkan.VkThread.run+860)
  #16  pc 0x0000000000317194  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612)
  #17  pc 0x0000000000302838  /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+216)
  #18  pc 0x00000000004c8298  /apex/com.android.art/lib64/libart.so (art::Thread::CreateCallback(void*)+932)
  #19  pc 0x00000000004c7ee4  /apex/com.android.art/lib64/libart.so (art::Thread::CreateCallbackWithUffdGc(void*)+8)
  #20  pc 0x000000000006b1f0  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+196)
  #21  pc 0x000000000005e1b4  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)

This was partially mentioned in #112157 but it seems conflated with other rendering related crashes which may not have the same cause there, so it's not fixing that issue per se.

Some more context on devices where this issue was reproduced in Rift Riff, mainly Mediatek, Google Tensor, Spreadtrum and Samsung s5e8835 SoCs.

Details image image image

@akien-mga akien-mga added this to the 4.6 milestone Dec 16, 2025
@akien-mga akien-mga requested a review from clayjohn December 16, 2025 09:09
@akien-mga akien-mga requested a review from a team as a code owner December 16, 2025 09:09
@akien-mga akien-mga added bug topic:rendering crash cherrypick:4.5 Considered for cherry-picking into a future 4.5.x release labels Dec 16, 2025
This may mitigate a crash seen in the wild in Rift Riff on Android, most
likely trading it for a single-frame rendering bug (which is better than
crashing on user devices).

It doesn't solve the underlying issue which seems to be a race condition
where a uniform set RID gets has been freed while still being reported as
owned by the RID_Owner.
@akien-mga akien-mga force-pushed the rd-uniform-set-check branch from 78f2c4e to adb7774 Compare December 16, 2025 09:15
Comment on lines +5002 to +5003
const String us_info = us ? vformat("(%d):\n%s\n", i, _shader_uniform_debug(us->shader_id, us->shader_set)) : vformat("(%d, which was just freed) ", i);
ERR_FAIL_MSG(vformat("Uniforms supplied for set %sare not the same format as required by the pipeline shader. Pipeline shader requires the following bindings:\n%s", us_info, _shader_uniform_debug(draw_list.state.pipeline_shader)));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bit convoluted with the nested vformats, but it's basically switching between these two messages depending on whether us is null:

Uniforms supplied for set (<i>, which was just freed) are not the same format as required by the pipeline shader. Pipeline shader requires the following bindings:
<pipeline shader debug info>

(same as the else branch)

and:

Uniforms supplied for set (<i>):
<uniform set debug info>
are not the same format as required by the pipeline shader. Pipeline shader requires the following bindings:
<pipeline shader debug info>

@clayjohn clayjohn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. I tested this with a custom build that deletes one uniform set one time right after calling draw_list_bind_uniform_set. The result was an error message and the sprite failed to draw, but that was it, Godot continued to run after that and did not crash.

So at the very least this will make Godot more resilient and less likely to crash when a uniform set is freed after being bound, but before the draw call is issued.

Now we just need to figure out how the uniform set is getting freed in these cases. Since it looks like some sort of race condition despite all the relevant code being single threaded

@Repiteo Repiteo merged commit 077d6fb into godotengine:master Dec 17, 2025
20 checks passed
@Repiteo

Repiteo commented Dec 17, 2025

Copy link
Copy Markdown
Contributor

Thanks!

@akien-mga akien-mga deleted the rd-uniform-set-check branch December 17, 2025 12:19
akien-mga added a commit to akien-mga/godot that referenced this pull request Dec 17, 2025
@akien-mga

Copy link
Copy Markdown
Member Author

Cherry-picked for 4.5.2.

@akien-mga akien-mga removed the cherrypick:4.5 Considered for cherry-picking into a future 4.5.x release label Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants