Add tests for scenery surface caching and preview behavior
- Introduced `test_scenery_brick_preview.gd` to validate focused placement and CPU draw-cost for large brick walls. - Added `test_scenery_surface_cache.gd` to assess texture budget, visual equivalence, and cache invalidation. - Created `test_scenery_surface_preview.gd` to evaluate adaptive preview selection and placement behavior. - Implemented various helper functions to measure drawing performance and validate expected behaviors during tests. - Added unique identifiers for new test files to ensure proper tracking and management.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# Patterned scenery rendering
|
||||
|
||||
A patterned wall or background uses one small repeating image for each finish and tint. The camera samples that same image wherever the surface appears. A pan or zoom changes the visible rectangle and sampling scale; it does not create a new image for each camera position.
|
||||
|
||||

|
||||
|
||||
## Render pipeline
|
||||
|
||||
1. A visible, committed patterned surface requests its style from the scene's `StageSurfaceBakePool`.
|
||||
2. The cache checks the style key. A ready entry is reused; a missing entry is queued for the shared baker.
|
||||
3. At most one queued style is baked per process frame. The baker draws a four-world-unit overscan around the material period so edge details can be cropped without gaps.
|
||||
4. After the submitted drawing reaches `frame_post_draw`, the pool captures the image, crops the period, generates mipmaps, and publishes an `ImageTexture`.
|
||||
5. The temporary vector commands are cleared. The surface draws one camera-clipped quad using the shared image, repeat sampling, and mipmapped filtering.
|
||||
|
||||
The GPU still renders the textured rectangle every frame. When the view changes, the CPU also updates that rectangle and its texture coordinates. This is a small drawing command; it does not rebuild brick faces, grain, pores, or other vector details. The cache counters distinguish image builds and readbacks from ordinary redraws.
|
||||
|
||||
## Shared style images
|
||||
|
||||
The cache is owned by a scene-scoped pool. Its key includes finish, tint, wall/background kind, material version, repeat period, pixel density, and mipmap configuration. It excludes surface position, camera position, zoom, rectangle size, and erase fragment. Thus same-style surfaces and fragments share one image, while a tint change correctly requests a different image.
|
||||
|
||||

|
||||
|
||||
The current periods are finite, seamless repeat units baked at two bitmap pixels per world unit:
|
||||
|
||||
| Finish | Repeat period in world units | Bitmap size |
|
||||
| --- | ---: | ---: |
|
||||
| Brick | 800 × 304 | 1600 × 608 |
|
||||
| Wood | 960 × 384 | 1920 × 768 |
|
||||
| Grass | 704 × 576 | 1408 × 1152 |
|
||||
| Carpet | 544 × 416 | 1088 × 832 |
|
||||
| Concrete | 1120 × 760 | 2240 × 1520 |
|
||||
|
||||
For example, a 100-world-unit brick remains 100 world units wide. At 0.4× zoom it covers about 40 screen pixels; zoom changes how the GPU samples the image, not the brick geometry. The finite repeat unit makes detail bounded, so landmarks can become noticeable on very large surfaces.
|
||||
|
||||
## Zoom and mipmaps
|
||||
|
||||
The renderer captures the material once at fixed density, then builds smaller mip levels from that image. During zoom-out the GPU samples a smaller level to reduce shimmer; during zoom-in it samples a larger level. This replaces separately drawing the material at several CPU-selected zoom levels. At the Stage's maximum 6× zoom, the source image is magnified three times and can look softer. The current quality/performance tradeoff is fixed in the cache configuration.
|
||||
|
||||

|
||||
|
||||
## Erasing and pattern alignment
|
||||
|
||||
The eraser still cuts a rectangle into up to four rectangular fragments. Each fragment draws only its own bounds, but requests the same style image; it does not bake a fragment-specific copy. Cancel restores the original geometry, and Commit retains the new fragments. Both use the existing material sampling rules.
|
||||
|
||||
The sampling origin is significant: walls use `global_position`; background ground uses `global_position + motif_origin_offset`. Each surface computes its source UV from its local visible position plus that draw origin, wrapped by the material period. This preserves the current alignment convention across cuts. Avoid changing the origin rule as a generic transform correction: unusual transformed fragments need separate verification before making a broader alignment claim.
|
||||
|
||||

