fix: prevent viewport export clipping at high resolutions#1311
Merged
MrNeRF merged 1 commit intoJun 15, 2026
Conversation
The viewport export renders once and reads back immediately. The VkSplat renderer sizes its per-frame visible-primitive and tile-instance scratch from deferred, one-frame-late high-water marks, so the first render at a new viewpoint/resolution can exceed them and render capacity-clamped (the depth/tile-ordered tail is dropped). That produces the curved clip that worsens with resolution. The live viewport hides this because it self-heals on the next frame, but a one-shot export saves the clamped frame; re-exporting from the same view works because the marks have since grown. Drive that self-heal synchronously for one-shot captures: re-render the Preview slot until the renderer confirms the previous pass produced complete, unclamped content (bounded by a max pass count), then read back. A new VksplatViewportRenderer::previewCaptureSettled() signal reports this from the deferred poll, rejecting the macro warm-up frame and requiring the steady-state chain; a pass>=1 guard keeps the signal tied to the current view so the tiled path stays correct. Settling is scoped to the export capture overloads, leaving film-strip thumbnails, asset previews, and depth captures on single-render behavior. Closes MrNeRF#1304 Co-Authored-By: Oz <oz-agent@warp.dev>
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.
Summary
Fixes the viewport export clipping the splat model along a curved edge at higher resolutions (worse the higher you go), which only happens on the first export from a new viewpoint — re-exporting from the same view produces a correct image.
Root cause
The viewport export renders once and reads the image back immediately. The VkSplat renderer sizes its per-frame scratch (visible-primitive and tile-instance capacity) from deferred, one-frame-late high-water marks. The first render at a new viewpoint/resolution can exceed those marks and render capacity-clamped — the depth/tile-ordered tail of primitives/tile-instances is dropped — which is the curved clip that grows with resolution (tile-instance count scales with resolution). The interactive viewport hides this because it self-heals on the next frame, but a single-shot export saves the clamped frame. Re-exporting from the same view works because the deferred readback has since grown the marks.
Fix
Drive that self-heal synchronously for one-shot captures instead of capturing the first frame:
VksplatViewportRenderer::previewCaptureSettled(), set from the start-of-frame deferred poll. It reports that the previously rendered frame produced complete, unclamped content using the steady-state macro chain (rejecting the legacy macro warm-up frame and partial-stats frames).RenderingManager::renderPreviewImageToPreviewSlotWithState, re-render the Preview slot untilpreviewCaptureSettled()is true (bounded bykMaxPreviewSettlePasses = 8), then read back. Apass >= 1guard ties the signal to the current view, which keeps the tiled (very-high-res) path correct since each tile is a different sub-view.renderPreviewImageRgb8/Rgba8+ tiled path). Film-strip thumbnails (FloatRgb), asset previews (model overloads), and depth captures keep their single-render behavior, so there is no perf regression there.Typical convergence is 2–4 passes; the cap only guards a pathological non-converging case.
Files changed
src/visualizer/rendering/vksplat_viewport_renderer.hpp/.cppsrc/visualizer/rendering/rendering_manager.hppsrc/visualizer/rendering/rendering_manager_viewport.cppTesting
vksplat.render.visible_clamped/vksplat.render.macro_instances_clampedno longer fire for the frame that is saved.Closes #1304