Files
stickman/docs/architecture/background_rendering_guide.md
T
ryan c1758efd3b 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.
2026-10-21 11:06:57 -04:00

88 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
![Patterned scenery render and cache pipeline](diagrams/background_pipeline.svg)
## 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.
![Many surfaces sharing one finished material image](diagrams/background_reuse.svg)
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.
![Fixed material image with mipmap levels](diagrams/background_mipmaps.svg)
## 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.
![Erase fragments clipping the same repeating image](diagrams/background_erase.svg)
## 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.