|
||||
|
||||
## Memory and rebuild boundaries
|
||||
|
||||
The scene cache has a 64 MiB resident texture budget. Accounting includes mip levels, textures waiting for safe retirement, and reserved space for a capture. Unused entries are evicted by least-recent use when needed. A style held by active surfaces can prevent admission of another style; the pool reports this as budget pressure rather than exceeding the cap. Under ordinary use, returning to a recently used style should reuse its warm image. Many tint variants can force eviction and a later rebuild.
|
||||
|
||||
A style's first capture still costs CPU/GPU work and can briefly affect a frame. Once ready, moving the camera across a large wall does not enqueue position tiles or read back new images. The shared baker also clears its vector command list after capture, preventing the native drawing resources that previously accumulated in many per-tile viewports.
|
||||
|
||||
These images are temporary runtime data. A saved `.scene` keeps surface settings and geometry; the cache recreates its images when needed in a new Stage session. Closing the scene releases its pool.
|
||||
|
||||
## What causes another bake?
|
||||
|
||||
| Action | New image work? |
|
||||
| --- | --- |
|
||||
| Pan left, then return right | No, while the style remains cached. |
|
||||
| Zoom in or out | No. The GPU samples existing mipmaps. |
|
||||
| Resize a wall | No. Change the rectangle and texture coordinates. |
|
||||
| Erase, Cancel, or Commit | No, if the style is ready. Change geometry and clipping. |
|
||||
| Use the same finish and tint on another wall | No, if the style is ready. Share its image. |
|
||||
| Choose a new finish or tint | Bake once if that style is missing. |
|
||||
| Return to an evicted style, or open a fresh session | Recreate the missing image. |
|
||||
|
||||
## Terms used here
|
||||
|
||||
- **Bake:** render the vector artwork once to make an image.
|
||||
- **Texture:** the image the GPU samples when drawing.
|
||||
- **Quad:** one rectangle, represented by two triangles.
|
||||
- **UVs:** coordinates that select which part of a texture to sample.
|
||||
- **Mipmaps:** smaller copies of an image, prepared in advance for zooming out.
|
||||
- **Readback:** copy the rendered image from the GPU so the cache can keep an independent texture.
|
||||
|
||||
## Scope and code map
|
||||
|
||||
The bitmap cache is for committed patterned walls and backgrounds in render-capable runs. Plain paint surfaces, interactive placement previews, and headless runs retain the direct renderer path. Fully opaque scenery can suppress a fully hidden surface's texture request; partial coverage remains conservative.
|
||||
|
||||
- [StageSurfaceCache](../../scripts/stage/scenery/stage_surface_cache.gd) owns style handles, the scene pool, bake queue, image capture, mipmaps, cache keys, and budget statistics.
|
||||
- [StageRectSurface](../../scripts/stage/scenery/stage_rect_surface.gd) requests the style, clips the surface to the visible camera area, and draws the repeated texture.
|
||||
- [StageSurfaceRenderer](../../scripts/stage/scenery/stage_surface_renderer.gd) defines patterned artwork, material periods, and the direct-drawing path.
|
||||
- [Repeating scenery material cache spec](../specs/scenery_bitmap_tile_cache.md) records implementation constraints and acceptance checks.
|
||||
|
||||
In one observed warm run of `fps.scene`, four style entries used 36,399,144 bytes (about 34.71 MiB); 720 warm camera frames recorded zero builds, readbacks, or evictions. This is a run snapshot, not a guarantee for other scenes, tint combinations, or hardware.
|
||||
@@ -0,0 +1,43 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Erasing changes rectangles while sharing the same background texture</title>
|
||||
<desc id="desc">A rectangular cut turns one wall into top, bottom, left and right fragments. All fragments sample the existing image using their pattern coordinates. The image itself is not painted over or copied.</desc>
|
||||
<defs>
|
||||
<pattern id="brick" width="80" height="48" patternUnits="userSpaceOnUse"><rect width="80" height="48" fill="#d2cbaf"/><rect x="2" y="2" width="36" height="19" fill="#25b8df"/><rect x="42" y="2" width="36" height="19" fill="#39c7ee"/><rect x="-18" y="26" width="36" height="19" fill="#32c2e8"/><rect x="22" y="26" width="36" height="19" fill="#21b4da"/><rect x="62" y="26" width="36" height="19" fill="#32c2e8"/></pattern>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#475569"/></marker>
|
||||
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#475569;stroke-width:3;marker-end:url(#arrow)}</style>
|
||||
</defs>
|
||||
<rect width="1200" height="640" rx="16" fill="#f8fafc"/>
|
||||
<text x="40" y="53" class="title">Erase the wall geometry. Keep the shared image.</text>
|
||||
<text x="40" y="86" class="body">Fragments crop their own rectangles and use the existing pattern coordinates.</text>
|
||||
<text x="50" y="136" class="heading">Before: one wall rectangle</text>
|
||||
<g transform="translate(50 160)">
|
||||
<rect width="460" height="300" fill="url(#brick)" stroke="#94a3b8" stroke-width="2"/>
|
||||
<rect x="155" y="90" width="150" height="120" fill="#fff" fill-opacity=".3" stroke="#b45309" stroke-width="4" stroke-dasharray="10 6"/>
|
||||
<rect x="174" y="131" width="112" height="36" rx="6" fill="#fff7ed"/>
|
||||
<text x="230" y="155" class="body" text-anchor="middle">Cut here</text>
|
||||
</g>
|
||||
<path d="M532 310H648" class="arrow"/>
|
||||
<text x="590" y="283" class="small" text-anchor="middle">Erase area</text>
|
||||
|
||||
<text x="680" y="136" class="heading">After: up to four fragments</text>
|
||||
<g transform="translate(680 160)">
|
||||
<rect width="460" height="300" fill="#dbeafe"/>
|
||||
<g fill="url(#brick)" stroke="#0f766e" stroke-width="3">
|
||||
<rect width="460" height="90"/>
|
||||
<rect x="0" y="210" width="460" height="90"/>
|
||||
<rect x="0" y="90" width="155" height="120"/>
|
||||
<rect x="305" y="90" width="155" height="120"/>
|
||||
</g>
|
||||
<g fill="#fff"><rect x="195" y="26" width="70" height="36" rx="6"/><rect x="182" y="242" width="96" height="36" rx="6"/><rect x="43" y="131" width="70" height="36" rx="6"/><rect x="342" y="131" width="78" height="36" rx="6"/></g>
|
||||
<text x="230" y="51" class="body" text-anchor="middle">Top</text>
|
||||
<text x="230" y="267" class="body" text-anchor="middle">Bottom</text>
|
||||
<text x="78" y="155" class="body" text-anchor="middle">Left</text>
|
||||
<text x="381" y="155" class="body" text-anchor="middle">Right</text>
|
||||
<text x="230" y="143" class="body" text-anchor="middle">Empty area</text>
|
||||
<text x="230" y="173" class="small" text-anchor="middle">Scene behind</text>
|
||||
</g>
|
||||
|
||||
<rect x="40" y="510" width="1120" height="95" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="62" y="546" class="heading">All remaining pieces still use the same cached texture.</text>
|
||||
<text x="62" y="580" class="body">Cancel restores the original geometry. Commit keeps the fragments. Neither paints into the image.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,39 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Zoom uses smaller copies already stored in the material texture</title>
|
||||
<desc id="desc">The brick material starts at 1600 by 608 bitmap pixels. Its mip chain includes 800 by 304, 400 by 152 and 200 by 76, continuing down to one pixel. These copies are prepared once. Brick world dimensions stay fixed.</desc>
|
||||
<defs>
|
||||
<pattern id="brick" width="100" height="38" patternUnits="userSpaceOnUse"><rect width="100" height="38" fill="#d2cbaf"/><rect x="2" y="2" width="46" height="15" fill="#26b8df"/><rect x="52" y="2" width="46" height="15" fill="#39c7ee"/><rect x="-23" y="21" width="46" height="15" fill="#32c2e8"/><rect x="27" y="21" width="46" height="15" fill="#21b4da"/><rect x="77" y="21" width="46" height="15" fill="#32c2e8"/></pattern>
|
||||
<pattern id="half" xlink:href="#brick" patternTransform="scale(.5)"/>
|
||||
<pattern id="quarter" xlink:href="#brick" patternTransform="scale(.25)"/>
|
||||
<pattern id="eighth" xlink:href="#brick" patternTransform="scale(.125)"/>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#7c3aed"/></marker>
|
||||
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#7c3aed;stroke-width:3;marker-end:url(#arrow)}</style>
|
||||
</defs>
|
||||
<rect width="1200" height="630" rx="16" fill="#f8fafc"/>
|
||||
<text x="40" y="53" class="title">Zoom samples ready-made sizes of the same image.</text>
|
||||
<text x="40" y="86" class="body">Mipmaps are generated once with the material. The GPU selects and blends them during rendering.</text>
|
||||
|
||||
<rect x="60" y="150" width="400" height="152" fill="url(#brick)" stroke="#94a3b8"/>
|
||||
<path d="M471 228H508" class="arrow"/>
|
||||
<rect x="530" y="188" width="200" height="76" fill="url(#half)" stroke="#94a3b8"/>
|
||||
<path d="M741 228H778" class="arrow"/>
|
||||
<rect x="800" y="207" width="100" height="38" fill="url(#quarter)" stroke="#94a3b8"/>
|
||||
<path d="M911 228H948" class="arrow"/>
|
||||
<rect x="970" y="217" width="50" height="19" fill="url(#eighth)" stroke="#94a3b8"/>
|
||||
<text x="1043" y="234" class="heading">…</text>
|
||||
|
||||
<text x="260" y="342" class="heading" text-anchor="middle">1600 × 608</text>
|
||||
<text x="260" y="373" class="small" text-anchor="middle">Base image: 2 pixels / world unit</text>
|
||||
<text x="630" y="342" class="heading" text-anchor="middle">800 × 304</text>
|
||||
<text x="630" y="373" class="small" text-anchor="middle">Half size</text>
|
||||
<text x="850" y="342" class="heading" text-anchor="middle">400 × 152</text>
|
||||
<text x="850" y="373" class="small" text-anchor="middle">Quarter size</text>
|
||||
<text x="1000" y="342" class="heading" text-anchor="middle">200 × 76</text>
|
||||
<text x="1000" y="373" class="small" text-anchor="middle">Eighth size</text>
|
||||
<text x="60" y="414" class="small">The actual chain continues down to 1 × 1. Diagram sizes are scaled for illustration.</text>
|
||||
|
||||
<rect x="40" y="450" width="1120" height="143" rx="12" fill="#ecfdf5" stroke="#0f766e" stroke-width="2"/>
|
||||
<text x="62" y="486" class="heading">World size, image size and screen size are different things.</text>
|
||||
<text x="62" y="521" class="body">A brick stays 100 world units wide. At 40% zoom, it covers about 40 screen pixels.</text>
|
||||
<text x="62" y="556" class="body">Camera zoom changes sampling detail; it does not bake another image or resize the brick geometry.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,51 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Background rendering: prepare a style once, reuse it while moving the camera</title>
|
||||
<desc id="desc">A visible surface requests its finish and tint from the scene cache. A cache hit goes directly to the GPU repeat sampler. A cache miss uses one temporary baker, captures an image and mipmaps, and releases vector commands.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#475569"/></marker>
|
||||
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#475569;stroke-width:3;marker-end:url(#arrow)}</style>
|
||||
</defs>
|
||||
<rect width="1200" height="640" rx="16" fill="#f8fafc"/>
|
||||
<text x="40" y="53" class="title">Prepare a material once. Reuse it as the camera moves.</text>
|
||||
<text x="40" y="86" class="body">Only visible, committed patterned scenery requests a cached material.</text>
|
||||
|
||||
<rect x="40" y="140" width="250" height="126" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="60" y="177" class="heading">Visible surface</text>
|
||||
<text x="60" y="210" class="body">Finish + tint</text>
|
||||
<text x="60" y="239" class="small">Example: blue brick</text>
|
||||
<path d="M292 203H370" class="arrow"/>
|
||||
|
||||
<rect x="390" y="140" width="260" height="126" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="410" y="177" class="heading">Scene style cache</text>
|
||||
<text x="410" y="210" class="body">Already have this image?</text>
|
||||
<text x="410" y="239" class="small">Shared by all matching surfaces</text>
|
||||
<path d="M652 203H740" class="arrow"/>
|
||||
<text x="674" y="182" class="small">Hit</text>
|
||||
|
||||
<rect x="760" y="140" width="400" height="126" rx="12" fill="#ecfdf5" stroke="#0f766e" stroke-width="2"/>
|
||||
<text x="782" y="177" class="heading">GPU draws the visible rectangle</text>
|
||||
<text x="782" y="210" class="body">Repeat texture + sample its mipmaps</text>
|
||||
<text x="782" y="239" class="small">Pan / zoom updates sampling coordinates</text>
|
||||
|
||||
<path d="M520 268V324" class="arrow"/>
|
||||
<text x="540" y="304" class="small">Miss: prepare this style</text>
|
||||
<rect x="390" y="344" width="260" height="158" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="410" y="379" class="heading">One temporary baker</text>
|
||||
<text x="410" y="412" class="body">Draw one pattern period</text>
|
||||
<text x="410" y="441" class="body">Wait for GPU; capture image</text>
|
||||
<text x="410" y="476" class="small">Then clear vector commands</text>
|
||||
<path d="M652 423H740" class="arrow"/>
|
||||
|
||||
<rect x="760" y="344" width="400" height="158" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="782" y="379" class="heading">Store the reusable texture</text>
|
||||
<text x="782" y="412" class="body">Crop overscan; generate mipmaps</text>
|
||||
<text x="782" y="441" class="body">Keep the image, not the vector artwork</text>
|
||||
<text x="782" y="476" class="small">Ready styles stay warm within the cache budget</text>
|
||||
<path d="M960 342V282" class="arrow"/>
|
||||
<text x="980" y="309" class="small">Ready</text>
|
||||
|
||||
<rect x="40" y="548" width="540" height="56" rx="10" fill="#dbeafe"/>
|
||||
<text x="59" y="582" class="body">Blue route: reuse during ordinary pan and zoom</text>
|
||||
<rect x="604" y="548" width="556" height="56" rx="10" fill="#ede9fe"/>
|
||||
<text x="623" y="582" class="body">Purple route: bake only when a style is missing</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,44 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="650" viewBox="0 0 1200 650" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Many wall rectangles share one material image</title>
|
||||
<desc id="desc">One blue-brick image supplies Wall A, Wall B, and an erased fragment. Different sizes, positions, and camera views do not create more images. A different tint or finish is a different cached style.</desc>
|
||||
<defs>
|
||||
<pattern id="brick" width="64" height="40" patternUnits="userSpaceOnUse">
|
||||
<rect width="64" height="40" fill="#d2cbaf"/>
|
||||
<g><rect x="2" y="2" width="28" height="15" fill="#25b8df"/><rect x="34" y="2" width="28" height="15" fill="#39c7ee"/><rect x="-14" y="22" width="28" height="15" fill="#32c2e8"/><rect x="18" y="22" width="28" height="15" fill="#21b4da"/><rect x="50" y="22" width="28" height="15" fill="#32c2e8"/></g>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#2563eb"/></marker>
|
||||
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#2563eb;stroke-width:3;marker-end:url(#arrow)}</style>
|
||||
</defs>
|
||||
<rect width="1200" height="650" rx="16" fill="#f8fafc"/>
|
||||
<text x="40" y="53" class="title">Wall area can grow. The number of style images stays the same.</text>
|
||||
<text x="40" y="86" class="body">Position, wall size, camera position and zoom are not part of the style key.</text>
|
||||
|
||||
<rect x="400" y="117" width="400" height="163" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<rect x="420" y="140" width="160" height="112" fill="url(#brick)" stroke="#94a3b8"/>
|
||||
<text x="602" y="162" class="heading">One shared image</text>
|
||||
<text x="602" y="194" class="body">Blue brick</text>
|
||||
<text x="602" y="224" class="small">Varied brick faces</text>
|
||||
<text x="602" y="250" class="small">Repeats both ways</text>
|
||||
<path d="M600 282V311H210V341" class="arrow"/>
|
||||
<path d="M600 311V341" class="arrow"/>
|
||||
<path d="M600 311H990V341" class="arrow"/>
|
||||
|
||||
<rect x="40" y="360" width="340" height="192" rx="12" fill="#fff" stroke="#cbd5e1" stroke-width="2"/>
|
||||
<text x="62" y="396" class="heading">Wall A</text>
|
||||
<rect x="62" y="416" width="296" height="103" fill="url(#brick)"/>
|
||||
|
||||
<rect x="430" y="360" width="340" height="192" rx="12" fill="#fff" stroke="#cbd5e1" stroke-width="2"/>
|
||||
<text x="452" y="396" class="heading">Wall B</text>
|
||||
<rect x="452" y="416" width="190" height="103" fill="url(#brick)"/>
|
||||
<text x="659" y="455" class="small">Same</text>
|
||||
<text x="659" y="480" class="small">style</text>
|
||||
|
||||
<rect x="820" y="360" width="340" height="192" rx="12" fill="#fff" stroke="#cbd5e1" stroke-width="2"/>
|
||||
<text x="842" y="396" class="heading">Erased fragment</text>
|
||||
<rect x="842" y="416" width="90" height="103" fill="url(#brick)"/>
|
||||
<text x="952" y="455" class="small">Only the visible</text>
|
||||
<text x="952" y="480" class="small">rectangle changes</text>
|
||||
|
||||
<text x="40" y="591" class="body">A different finish or tint is a separate style. A matching warm style reuses the existing image.</text>
|
||||
<text x="40" y="621" class="small" fill="#64748b">Patterns shown here are schematic; the actual brick image contains 8 columns × 8 rows of varied artwork.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,42 @@
|
||||
# Scenery surface caching
|
||||
|
||||
`StageRectSurface` renders committed patterned scenery through reusable texture tiles.
|
||||
Drawing previews use the adaptive lightweight renderer. Paint remains a single rectangle.
|
||||
|
||||
## Camera movement
|
||||
|
||||
Tiles use a fixed grid in the surface's local coordinates. Texture resolution changes in
|
||||
powers of two, so adjacent wheel steps reuse existing tiles. Panning requests only newly
|
||||
exposed tiles. Existing textures stay visible while missing tiles are prepared, with a
|
||||
lightweight surface motif beneath them to prevent blank areas.
|
||||
|
||||
`StageSurfaceCache` keeps queued tiles as metadata. A shared queue creates and renders at
|
||||
most one tile per process frame across all surfaces. It also retires at most two obsolete
|
||||
render targets per frame. This avoids large allocations, vector rebuilds, and batches of
|
||||
GPU resource releases during camera input.
|
||||
|
||||
Each tile has a 256 × 128 pixel core and a four pixel gutter. The gutter is cropped during
|
||||
compositing to prevent seams. Each surface requests at most 192 visible tiles and retains
|
||||
at most 224 tiles, evicting older unused entries. Half-resolution tiles retain full brick
|
||||
texture and wood grain. Only quarter-resolution tiles and below omit fine texture, for
|
||||
distant views. Brick and plank positions remain fixed. Detail can fill in progressively
|
||||
after movement, including when returning from a distant view to a normal zoom level.
|
||||
|
||||
## Invalidation and coverage
|
||||
|
||||
Geometry, finish, tint, and motif-origin changes invalidate that surface's tiles. Camera
|
||||
movement selects tiles without invalidating their artwork. Render targets belong to the
|
||||
surface node; queued references are weak so deleted surfaces cannot keep jobs alive.
|
||||
|
||||
Fully opaque rectangular sibling surfaces can suppress artwork entirely hidden behind
|
||||
them. Coverage checks account for transforms, draw order, and visibility. Transparent
|
||||
surfaces, custom materials, differing relative-Z conventions, and Y-sorted parents disable
|
||||
this optimization conservatively. Revealing a surface resumes its pending tiles.
|
||||
|
||||
## Verification
|
||||
|
||||
`tests/test_scenery_surface_cache.gd` checks rendered appearance against the direct vector
|
||||
renderer, tile reuse and invalidation, the shared build limit, allocation and retention
|
||||
bounds, and covered-surface behavior. Run it with a graphical Godot renderer for pixel
|
||||
comparisons. The adaptive preview regressions remain in
|
||||
`tests/test_scenery_surface_preview.gd` and `tests/test_scenery_brick_preview.gd`.
|
||||
@@ -178,4 +178,8 @@ This document tracks known technical debt, optimization opportunities, and minor
|
||||
| 2026-10-17 | Wrote the Phase 19 selected-artwork depth inspector specification (`docs/specs/phase_19_selected_artwork_depth_inspector.md`) for final sign-off. It reuses the existing `StageInspectorPanel` and `StagePanelChoiceGroup`, applies Far/Near/On the floor immediately to one live or missing Scenery placement through `StageSceneryArtwork.set_depth()`, and follows the Stage's existing dirty/save path. The current `.scene` depth field, `StageDrawOrder`, and `stage_panels` tokens need no changes. Updated #72 to include immediate depth edits in a future Stage-wide Undo phase. No runtime code changed by this specification entry. |
|
||||
| 2026-10-17 | Wrote the Phase 19 Background ground and wall color architecture specification (`docs/specs/phase_19_background_ground_and_wall_color.md`) for final sign-off. It defines distinct inert wall/backdrop scene types over a small shared rectangle contract, world-aligned bounded vector materials, selected-piece color preview without mutating saved tint, one eight-color Stage settings list, scene v1.6 migration of old Brick/Wood placeholder green, shared Stage inspector controls, and confirmed single-piece backdrop erasure. Logged #74 for zoom-consistent motif budgets and #75 for the old unused wall-color ambiguity. No runtime code changed. |
|
||||
|
||||
| 2026-10-20 | Approved the scenery bitmap tile cache architecture (`docs/specs/scenery_bitmap_tile_cache.md`). Lightweight `StageSurfaceCache` handles share immutable bitmap tiles through one `StageSurfaceBakePool` under the current scene, with a 64 MiB resident bitmap budget and one reusable offscreen baker. Camera-visible tiles use four fixed zoom levels (0.125, 0.5, 1, 2); the coarsest tile bakes first and refined detail overlays it. At maximum 6× zoom, the 2× bitmap may look softer because it is magnified threefold. Erased fragments clip shared tiles only when their complete pattern sampling phase matches. The previous per-surface viewport cache retained vector commands and had no scene-wide resource limit, a likely contributor to `Element limit reached`; the exact RID pool remains unconfirmed. GPU readback during bitmap capture and regeneration after budget eviction are accepted costs to measure during implementation. |
|
||||
|
||||
| 2026-10-20 | Replaced the positional scenery tile design after the user observed continuous buffering when panning after zoom. The accepted revision in `docs/specs/scenery_bitmap_tile_cache.md` bakes one seamless, 2× bitmap per finish/tint period and uses GPU repeat plus generated mipmaps for all camera positions and zooms. Erased fragments share the style texture and crop it using their existing sample coordinate. The scene-wide 64 MiB limit includes mipmaps; inactive style variants may be evicted, while recently used styles stay warm when space allows. This trades unique random detail across an unlimited wall for finite repeated variation and keeps the existing possible 3× bitmap enlargement at maximum zoom. A style change can bake once; repeated camera movement must not increment build or readback counts after styles are ready. |
|
||||
|
||||
_End of Log_
|
||||
|
||||
Reference in New Issue
Block a user