8000 gl: leave a pass's framebuffer bound when it ends by benface · Pull Request #672 · not-fl3/miniquad · GitHub
[go: up one dir, main page]

Skip to content

gl: leave a pass's framebuffer bound when it ends - #672

Open
benface wants to merge 1 commit into
not-fl3:masterfrom
benface:keep-framebuffer-bound
Open

benface wants to merge 1 commit into
not-fl3:masterfrom
benface:keep-framebuffer-bound

Conversation

@benface
@benface benface commented Oct 6, 2026
Copy link
Copy Markdown
Contributor

Why

end_render_pass binds the default framebuffer after every pass. On tile-based GPUs, binding another framebuffer ends the current one's render pass: the driver submits the work so far, writes the tiles out to memory, and reads them back in the next time that framebuffer is drawn to. macroquad begins and ends a pass for every batch, so a frame drawn into a render target becomes one GPU render pass per batch. This is the OpenGL side of what #641 did for Metal.

On a Pixel 6 (Mali-G78, OpenGL ES), a macroquad game draws its frame into a 4× multisampled render target, then copies it to the screen. It ran at 26–37 fps, idle or moving, with every frame taking two refreshes. A CPU profile put its render thread in the driver's job submissions, called from glBindFramebuffer in end_render_pass. Drawing straight to the screen, which never switches framebuffers, ran at the display's 90 fps.

Change

  • end_render_pass leaves the pass's framebuffer bound. begin_pass binds the framebuffer it draws to, so every pass still draws where it did.
  • commit_frame binds the default framebuffer, so a frame still ends with it bound.

The Metal backend is unchanged.

Compatibility

Between passes, the bound framebuffer is now the last pass's rather than the default one. Drawing through miniquad doesn't notice. Raw GL calls made between passes that expect the screen do: reading the screen back with glCopyTexImage2D or glReadPixels after a render-target pass reads that render target instead. To read the screen, call begin_default_pass(PassAction::Nothing) and end_render_pass() first.

In macroquad, that's Texture2D::grab_screen, which get_screen_data and _ScreenTexture materials use. not-fl3/macroquad#1071 binds the screen there first. It changes nothing with miniquad as released, so it can merge before this. Texture::read_pixels saves and restores the binding, so it's unaffected.

Verification

On the Pixel 6, with this change:

Before After
Frame rate, two levels idle and moving 26–37 fps 90 fps
Frames taking two refreshes 100 % 0–1 %
CPU time per frame 20–43 ms 7–9 ms

On macOS (OpenGL), the program from not-fl3/macroquad#1071 draws to the screen, then into a render target, then to the screen again, and reads both back. Both hold what was drawn to them, with and without this change.

The library and examples build, and cargo check passes for aarch64-linux-android and aarch64-apple-ios.

🤖 Generated with Claude Code

`end_render_pass` bound the default framebuffer after every pass.
Tile-based GPUs end a framebuffer's render pass whenever another one
is bound: the driver submits the work so far, writes the tiles out to
memory, and reads them back in when the framebuffer is next drawn to.
macroquad begins and ends a pass for every batch it draws, so a frame
drawn into a render target became one GPU render pass per batch.

On a Pixel 6 (Mali-G78, OpenGL ES), a game that draws its frame into a
4× multisampled render target and then copies it to the screen ran at
23 to 40 fps, its render thread mostly submitting jobs from
`glBindFramebuffer`. Without the rebind, it holds the display's 90 fps
at 7 to 9 ms of CPU a frame.

`begin_pass` binds the framebuffer it draws to, so passes draw where
they did. Between passes, the bound framebuffer is now the last pass's
rather than the default one, so code that reads the screen back with
raw GL calls, such as macroquad's `grab_screen`, should bind the
default framebuffer first. `commit_frame` binds it, so a frame still
ends with it bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0