Repository navigation
Bake ground tiles from Lua into mipmapped arrays, hex tiled in the shader - #332
Merged
Merged
Conversation
tessellateAsset took its base index as a uint16 with no limit check, so an asset over 65,535 vertices wrapped its indices and drew garbage without a word. It now packs paths into as many meshes as it takes, in path order, and AssetRegistry::buildMeshes hands them all back. buildMesh stays for callers that draw one mesh (flora, groundcover, the asset-manager thumbnails) and now fails with an error when the asset needs more than one, instead of corrupting. A single path that tessellates past the limit on its own is dropped with a warning, the way SVGLoader does. GeneratedPath gains minPixelsPerMeter (0 = always drawn), and buildMeshes takes a pixel density that drops the paths asking for more, which is what the ground tile bake will use to leave fine detail out of coarse mips.
noise2(x, y, seed) is salal's gradientNoise2, about -1 to 1, so a generator gets the same deterministic field the rest of the engine uses. periodicNoise2 (x, y, period, seed) wraps the integer lattice every `period` cells, so the field tiles seamlessly (ground tiles need that); inside cells 0 to period - 2 it equals noise2 bit for bit. The period arrives as a double because P / F is a float in Lua even when it divides evenly, and anything that isn't a whole number >= 1 is a script error. path:setLod(n) sets GeneratedPath::minPixelsPerMeter. Existing bindings are unchanged.
A ground def (role ground, at assets/world/ground/<Surface>/<Surface>.xml) is the tile art for one land surface. It names its <surface>, and optionally a <period> in meters (default 4), <pixelsPerMeter> (64), <rotate> free, quarter or none (free), <variantCount> (8 here, 1 elsewhere) and a <macro> with dry and lush r,g,b tints, an amplitude and two wavelengths. The generator or svgPath are the existing ones. assets already links world, so the surface is a world::Surface, parsed with a new surfaceFromString next to surfaceToString; AssetDefinition only forward-declares the enum, as PlacementExecutor.h does. The validator checks that period divides 512 and fills whole pixels at the chosen density, that variantCount is 1 to 32, pixelsPerMeter is a power of two from 16 to 512, the surface is a land surface, that no two ground defs paint the same surface, and that a ground def has no <biome> rules (placement already skips defs without any). Once the tree has any ground def it warns for each land surface that lacks one; that becomes an error when the content lands. Ground-only elements on other roles warn. Ground defs are skipped by the eager collision capture, which would otherwise run their (heavy) generators at load for nothing.
… split Validator rules for period, pixelsPerMeter, variantCount, surface, duplicates and missing surfaces; schema parse and defaults; noise2 against salal's gradientNoise2 and per seed; periodicNoise2 repeating along x, y and both, including across the period seam and negative cells; setLod landing on the path; a 22,000 triangle asset splitting into two valid meshes with every vertex kept; and the pixel density filter.
…gent-a8260b19db14da839
…sRGB encode, metrics Every downsample is a Kaiser-windowed sinc (alpha 4, 3 texels each way) that wraps, clamped to the min and max under its main lobe, then given the source's mean back: the clamp alone brightened a coarse mip of small light detail by 25%. The mean lock adds a 2-texel blur of the residual against the reference, so the mean moves and surviving detail keeps its contrast. Encoding dithers half a code so flat far mips keep their mean.
GroundTileBaker draws each mip from the def's meshes at that mip's texel density, so setLod paths leave at the level their author chose. A triangle's wrap copies follow it, which keeps the generator's paint order at the edges. Mips 0 and 1 render at 8x8, the rest at 4x4, tiled past 4096 px, then go through the Kaiser downsample, the mean lock against mip 0 and sRGB encoding. It returns CPU chains plus per-mip metrics (mean, delta E, variant spread, windowed contrast) and timings, logs the table, and puts the coordinate system, viewport and bound target back.
Every level costs one full generator run whatever survives the LOD cut, about 95 ms a level on the snow placeholder, which the per-mip column makes plain.
A ground def's detail pane gets a Vectors / Bake switch. Bake runs the game's GroundTileBaker one variant a frame and shows the variant's mips at one texel a pixel, a strip of what the game draws at 160 down to 3 px/m (a 2x2 of four variants, one LOD a draw with the +0.25 bias, trilinear in linear light, computed on the CPU since salal textures clamp), and the metrics table. Next variant steps through the chain; Reload drops the cached bakes so the def bakes again.
Drawing a tile's whole mesh 3x3 doesn't wrap it: each copy's full-tile base paints over the overhang of the copies drawn before it, and its own overhang lands on top of everything drawn after, so blades get cut at two edges and float over the other two. buildMeshes now repeats every path of a procedural ground def that crosses an edge of its [0, period] square at the offsets it crosses into, right after the original, so paint order holds across the seam. Clipped to the square, the meshes are the seamless tile, which means the baker draws them once instead of 3x3; the spec says so now.
A dense ground def needs several meshes, and buildMesh refuses it, so the asset render path takes them all from buildMeshes. A ground def is framed on its [0, period] square rather than its mesh bounds, and drawn clipped to it, since its art crosses the edges. The detail pane shows the first variant tiled 3x3, each copy clipped to its own cell, beside the usual variant grid, so a seam would show. asset-cli's render smoke pass uses buildMeshes too, so a big ground def isn't reported as producing nothing. Drawing a ground def's 15 copies is slow in Debug, about 2 s a frame with the grass tile selected.
ground_grass.lua paints one 4 m tile dark to light: a far stack for the coarse mips (mean base, soil, clover and tuft proxies), a gap-shadow cover tagged setLod(20), then soil peeks with pebbles, dark undergrowth fans, two-tone tuft fans grown in clumps that share a tone and a lean and shade like a dome from above, broadleaf rosettes, clover in Thomas clusters, and top blades with centered underlays, broad blades, straw and dry tips. Density fields are periodicNoise2 at 0.8 and 0.4 m, counts are per square meter, and every look knob is a param, so the other grass surfaces can reuse the script. Ground_Grass is the Grass surface's def with the tuned params. Mean is about sRGB (73, 103, 49), the eight variants agree within 0.6% in luma, and the far stack lands within 0.1% of the full-detail mean. In RelWithDebInfo a variant is about 190 ms of Lua and 340 ms with wrap and tessellation, 27.7k paths and 230k vertices.
…ures-1cd28e # Conflicts: # apps/asset-manager/AssetDetailView.h
buildMeshes repeats every ground path that crosses the tile edge right after the original, so the baker no longer emits 3x3 copies of each triangle. It scales the meshes into render pixels, drops what misses the target, and draws them in paint order; the target's clip is the wrap.
buildPaths runs the generator once and tessellates each path once, tagged with its setLod density, and TessellatedPaths::meshesAt packs the paths a density keeps into the meshes buildMeshes returned there. buildMeshes is now a thin call over the two, and tessellateAsset is gone. The registry also resolves a def's script path for callers, and remembers which XML file a def came from.
…kes on disk The baker builds every variant's paths at once on the TaskPool, one generator run and one tessellation each, then draws each mip from them. Each def's bake is kept in cache/ground beside the exe, keyed by its XML, its script, its shape and the baker's version, so an unchanged def comes back in about 20 ms. Ground_Grass in RelWithDebInfo goes from 19.6 s to 5.8 s cold; what's left is the GPU finishing salal's batched draws. The asset-manager's Bake view takes the same steps, the paths in one frame and a variant's render a frame, and Reload bakes a def again once its files change.
GrassShort, GrassMeadow, GrassTall, Dirt, Sand, Mud, Rock and Snow get placeholder defs that share ground_placeholder.lua: a flat base in the surface's old color under a light, low-contrast speckle, 4 variants at 64 px/m. Real art replaces them later. With all nine covered, the validator's missing-surface warning is now an error; the ground tests build a full tree so they still check one def at a time.
GroundTileBank bakes every ground def with GroundTileBaker once the registry has loaded, a variant a call so the splash keeps drawing (its boot log counts them; the loading scene finishes the bake after a --scene jump), and uploads them into GL_SRGB8_ALPHA8 texture arrays, one per layer size, every mip, with a per-surface table of layers, period, density, rotation and macro tints. land.glsl samples them on a chunk-continuous hex lattice (4 m columns, 148 rows a chunk): per-cell variant, rotation and offset, Mikkelsen's luminance-weighted blend with a gain that relaxes with zoom, one sample where a cell holds 98% or below terrain/ground/hexMinPxPerMeter, textureLod at a per-surface LOD the CPU computes once a frame. The fringe's other surface takes the dominant cell alone. Samples blend in linear light and are encoded back to sRGB for the rest of the pass. The chunk worker fills a 257x257 macro texture at 2 m a texel, uploaded with the render tiles: a dry to lush mix at 36 and 14 m and value noise at 10 and 5 m. Its last row and column are the next chunk's first, so borders match, and each lattice corner is hashed once a chunk (3 ms a chunk). Deleted: the tile atlas (GameBootstrap's initializeTileAtlas and surfacePatternPath), the old pattern SVGs in assets/tiles/surfaces, and Chunk::getSurfaceColor and getBiomeColor.
The near stack now runs down to mip 3, so the default 24 px/m view (two thirds mip 3) shows real clumps: tufts around a lit crown, light cores, dark ground between them, clover and the odd soil peek. The far stack for mips 4 to 6 is a coarse tone model of the near stack averaged into one rect a mip 4 texel, so it keeps mip 0's mean and patches in the same places instead of bright clover and tuft proxies. Blades take the model's tone where they lie, over a proxy strip of that tone kept to mip 3, so a light blade and its dark underlay average to what the coarser mips show. Clump sizes and tones are normalized per variant. The mean lock is under 2.3% on every mip (it was 8 to 17%), mips 4 to 6 sit at 72 to 94% contrast, and the palette leans a little more olive.
…res-1cd28e The baker builds every variant's paths once on the pool, bakes a variant from them on the GL thread, and keeps each def's bake on disk under cache/ground beside the exe.
…-1cd28e The bank now bakes through the staged baker, so there's one baker API and the game reads the same kind of disk cache the asset-manager does. Each step uploads every def in a row the cache holds, and at the first it doesn't, builds that def's paths on the pool; later steps bake and upload a variant each, and the last one stores the def. Cold, the loading scene's bake takes 4.5 s of steps; warm, 36 ms.
Each hex cell flips left to right or not off a hash bit, with its random uv offset and no rotation, so art with an up (grass blades) still breaks repetition without spinning sideways.
The camera is a hair off top-down, so the tile is drawn that way now: tufts are upright fans rooted at their base, blades lean within about 30 degrees of vertical, and everything standing is painted back to front by root. Shapes crossing the top or bottom edge are split there, so the depth order holds across the wrap. Undergrowth goes where a cover grid says the ground would show, until about a tenth of the tile is left open, so every variant shows the same 15-20% of earthy soil and litter at close zoom. Blade proxies take the blade's own average, the far stack is pinned to the measured mip 0 mean, and the tile doesn't rotate.
On light views the driver drops the 3090 to P3 or P2 and tileGpuMs then measures the clock, not the code (WOR-553). GpuHold draws a throwaway shader into its own 512x512 target after the scene's GPU timer closes, so no scene timer counts it and the frame is untouched, and sizes that work each frame so scene plus hold GPU time sits near 0.8 of the frame. Off by default, toggled with /api/control?action=gpuhold&value=0|1 and read back from /api/state?what=gpuhold, so only builds with the dev server can turn it on. perf-capture.ps1 turns it on for the whole capture (-NoGpuHold skips it), records the P-state and clocks per sample from a read-only nvidia-smi query, and warns in red when a sample isn't P0.
ground-textures.md describes how captures hold P0 now (WOR-553), with the on and off numbers for main and this branch, and the testing README lists the gpuhold control and state view. The terrain spec's perf section points at the hold instead of nvidia-smi -lgc.
The tile pass is short of quarter-rate work (int/float conversions and MUFU ops), and the hex sampler added a dozen conversions a pixel. Cell ids stay floats and cross to int through the 1.5 * 2^23 bias, the variant pick rounds in float on the hash's low 12 bits instead of an integer modulo by a uniform (the same pick for every power-of-two variant count), and the CPU folds the chunk's cell origin and the macro tint and value constants. River view: tileGpuMs down 0.01 to 0.02 ms in every cell.
Each inner tile's 3x3 binomial sum comes from its rows' 1 2 1 sums, read from a nibble table, and the thin-feature test runs on the whole 4x4 mask at once. The sums, and so the field, are bit-identical to the per-bit version and to SurfaceField.cpp. The distance to an isoline takes one inversesqrt in place of length() and a divide. tileGpuMs on the river view drops 0.02 to 0.07 ms against the previous commit in every cell; with both commits, coast and desert drop 0.03 to 0.09 ms against e533e5b.
…urface Render tiles carry a near-interior bit (every tile within two is one surface) beside the interior bit. In such a tile the fine warp term moves the read at most its amplitude from where the low term puts it, so when that range keeps the read's lattice cell at -1 or 0 the block is one surface and the field is 1. evaluateSurfaceField and land.glsl both check that after the low noise and skip the fine noise and the block; surface, field and margin are unchanged (a new test holds the shortcut to the full evaluation), only the reported warp is zero there. The golden test leaves the warp uncompared where the shortcut's bound falls within rounding on one side only. tileGpuMs against the previous commit: 0.03 to 0.06 ms lower at zoom 20 and 8, up to 0.02 lower elsewhere.
Under open water the shading mixes the ground out by 1, so the land field and its ground samples there were thrown away. The tile pass now works out the pixel's water coverage first (waterPixel: the distance field, its wobble, the coverage) and calls landColor, from one place, only where some ground shows; shadeWater takes that coverage and the ground. The picture is unchanged. tileGpuMs against the previous commit: river 0.10 to 0.24 ms lower at zoom 3 to 20 and up to 0.03 lower further out; coast up to 0.07 lower idle and 0.06 to 0.11 lower on both scrolls over the sea; desert, with little water, within 0.01.
At 12 px/m (zoom 1.5) the one-sample ground looks the same as the blend in screenshots: the hard cell edges it leaves are a few levels at most in speckled art and don't read at 1:1. Starting at 16 lines the blend up with the gain ramp, which is off at 16 and under, and saves 0.05 to 0.10 ms of tileGpuMs at zoom 1.5.
Every hex pixel now samples its heaviest cell through the same instructions, and only the transition bands branch for the second and third. Before, the one-sample cases returned from three separate copies of the sample and the bands sampled from three more, so a warp straddling a band edge ran several. The weights, the cells and the blend are the same; the blend sums in weight order now, a rounding-level difference. The desert scroll at zoom 3, the one cell still over the gate, drops from 0.25 to 0.24 ms of tileGpuMs; other cells move within 0.01.
ground-textures.md gets the attribution, the changes with what each saved, the final two-run table against main and what was tried and dropped, and its Shader, render tiles, land field and tunables sections catch up with the near-interior bit, the 16 px/m hex cutoff and the water skip. D16 in the terrain spec gets the near-interior bit and shortcut, and status.md the task.
The dither leaves each texel's rounding unbiased, but a small level's errors don't average out: on a 4x4 mip 6 one code of a dark channel is several percent of its light, so a dark ground tile (Mud, Dirt) missed the bake's 0.5% mean gate at mips 5 and 6 on dither luck alone. Now a channel whose linear mean lands more than 0.05% off has its codes stepped one at a time, the texels that rounded furthest the wrong way first, until no step brings the mean closer. Big levels are already within that and untouched; every bake's mean error is now under 0.1% at every mip. The baker's output changes for the same inputs, so its cache version goes to 2 and every cached bake is dropped once.
Reading the cached bakes is most of a warm bake, and with real art for all nine surfaces there are 56 MiB of them: one after another the lookups took 76 to 78 ms, 13 to 14 ms for each grass def's 11 MB file, and the warm splash bake 90 to 93 ms. The bank's first step now looks every def up at once on its pool and later steps take the result in order, so the lookups take 20 to 21 ms and a warm bake 35 to 38 ms wall clock.
The three remaining grass surfaces get real ground art from ground_grass.lua, each with its own params at 128 px/m and 8 variants, mirrored like Grass: - GrassShort is dry short turf, an olive sward dotted with tussocks of short yellow-green tufts, more straw, dry tips and soil peeks. - GrassMeadow is a lush, saturated sward with more clover, almost no open ground and wildflowers. - GrassTall is long, dark grass, bigger clumps of taller tufts and long flopping blades, some with tan seed-head tips. Wildflowers are the generator's one addition: heads in blue-noise clusters and singles, petal colors taken in turn so every variant has the same mix, each on a proxy disc in its average color kept to mip 3, drawn over everything and painted into the far stack's tone model. Off by default, so Grass bakes as before. Each def's far stack is calibrated from its bake (farMean, farContrast, farCoarseContrast): every check passes but the mips 1 to 3 contrast flags Grass shows too, and baked alone each is at or under Grass's cold bake.
ground_bare.lua replaces the placeholder speckle for the five unvegetated surfaces, each def differing only in params, at 64 px/m and 8 variants: Dirt is a crumble of clods with stones, cracks and dead grass bits, Sand meandering wind ripples with shells and pebbles, Mud churned wet lumps with puddles in hollows, Rock weathered slabs along wandering joints with lichen, and Snow wind-sculpted drifts with blue lees and sparkle. Everything is lit from the top of the screen, so every def is mirrored. The ground (base, tone patches, rock plates) is laid out first into a tone grid that the layers above take their colors from, so they keep the patches' contrast at every mip, scaled so each variant averages to its base color. Mips 4 to 6 are that grid as one rect a mip 4 texel, with puddles and lichen averaged in, set from the bake report (farContrast, farCoarseContrast, farGain) the way Grass's far stack is. What drops out at the coarser mips is balanced so the mean lock has little to do: paired light and dark grain, crests whose lit strip cancels their lee, lumps whose cap cancels their darkened body, stones on proxies. Every check passes but a few mips 1 to 3 contrast flags like Grass's. The placeholder generator had nothing else using it and is gone.
ground-textures.md's art direction now covers each surface: its look, its rotation mode and why, its tile mean and macro tints, and what blending has to know about it (Mud's glossy puddles want a hard edge, Sand's ripples and Snow's drifts break at hex cell borders). The content table names the two generators, the baker and bank notes cover the mean-keeping encode and the side-by-side cache lookups, and the bake and memory numbers are today's: 18.3 s cold and 35 to 38 ms warm, 56.0 MiB of arrays. status.md ticks the content tasks and adds round 2.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Validation accepts oversized ground layers and unsupported SVG ground assets, allowing excessive GPU allocations and startup-blocking bake failures.
4 open issues
What changed in this PR
Replaces World-sim’s ground atlas with baked, mipmapped texture arrays to eliminate atlas-edge bleeding and improve ground rendering across zoom levels.
Changes:
- Adds ground asset definitions, reusable tessellated paths, bake/cache interfaces, and asset-manager previews.
- Integrates ground arrays, per-chunk macro tint textures, and surface-field shortcuts into world rendering and startup.
- Adds GPU-hold controls for performance captures and updates tests and documentation.
| File | Description |
|---|---|
| scripts/perf-capture.ps1 | Enables GPU hold and records clock states. |
| libs/world/world/chunk/SurfaceField.test.cpp | Tests near-interior flags and shortcut equivalence. |
| libs/world/world/chunk/SurfaceField.h | Defines near-interior bounds. |
| libs/world/world/chunk/SurfaceField.cpp | Adds near-interior evaluation shortcuts. |
| libs/world/world/chunk/RenderTiles.h | Adds the near-interior flag. |
| libs/world/world/chunk/GroundMacro.test.cpp | Tests shared macro-texture borders. |
| libs/world/world/chunk/GroundMacro.h | Defines per-chunk macro texture data. |
| libs/world/world/chunk/GroundMacro.cpp | Generates seeded macro tint fields. |
| libs/world/world/chunk/Chunk.h | Stores macro textures and adds surface parsing. |
| libs/world/world/chunk/Chunk.cpp | Generates macro textures; removes old color helpers. |
| libs/world/CMakeLists.txt | Builds macro texture generation. |
| libs/world-view/world/rendering/SurfaceFieldGolden.test.cpp | Adjusts CPU/GPU comparisons for shortcuts. |
| libs/world-view/world/rendering/shaders/tile.frag | Skips ground shading beneath opaque water. |
| libs/world-view/world/rendering/shaders/includes/water.glsl | Separates water coverage from shading. |
| libs/world-view/world/rendering/GroundTileFilter.h | Defines filtering, color, and sampling interfaces. |
| libs/world-view/world/rendering/GroundTileCache.test.cpp | Tests cache round trips and invalidation. |
| libs/world-view/world/rendering/GroundTileCache.h | Defines persistent bake caching. |
| libs/world-view/world/rendering/GroundTileBank.h | Defines texture-array ownership and staged baking. |
| libs/world-view/world/rendering/ChunkRenderer.h | Adds ground-array and macro-texture integration. |
| libs/world-view/CMakeLists.txt | Builds ground baking components. |
| libs/assets/CMakeLists.txt | Builds reusable tessellated paths. |
| libs/assets/assets/TessellatedPaths.h | Defines density-tagged path storage. |
| libs/assets/assets/TessellatedPaths.cpp | Tessellates paths and packs bounded meshes. |
| libs/assets/assets/lua/LuaEngine.cpp | Exposes path LOD and seeded noise functions. |
| libs/assets/assets/IAssetGenerator.h | Adds per-path detail thresholds. |
| libs/assets/assets/AssetValidator.test.cpp | Adds ground-definition validation cases. |
| libs/assets/assets/AssetValidator.cpp | Validates ground roles and settings. |
| libs/assets/assets/AssetRenderer.h | Supports multi-mesh, clipped previews. |
| libs/assets/assets/AssetRenderer.cpp | Frames and renders ground tile previews. |
| libs/assets/assets/AssetRegistry.h | Exposes multi-mesh and reusable-path APIs. |
| libs/assets/assets/AssetDefinition.h | Adds ground roles and rendering parameters. |
| docs/testing/scenarios/06-asset-manager-browse.md | Adds bake and cache verification steps. |
| docs/testing/README.md | Documents GPU-hold capture controls. |
| docs/technical/vector-graphics/INDEX.md | Updates the ground-art workflow. |
| docs/technical/organic-terrain/terrain-polygons-architecture.md | Documents shortcuts and clock-state measurements. |
| docs/technical/INDEX.md | Updates the ground-texture documentation entry. |
| docs/technical/asset-manager/README.md | Includes ground tiles in browser coverage. |
| assets/world/ground/Snow/Snow.xml | Defines procedural snow ground art. |
| assets/world/ground/Sand/Sand.xml | Defines procedural sand ground art. |
| assets/world/ground/Rock/Rock.xml | Defines procedural rock ground art. |
| assets/world/ground/Mud/Mud.xml | Defines procedural mud ground art. |
| assets/world/ground/GrassTall/GrassTall.xml | Defines tall-grass ground art. |
| assets/world/ground/GrassShort/GrassShort.xml | Defines short-grass ground art. |
| assets/world/ground/GrassMeadow/GrassMeadow.xml | Defines meadow ground art. |
| assets/world/ground/Grass/Grass.xml | Defines grass ground art and parameters. |
| assets/world/ground/Dirt/Dirt.xml | Defines procedural dirt ground art. |
| assets/tiles/surfaces/GrassTall/pattern.svg | Removes the old tall-grass atlas pattern. |
| assets/tiles/surfaces/GrassShort/pattern.svg | Removes the old short-grass atlas pattern. |
| assets/tiles/surfaces/GrassMeadow/pattern.svg | Removes the old meadow atlas pattern. |
| assets/tiles/surfaces/Grass/pattern.svg | Removes the old grass atlas pattern. |
| assets/tiles/surfaces/Dirt/pattern.svg | Removes the old dirt atlas pattern. |
| apps/world-sim/scenes/splash/SplashScene.cpp | Bakes ground tiles before enabling entry. |
| apps/world-sim/scenes/loading/GameLoadingScene.cpp | Handles baking when startup skips the splash. |
| apps/world-sim/scenes/game/GameScene.cpp | Integrates ground tiles and GPU-hold controls. |
| apps/world-sim/scenes/game/dev/GpuHold.h | Defines the GPU-hold controller interface. |
| apps/world-sim/scenes/game/dev/GpuHold.cpp | Implements adjustable offscreen GPU work. |
| apps/world-sim/Main.cpp | Passes the app into resource initialization. |
| apps/world-sim/GameBootstrap.h | Exposes ground-bank lifecycle functions. |
| apps/world-sim/GameBootstrap.cpp | Replaces atlas bootstrap with the ground bank. |
| apps/world-sim/CMakeLists.txt | Builds GPU-hold support. |
| apps/asset-manager/TileImageStrip.h | Defines captioned bake-image strips. |
| apps/asset-manager/TileImageStrip.cpp | Uploads and draws bake-image strips. |
| apps/asset-manager/scenes/BrowserScene.cpp | Wires bake previews and cache reloads. |
| apps/asset-manager/GroundBakeView.h | Defines the staged bake inspector. |
| apps/asset-manager/CMakeLists.txt | Builds bake-preview components. |
| apps/asset-manager/AssetThumbnail.h | Supports repeated, clipped ground previews. |
| apps/asset-manager/AssetDetailView.h | Adds ground preview and bake-mode state. |
| apps/asset-manager/AssetDetailView.cpp | Adds Vectors/Bake switching and tiled previews. |
| apps/asset-cli/Main.cpp | Uses multi-mesh generation for smoke checks. |
Review effort: Balanced
- One layer-size rule (GroundLayer.h: period x pixelsPerMeter a whole number of texels, 1 to 1024) shared by AssetValidator, GroundTileBaker::begin and GroundTileBank::begin, so an 8192 texel layer is rejected at validation and the bank allocates no array for it. - Validation reports an SVG (assetType simple) ground def as an error; the baker only runs generators. The spec says so. - periodicNoise2 wraps the lattice index without overflowing int32 for periods near INT32_MAX. - perf-capture.ps1's cleanup resets the pan too, so a failed scroll scenario doesn't leave the camera moving.
Owner
Author
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.


