Repository navigation
Conversation
A multisampled render target's samples are stored at the end of every pass (`StoreAndMultisampleResolve`), so a later pass into the same target can load them back. An app that clears the target each frame and only reads what it resolves into never loads them, but still writes them out every frame, and the multisampled texture still takes up memory: about 50 MB at an iPhone 17 Pro's full resolution with 4× antialiasing. `TextureParams::transient` says a render texture's contents only last as long as the pass drawing into it. Metal then stores nothing of it when a pass ends but what it resolves into (`MultisampleResolve`, or `DontCare` without a resolve texture), a pass that doesn't clear it starts with `DontCare`, and on a GPU with tile memory, every Apple GPU, the texture is `MTLStorageModeMemoryless` and takes up no memory at all. Consecutive passes into the same target are still merged into one encoder, so a target drawn by one draw call after another keeps its samples throughout. A transient depth texture isn't stored either. OpenGL keeps a transient texture as it does any other, which the contract allows. Everything else is unchanged for textures that aren't transient. Drawing a full-screen 4× target that's cleared each frame and resolved once took an iPhone 17 Pro's GPU from 5.08 to 4.85 ms a frame, and the app's footprint from 319 to 269 MB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
…pled_render_to_texture On OpenGL, a multisampled render pass draws into a renderbuffer, and `end_render_pass` blits it into the resolve texture. On a tile-based GPU, the blit makes the GPU write every sample out to memory and read them all back: about 40 MB each way per resolve of a full-screen 4× target on a 1080×2400 phone. OpenGL ES's `GL_EXT_multisampled_render_to_texture` lets a framebuffer draw into a single-sampled texture multisampled, keeping the samples in tile memory and resolving them as the GPU writes the tile out. `TextureParams::transient` already says a render texture's contents only last as long as the pass drawing into it, which is that extension's contract. On OpenGL ES 3 with the extension, which `Features::multisampled_render_to_texture` reports: - A pass with one transient multisampled color attachment and a resolve texture, and no depth or a transient one, attaches the resolve texture with `glFramebufferTexture2DMultisampleEXT` instead of the renderbuffer, and `end_render_pass` has nothing to blit. The resolve texture holds what's drawn once drawing leaves the pass. - A transient multisampled texture's storage is an implicit multisample buffer (`glRenderbufferStorageMultisampleEXT`), which can share such a framebuffer and live in tile memory alone. - A pass that doesn't clear starts by invalidating its transient attachments (`glInvalidateFramebuffer`), unless it carries on drawing into the target the last pass begun drew into, so the GPU doesn't load them. Everything else is unchanged, and so is any other context. WebGL never reports the feature, and stand-ins keep the module from importing the new functions from `gl.js`. On a Pixel 6 (Mali-G78), at 60 Hz, a macroquad game that draws each frame into a full-screen transient 4× target, leaves it mid-frame to blur it and copies it to the screen held 60 fps at a GPU clock of 302 MHz instead of 471, and its graphics memory went from 184 to 146 MB. That was measured with buffer updates no longer waiting on the driver: before that, those waits held the frame rate down, and they grew as the lighter frames let the GPU clock down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #680, which adds
TextureParams::transient: this branch's first commit is #680's, and its second is this change.Why
On OpenGL, a multisampled render pass draws into a renderbuffer, and
end_render_passblits it into the resolve texture. On a tile-based GPU, that blit makes the GPU write every sample out to memory and read them all back: about 40 MB each way for a full-screen 4× target on a 1080×2400 phone, every time it resolves.OpenGL ES's
GL_EXT_multisampled_render_to_texturelets a framebuffer draw into a single-sampled texture multisampled. The samples stay in tile memory and resolve as the GPU writes the tile out, and they need no memory of their own. Mali and Adreno GPUs support it, among others.Change
TextureParams::transientalready says a render texture's contents only last as long as the pass drawing into it, which is the extension's contract.Features::multisampled_render_to_texturereports OpenGL ES 3 with the extension, and with it:glFramebufferTexture2DMultisampleEXTin place of the renderbuffer.end_render_passhas nothing to blit for it: the resolve texture holds what's drawn once drawing leaves the pass, so a pass begun and ended for every batch, as macroquad does, resolves once.glRenderbufferStorageMultisampleEXT), which can share such a framebuffer and live in tile memory alone.glInvalidateFramebuffer), so the GPU doesn't load them, unless it carries on drawing into the target the last pass begun drew into. That mirrors Metal'sDontCareload action in metal: keep transient render textures' samples in tile memory #680, which consecutive passes into the same target don't get either, since they share an encoder.Every other texture and pass is unchanged, and so is any other context. WebGL never reports the feature, and stand-ins for the three new functions keep the module from importing them from
gl.js.Measurements
A macroquad game (CHOMP) draws each frame into a full-screen transient 4× target, leaves it mid-frame to blur it behind a modal, and copies it to the screen. On a Pixel 6 (Mali-G78) at 60 Hz, with #682 in both builds:
Without #682, this change alone made frames later: buffer updates waited on the driver, and the lighter frames let the GPU clock down, which made those waits longer.
Verification
cargo checkpasses foraarch64-linux-android,wasm32-unknown-unknown,aarch64-apple-darwin,aarch64-apple-iosandaarch64-apple-tvos.🤖 Generated with Claude Code