Repository navigation
Annotation fills drawn onto the terrain: offscreen surfaces + one-draw fills (#841) - #842
Open
haraldsteinlechner wants to merge 7 commits into
Open
haraldsteinlechner wants to merge 7 commits into
haraldsteinlechner wants to merge 7 commits into
Conversation
) Fills were a flat lid between the outline points, which terrain relief inside the outline cut through. Each fill is now a closed prism around its triangulation, extruded along the outline's fitted-plane normal; a z-fail stencil pass marks the terrain inside it and a colour pass tints exactly those pixels. - FillVolume (Base): pure prism construction, walls from the boundary edges of the triangulation; FillReach: Auto (half the outline size, capped per body) or metres - viewer config: Fill Reach (m), auto by default (new keys, read with defaults) - one packed draw also for the legacy/Snapshots branch, SPICE trafo applied on the CPU; the pick pass and the 2D map keep the flat caps - stencil cleared before the depth-tested render command - unit tests (closedness, orientation, volume; FsCheck incl. self-intersecting rings) and a GPU test (bump inside the outline, reach, camera inside the prism)
…841) The shared stencil of the first version painted a pixel from whichever prism face reached it first, so a near prism's wall took the terrain of a fill behind it on screen (straight cuts in oblique views). Ordering a stencil pass per fill fixes that but costs 23 ms at 10k fills. DeferredFills draws all prisms in one draw into an RGBA32F target: faces behind the surface depth add their fill's premultiplied colour, +1 for back faces, -1 for front faces - the z-fail count per fill, in colour, order-independent. A fullscreen composite lays it over the terrain. 10240 fills: 0.16 ms, against 23 ms bit-batched stencil. OffscreenSurfaces renders the surfaces into MSAA colour+depth textures and copies both back per sample, so the depth exists as a texture while everything downstream keeps depth-testing as before. Not wired into the viewer yet; the stencil renderers stay until it is. GPU tests run the deferred path: bump, reach, camera inside the prism, far plane (no background painting), a near prism covering a far fill on screen (0 wrong pixels), overlapping fills blending; the benchmark compares 512 and 10240 fills with a moving camera.
) The main and instrument views render their surface priority groups into a multisampled colour+depth target of the control's size, and a per-sample blit copies colour and depth back, so everything downstream depth-tests as before. The depth texture feeds DeferredFills: all annotation fills in one draw. - surfaces stay clickable: their scene graphs are added again through OffscreenSurfaces.pickOnly (no render objects, scene events kept) - offscreen projection = RenderControlConfig.standard.adjustAspect; blit and composite map gl_FragCoord through ViewportSize - Snapshots keep rendering surfaces directly; their fills use the stencil renderer - known gap: server screenshots at another size get the control-sized surface textures, scaled Verified: GPU tests on the deferred path (7/7, 10240 fills 0.15 ms), the user's fillProblem scene in the viewer (no straight cut), and the Playwright annotation-fill spec drawing a filled polygon on Dimorphos by ctrl+click (99% inside tinted, 0% outside).
) The surface and fill-accumulation tasks were compiled eagerly in the view function. View code runs again per page load (page routing, one viewRenderView per ?page=render request), and nothing disposed those tasks - each load left another copy of the surfaces' GPU data behind (GPU memory full, 10 GB committed, slow and rare frames). OffscreenSurfaces.renderTargets compiles the task in an AdaptiveResource: on first acquire, disposed on last release. Used as textures in the render control's scene, the client's render task acquires them, and aardvark.media disposes that per client - so the offscreen passes now live exactly as long as a page shows them. Probes: probe-reload (open/close the render page, memory after each) returns to ~530-650 MB after every close over 6 loads; probe-leak (continuous camera drag) grows the same on develop and this branch, so that growth predates the offscreen path. GPU tests 7/7.
… view, cap orientation - remove the main path's ClearStencil (views draw fills against the surface depth); SnapshotSg keeps it for the stencil fallback - packedFillRender: one batch size again, documented as the Snapshots fallback and why it is slow at scale - instrument view: the offscreen size falls back to the control's CSS size (half the instrument resolution) until the page reports one; it rendered at 1x1 and stayed black (develop shows the surface) - FillVolume: no per-triangle re-orientation of the cap. libtess emits a consistent winding; a sliver triangle's sign is rounding noise and flipping it left the prism open (FsCheck counterexample, now a fixed test; properties run 2000 cases) - FillStencilRenderTest -> FillRenderTest; benchmark arms: terrain direct, offscreen+blit, deferred fills, stencil fallback - probe-instrument.ts; docs: known limitations in ai/RENDERING.md, screenshot note in docs/AnnotationFill.md
…e lifetime - blit copies colour and depth unconditionally: depth is cleared before every surface priority group, so earlier groups have depth 1 outside the last one; discarding depth-1 samples dropped those surfaces (regression test with two groups) - offscreen size clamped to >= 1 (views report their size only after the first resize; an instrument with zero resolution gave a 0 fallback) - framebuffer signatures created with the lazily compiled task and disposed with it, not in view code - deferred fills off while there is no fill (no full-screen RGBA32F pass) - fill reach tooltip built from FillReach.maxAuto - docs: what the blit copies, the sample count actually used, and known limitations (screenshot aspect/samples mismatch, shared reported size, fixed background, single-sampled fill accumulation, stencil fallback rebuilding batches); FillVolume header no longer an orphaned XML doc
This was referenced Oct 13, 2026
…make The offscreen surface targets were sized from the page's reported size, so a /rendering/screenshot of another size got stretched control-sized textures under correctly projected outlines, and the copy painted the viewer's #222222 over the screenshot's background. - The views pass DomNode.RenderControl a RenderClientValues -> ISg function; ViewerUtils.offscreenScene builds the offscreen targets and the fill accumulation from values.size/viewTrafo/projTrafo, which aardvark.media sets before every frame it renders, live or screenshot. Grouped surfaces, overlay, annotations and fill geometry are still built once and shared. - DrawingApp.view returns the fill geometry instead of drawing it; views draw it against their per-client surface depth, Snapshots with the stencil renderer. - Surfaces render offscreen over transparent black (premultiplied) and are copied with premultiplied blending: the target keeps its clear colour where no surface is. - The offscreen sample count stays the viewer's (OPC patches are prepared for it); the blit converts to the target's. - The instrument view's size fallback is gone: values.size is the real size. Tests: FillRenderTest +2 (clear colour/translucency kept; copy into 1 and 8 samples), annotation-fill.spec compares 3200x900 and 2400x1350 server shots with the live view (fails on the previous build: 60% of the fill differs) and checks background=. Pre-existing limits found on develop too are in ai/RENDERING.md.
This branch has not been deployed
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.
Fixes #841: filled annotations z-fought with the terrain, because terrain relief inside the outline cut through the flat fill sheet.
How it works now
FillVolume, PRo3D.Base, pure): each fill is a closed prism around its triangulation, extruded along the outline's fitted-plane normal (this also works on vertical rock faces). New viewer setting Fill Reach (m): auto by default (half the outline size, capped per body), or a fixed number of metres.OffscreenSurfaces): the main and instrument views render their surface priority groups into a multisampled colour+depth target. A per-sample blit copies both back, so everything downstream depth-tests as before, and the surface depth now also exists as a texture.AdaptiveResource, so they live as long as a page shows them. Compiling them eagerly in view code leaked the surfaces' GPU data on every page load; aardvark.media docs: docs(ai): offscreen passes and resource lifetime aardvark-platform/aardvark.media#87.PackedRendering.DeferredFills):Verified
FillRenderTest, 8/8):annotation-fill: a filled Sky polygon drawn by ctrl+click on Dimorphos. 98.9 % of pixels inside the outline are tinted, 0 % outside.--skip-hera): 732 passed, 0 failed, 4 errored. All four are inProfileAttributeExtraction(a NullReferenceException in the shared KdTree cache when those tests run in parallel). This branch does not touch that code, and the four pass when run sequentially (7/7), so the race predates this PR.Server screenshots (follow-up commit)
RenderClientValues(size, view, projection), which the render server sets before every frame, live or screenshot. So/rendering/screenshotat any size or aspect ratio renders the surfaces for exactly that image.background=is kept, and translucent surfaces blend over the real clear colour.annotation-fill.speccompares 3200x900 and 2400x1350 server screenshots with the live view: 0.0% of pixels differ. On the previous commit 60% of the fill differs.background=shows where the sky is (81.6%).FillRenderTest10/10, including the new clear-colour/translucency test and the copy into 1 and 8 samples.Known limitations / follow-ups
samples=other than the viewer's (4) come out without surfaces. The same happens on develop, so this PR does not cause it; PRo3D's own screenshot clients sendsamples=4.Pre-existing issues noticed along the way, unrelated to this PR:
Docs:
docs/AnnotationFill.md(new) andai/RENDERING.md.🤖 Generated with Claude Code