Skip to content

Do not draw what will not be shown in non-swapped frames - #6901

Open
Fedr wants to merge 3 commits into
masterfrom
skip-draw-nonswapped-frames
Open

Fedr wants to merge 3 commits into
masterfrom
skip-draw-nonswapped-frames

Conversation

@Fedr

@Fedr Fedr commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Only the frames that reach glfwSwapBuffers are ever seen:

    if ( window && swapped )
    {
        Timer t( "glfwSwapBuffers" );
        glfwSwapBuffers( window );
    }

so everything such a frame puts in the main framebuffer is thrown away. The scene texture is the opposite: it outlives the frame, a later visible frame draws it without rendering the scene again, and it can be read. So nothing on its path is conditional, and it is now rendered whenever the scene is dirty rather than only in a frame that is swapped — the texture is current in every frame.

What a non-swapped frame skips:

  • clearFramebuffers() of the main framebuffer;
  • sceneTexture_->draw();
  • with no scene texture, where the scene goes right in the main framebuffer: preDrawSignal(), drawScene() and postDrawSignal() as well. The two signals are skipped as a pair because ShadowsGL binds its framebuffer in preDraw_ and consumes it in postDraw_.

preDrawSignal() may draw, so both clearFramebuffers() keep their place in front of it, and renderScene is still decided after it.

Rendering the scene earlier does not add work in the common case: drawScene() ends in resetRedraw_(), so a scene dirtied once during a run of non-swapped frames is rendered once either way, just in the first frame of the run instead of the last. It does render more than before when something dirties the scene on every frame of such a run.

One frame that could become visible after being skipped

ImGuiMenu::finishFrame() calls forceSwapOnFrame() when a secondary ImGui viewport is about to disappear — after drawFull() has already decided the frame is invisible. Such a frame would be presented with an uncleared main framebuffer, so it is now held back (swapped = false plus one more forced frame) instead.

captureUIScreenShot

It read the back buffer of whatever frame had been drawn last, which is no longer a complete image. It now draws one frame, forced to swap, and reads it in a callback fired just before glfwSwapBuffers — a defined back buffer, rather than relying on the contents surviving a swap as it did whenever the previous frame had been swapped. Its pixel buffer was also allocated as size.x * size.x rather than size.x * size.y, a heap overflow for any capture area taller than it is wide.

Not done

ImGui_ImplOpenGL3_RenderDrawData() in ImGuiMenu::finishFrame() is the remaining invisible GPU work in a non-swapped frame, but since ImGui 1.92 it is also what applies pending ImTextureData updates, so it cannot simply be skipped.

🤖 Generated with Claude Code

Fedr and others added 3 commits September 22, 2026 12:27
Frames drawn while forceRedrawFramesWithoutSwap_ > 1 never reach glfwSwapBuffers,
so everything put into the main framebuffer in them is thrown away. Skip
sceneTexture_->draw() and the main framebuffer clear in such frames, and fill the
background inside the scene texture only when the scene is actually rendered.

captureUIScreenShot() used to read the back buffer of whatever frame had been
drawn last, which is no longer complete; it now draws one swapping frame and
reads it right before the swap. Its pixel buffer was also allocated as
size.x * size.x instead of size.x * size.y.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
preDrawSignal() may draw, so clearFramebuffers() must stay in front of it;
instead skip preDrawSignal() itself in a frame that is not shown, together
with drawScene(), postDrawSignal() and the scene texture. ShadowsGL binds its
framebuffer in preDraw_ and consumes it in postDraw_, so the two signals have
to be skipped as a pair.

ImGuiMenu::finishFrame() can still force the swap of a frame drawFull() has
already skipped; such a frame is now held back and one more is drawn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The scene texture outlives the frame it is rendered in - a later visible frame
draws it without rendering the scene again, and it can be read - so nothing on
its path is conditional any more, and it is rendered whenever the scene is
dirty rather than only in a frame that is swapped.

Only what goes into the main framebuffer is skipped in a frame that is not
shown: its clearFramebuffers() and sceneTexture_->draw(). Without a scene
texture the scene is drawn right in the main framebuffer, so there both draw
signals and drawScene() are skipped too.

Co-Authored-By: Claude Opus 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