Replaces the ground atlas with baked, mipmapped texture arrays. The faint tile-edge lines that flickered while panning came from the atlas: each surface's pattern was packed edge to edge with
GL_LINEAR, no gutter and no mips, so every 1 m tile edge blended half a texel of its neighbor. That's gone at the source.ground,assets/world/ground/<Surface>/) drawn by Lua generators,ground_grass.luaandground_bare.lua, 8 variants each, withpath:setLodfor per-element LOD.GroundTileBakerrenders each mip from the vectors (supersampled, Kaiser filtered, mean locked) into aGL_TEXTURE_2D_ARRAY(sRGB8_ALPHA8), cached on disk under<exe>/cache/ground/. Cold bake about 18 s on the splash, warm about 38 ms.land.glsldoes stochastic hex tiling at a constant mip per draw, one tap where a cell dominates, and macro tint from a per-chunk texture baked on the chunk worker.Perf on an RTX 3090, RelWithDebInfo, 3072x1728, quickstart planet, GPU held at P0; river, coast and desert views at seven zooms plus two scrolls, median of 9 samples. The tile pass is a median 0.04 ms under main, with the worst cell +0.015 ms against a noise floor of about 0.01.
Spec:
docs/technical/ground-textures.md. The blending work is stacked on this in its own draft PR. Not for merge until I've tested it by hand.