Avoid GL readbacks for perf #3558
Open
PaulHax wants to merge 2 commits into
Open
Conversation
drawArrays read DEPTH_WRITEMASK back with gl.getParameter on every draw call, but the value is only used to save/restore the depth mask around the pointPicking path. Each such readback is a synchronous CPU/GPU sync point, so ordinary rendering paid one pipeline stall per drawn polydata for a value it never used. Scope the readback (and the restore) to pointPicking.
…ave/restore saveCurrentBindings read the binding back with gl.getParameter(FRAMEBUFFER_BINDING), a synchronous CPU/GPU sync point, on every save, e.g. once per ForwardPass zbuffer capture. OpenGLRenderWindow now carries a JS-side mirror of the raw binding (setFramebufferBinding/getFramebufferBinding). It is inert (undefined) by default, so stock behavior is unchanged. Once a caller seeds it (e.g. a host embedding vtk.js in its own context, which knows what it bound), saves use the mirror instead of querying, and vtkFramebuffer bind/restore keep it in sync. Covered by a test asserting both modes: unseeded saves still query GL, seeded saves issue zero FRAMEBUFFER_BINDING queries and the mirror follows nested bind/save/restore.
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.
Two hot-path
gl.getParameterreadbacks of dynamic GL state removed.Context
Reading dynamic state back is a synchronization point with the GPU process. On an uncontended context browsers answer from CPU-side state and the query is nearly free; standalone vtk.js scenes show no measurable change from this PR. But when vtk.js shares a context with a busy host (e.g. rendering inside a MapLibre custom layer), Chromium answers each query with a GPU-process round-trip (~0.2 ms), and the first query of the frame additionally blocks until the host's queued GPU work drains (20-45 ms measured mid-frame over a terrain-heavy map, spikes past 100 ms). A per-draw query keeps re-paying smaller stalls as each draw refills the queue.
Demo, no vtk.js involved (
gl-readback-stall-demobranch, one self-contained HTML file; serve it and open): a MapLibre terrain page whose custom layer just issues N ×getParameter(DEPTH_WRITEMASK)per frame, the pattern vtk.js's draw path had. On Chromium (ANGLE D3D12): N=0 → 30 fps; N=200, i.e. a 200-actor scene before this PR → 12 fps, with the first query of each frame costing 43 ms p50 / 137 ms max.Changes
drawArraysreadDEPTH_WRITEMASKunconditionally on every draw call, but only the point-picking pass uses the value. The readback (and restore) is now scoped topointPicking; ordinary rendering issues no query.saveCurrentBindingsqueriedFRAMEBUFFER_BINDINGon every save (e.g. each ForwardPass zbuffer capture).vtkOpenGLRenderWindownow carries a JS-side mirror (set/getFramebufferBinding, gated by an explicitgetFramebufferBindingKnown()flag). The mirror is inert until first seeded, so stock behavior is byte-identical. Once a caller seeds it (e.g. an embedding host that knows what it bound), saves use the mirror andvtkFramebufferbind/restore keep it in sync. A unit test covers both modes, including nested save/bind/restore through the mirror.Results
A/B benchmark (200-actor scene in a MapLibre terrain custom layer, continuous camera motion, Chromium/ANGLE D3D12; harness pairs this branch with the external-context render window follow-up): mean vtk render time 79 ms → 14 ms per frame, whole-frame 91 ms → 35 ms (11 → 29 fps). The measured win is dominated by the per-draw Helper readback; the Framebuffer mirror removes the queries that remain once an embedding host declares its framebuffer (any single remaining query inherits the whole stall), so the steady-state render path can issue zero readbacks.