Repository navigation
Metal: RENDER_TEXTURE_MEM_USED underflows when textures are freed #124407
Copy link
Copy link
Open
Description
Tested versions
Tested versions: reproducible in 4.7.2.stable [ed1daf0] and in master [c9730f4] built locally. Not reproducible with --rendering-driver vulkan (MoltenVK) on the same machine.
System information
macOS 26 (Darwin 25.3), Apple M4, Metal 4.0, Forward+.
Issue description
The following is my (mostly Claude's) understanding of the issue.
Our stream's OpenTelemetry gauge for Performance.RENDER_TEXTURE_MEM_USED read about 9.2e18. That's int() clamping the float the monitor returns, which is about 1.8e19, i.e. the uint64 counter wrapped below zero. A bare project with no game code reproduces it.
- Root cause:
- RenderingDevice adds texture_get_allocation_size() to its counter only for textures it allocates (in texture_create and shared fallbacks). On free, it subtracts that size for every texture (_free_pending_resources).
- Every 2D texture also gets an sRGB view (rd_texture_srgb, created by texture_create_shared in texture_storage.cpp).
- Vulkan returns 0 for a view because it empties the view's allocation.
- Metal returns the view's MTLTexture.allocatedSize, which is the parent's size, so each texture is subtracted twice.
- Textures imported with texture_create_from_extension have the same asymmetry: never added, but subtracted on Metal.
- Fix: in drivers/metal/rendering_device_driver_metal.cpp, texture_get_allocation_size returns 0 for views (parentTexture() != nullptr) and for imported textures, as Vulkan does. It's 2 lines.
- Side effect: with the fix, the counter's starting value is higher (about 4.8 MB vs 2.67 MB), because views freed during startup no longer get subtracted. The new number is the accurate one.
- Not tested: the imported-texture case. It needs a GDExtension or XR texture
Steps to reproduce
- Open the attached project on macOS with Godot 4.7.2 or master, using the Metal driver (the project sets rendering_device/driver.macos="metal", Forward+).
- Run it. It waits 10 frames, creates 20 RGBA8 256×256 ImageTextures (5,242,880 bytes in all), frees them 10 frames later, prints Performance.RENDER_TEXTURE_MEM_USED at each step, then quits. A helper in the script prints a wrapped counter as a negative number, so the drop is easy to read.
- Look at the output. Creating adds about 5,242,880 bytes, but freeing takes off about 10,485,000, roughly twice that. The counter ends up below where it started and wraps around (it reads as about 1.8e19 without the helper).
- For comparison, run the same project with --rendering-driver vulkan. Freeing then takes off exactly what creating added.
Minimal reproduction project (MRP)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Type
Projects
- StatusShow more project fieldsFor team assessment
Activity
AThousandShips commentedon Oct 12, 2026
Please see this, if you are using AI tools to assist in investigating issues please make sure to verify any AI output before pasting it
dcvz commentedon Oct 15, 2026
Also reproduces on master d824ca9, M1 on macOS 26.2.
The imported texture case can be checked without a GDExtension by importing the native handle of a texture you just made. For a 256x256 RGBA8 texture, creating and freeing a shared view or an imported copy each takes 264192 off the counter on Metal, and nothing on Vulkan:
To reproduce the imported case: