Continue 3d terrain work#4389
Draft
acalcutt wants to merge 232 commits into
Draft
Conversation
…endered to texutre. Once they are, they won't be projected right, and once that's fixed, performance will likely be terrible.
…cpp seems like a dead end/bad idea
…xture (one tile), which the whole viewport is mapped onto.
…me as the terrain tile. This should be calculated elsewhere and passed in the TileID (at least that's how GL JS does it)
…e, which is drawn using all tiles and a hardcoded matrix. Next steps: Only draw tiles that overlap the given terrain tile, and set the appropriate transformation matrix
…rrainRttPosMatrix. Remove terrainRttPosMatrix from overscaledTileID, which is where it is in GL JS but really doesn't belong.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the temporary macOS/Metal terrain render-test step, its artifact upload, and the metrics/macos-terrain-style.json manifest that were added to iterate on the terrain work. The terrain render tests still run as part of the general render-test suite; this only removes the extra debugging-oriented CI plumbing. (Re-commit of d850c30, which was orphaned by a branch reset.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regenerated from the native render with the elevation double-scale reverted and the occlusion/drape fixes in: the composition matches the maplibre-gl-js reference, with minor coastline relief now that elevation is at true scale. default and skirts-auto/none keep their gl-js baselines for now: they render blank because the camera is not elevated above exaggerated terrain (camera ends up inside the mountains; gl-js raises it per frame in the projection). Updating them would bake in blank images and hide that bug - they will pass once camera elevation over terrain is implemented. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2's status note said occlusion was verified on OpenGL and structurally the same elsewhere; it had in fact never worked on Vulkan. Record the six independent defects found and fixed there (aaa145c), and flag the open issue: GL occlusion, previously working, stopped hiding labels behind terrain after the same fix landed - prime suspect is switching calculate_visibility's argument from gl_Position to projectedPoint (fix maplibre#6), not yet diagnosed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes a monotonic GPU memory leak that eventually got the app OOM-killed after panning/zooming over 3D terrain (e.g. the "3D Terrain (planet vector map)" style, which adds a hillshade layer on a raster-dem source). Each hillshade tile builds a "prepare" render target: an offscreen 512x512 target for the Sobel derivatives plus its DEM input texture (514x514). RenderHillshadeLayer appended every one of these to activatedRenderTargets, a list only ever cleared by removeRenderTargets(), which is called solely from markLayerRenderable()/layerRemoved(). markLayerRenderable() is edge-triggered (RenderOrchestrator calls it only when a layer's renderability changes), so while panning - the layer stays continuously renderable - the list is never cleared. It grew without bound and kept a shared_ptr alive to every prepare target ever created, so their offscreen + DEM textures were never freed. Measured on device (PowerVR, panning the planet vector terrain style): live texture count climbed monotonically tex=175 -> 634 in ~45s, with the 512x512 and 514x514 counts rising in lockstep and together accounting for ~98% of all live textures - until the process was reclaimed. (meminfo did not show it: this driver reports Graphics/GL mtrack as 0, so GPU textures are not in PSS.) Now activatedRenderTargets is reconciled against the tiles still in the cover each frame: targets whose tile has left are released (RemoveRenderTargetRequest), newly covered ones are registered, and the diff is by identity so persistent tiles cause no churn. After the fix the same run plateaus (tex ~99-153, 512/514 ~40-75) and oscillates within a band instead of growing. Also harden RenderOrchestrator::addRenderTargets to drop any non-drape render target it is the sole owner of (its bucket already evicted) as a backstop, and remove the now-unused RenderHillshadeLayer::addRenderTarget helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(terrain): fix hillshade render-target memory leak causing OOM
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Draped roads went blurry in the near field over a sparse DEM (e.g. mapterhorn):
high-zoom DEM tiles 404 and native falls back to a lower-zoom DEM tile, and the
terrain mesh/drape cover was derived from that DEM render set (expandToDeepestCover),
so it inherited the low zoom - the fixed-size drape target then spread over too
much ground and the vector roads rasterized into too few texels.
Replace expandToDeepestCover with RenderTerrain::computeMeshCover, which computes
the mesh/drape tile set directly from the transform via util::tileCover with the
DEM elevation provider and the frame's LOD params - an elevation-aware, LOD-based
ideal cover, the way every other source is covered and the way maplibre-gl-js's
coveringTiles({tileSize: 512, terrain}) does. The near field now covers at a
higher zoom (less ground per 1024x1024 target), and the DEM is decoupled: only
the DEM/drape textures fall back to ancestors per tile while exact tiles load, so
a sparse DEM costs slightly coarser elevation (smooth, invisible) rather than
coarser road rasterization.
Both the drape-target allocation (Renderer::Impl) and the mesh build
(RenderTerrain::update) call computeMeshCover with the same state/updateParameters,
so the target set and mesh set stay in lockstep; the now-redundant separate
frustum-cull is dropped (tileCover already frustum-limits, elevation included).
Verified on device (Android/OpenGL, TerrainVectorMapActivity over mapterhorn):
foreground roads markedly sharper, with residual stepping only at extreme close
zoom (inherent to render-to-texture draping). Doc: mark the "direct terrain tile
cover" convergence item done, close out the GL symbol-occlusion known issue
(confirmed working on device), and record the transient near/bottom frontier
skirt as a remaining item.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…skirt util::tileCover is a top-down DFS that only descends into tiles whose flat (z=0) ancestor intersects the frustum, so at steep pitch a frontier tile whose terrain rises into view - but whose flat footprint is outside the sea-level frustum - is never visited. It stays out of the cover, its DEM is never requested, and the terrain draws a skirt with nothing behind it until a pan shifts the frustum and pulls it in. On-device logging confirmed the cover is always fully meshed (noDrawable=0) but simply too small. RenderTerrain::computeMeshCover now dilates the ideal cover by one ring (all 8 neighbours) and trims it with util::frustumCull, using DEMElevationProvider's new conservative fallback range (the aggregate elevation of loaded DEM) for the not-yet-loaded neighbours. This re-tests the neighbours the DFS skipped and keeps those whose elevation-extended bounds intersect the frustum; the elevation sign decides which side survives, so it is correct for terrain above or below sea level without hardcoding a direction, and the frustum trim keeps it from tripling the cover. Drop the frustumCull line for a pure uniform 1-ring. Measured on Android/OpenGL (mapterhorn vector map, 60 deg pitch): at zoom 12 the skirt shrinks from ~40% of the frame to a thin sliver at +1-2 tiles of cover. This is a partial fix: at higher zoom over tall relief the gap is several tiles because the cover frustum is anchored at z=0 rather than on the terrain, and no ring count spans that - the residual is the terrain-anchored-camera work (Phase 4), now documented as such in TERRAIN.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tiled-GPU bandwidth optimization (biggest win on the PowerVR GE8320 this was profiled on): when a GL RenderPass finishes, tell the driver the pass's depth/stencil contents are no longer needed (glInvalidateFramebuffer), so it can skip storing them from tile memory back to main memory. Only applied to offscreen targets (terrain drape / depth / hillshade-prepare framebuffers), whose depth/stencil is transient and only their colour is sampled afterwards; the default framebuffer (id 0) is left alone because its depth is shared across the frame's passes (opaque -> translucent -> terrain surface). The RenderPass tracks whether the descriptor cleared depth/stencil and invalidates only those. Curated from #5 (GL terrain perf); one of the isolated, low-risk optimizations landed first. Co-Authored-By: yacloud-ing <yacloud.ing@yandex.ru> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Skips a terrain drape target's whole per-target coverage scan (not just the
re-render) when its content signature is unchanged - the other big FPS win in the
set, and the perf gap noted in TERRAIN.md ("drawable visits scale with targets x
drawables, run even on a cache hit").
renderer_impl computes, once per frame when terrain is active, a frame-global
draped-content signature and each drape target's own signature (an
order-independent sum of the covering-tile hashes of the draped drawables
overlapping it), folded with the draped group count and the INTEGER tile-zoom -
not the continuous zoom or the paint-property epoch. So pan / pitch / in-level
pinch reuse the baked drape textures and a target re-renders only when its
covering tiles or integer zoom actually change (matches maplibre-gl-js). A
matching draped-tweaker skip gate avoids recomputing camera UBOs that a cached
(camera-independent) drape would not use. RenderTarget::render compares its
target's signature against what it last baked; on a real change the signature
moves and it re-renders and re-runs the tweaker the same frame, so content never
freezes.
render() gains a defaulted canRerender=true param (the drape budget, not brought
here, is the only caller that passes false), so the cache runs without any
per-frame re-render cap. Integrated onto RenderTerrain::computeMeshCover; PR5's
Android-only profiling timers and FPS-overlay plumbing are omitted.
Curated from #5 (GL terrain perf).
Co-Authored-By: yacloud-ing <yacloud.ing@yandex.ru>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e interaction stalls Zoom-in / tilt / pan that reveals new tiles or changes terrain coverage stalled single frames for hundreds of ms: a burst of newly revealed tiles built all their drawables in one frame, and a burst of dirty drape targets re-rendered at once. Spread both across frames, matching maplibre-gl-js's progressive render cache. - Tile-build budget: gfx::Context caps how many NEW tiles construct their drawables per frame (kNewTileBuildBudgetPerFrame = 8), counted per tile (allowNewTileBuild, keyed on the tile id hash) so all of a tile's layers build together; fill/line defer excess new tiles. RenderOrchestrator::updateLayers resets the budget each frame. - Drape render budget: cap drape-target re-renders per frame (kMaxDrapeRerendersPerFrame = 4) via the render() canRerender path; an already- baked target that would re-render is deferred and keeps its (slightly stale) texture, while never-baked targets always render to avoid blank tiles. - Both request a follow-up frame (needsRepaint) so deferred tiles/drapes catch up after the interaction stops - tiles fill in progressively instead of stalling. PR5 measured this on the PowerVR GE8320: worst interaction frames 713ms/233ms -> mostly 8-40ms. Curated from #5; the fill/line update dirty-gate that rode alongside in the same commit is deliberately omitted (PR5's own history reverted it for leaking drawable GL memory). Co-Authored-By: yacloud-ing <yacloud.ing@yandex.ru> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nt upgrade Two device-driven refinements on top of the progressive budgets and the drape signature cache: - Raise the per-frame budgets: tile build 8 -> 32, drape re-render 4 -> 16. At 8/4 a fresh terrain view visibly assembled over several frames (low-detail ancestor terrain until it caught up). 32/16 lets a normal cover (~10-30 tiles) build in one or two frames - sharp initial view - while still capping the pathological 40+-tile bursts that caused the worst zoom-in stalls. These are the values a future "Balanced" preset would use. - Fold the source bucket's identity into each draped drawable's signature so an in-place content upgrade re-renders the drape. With the tile-build budget a tile can be draped first from an over-zoomed ancestor bucket, then its own native bucket loads under the same covering-tile id; keyed only on the covering tile, the signature did not change and the cache served the stale low-detail bake until the next zoom (an occasional single tile stuck low-quality among sharp neighbours). Keyed on the bucket id (stable across paint/fade, unlike the drawable id) it adds no per-frame churn. Hillshade is excluded: its drape samples a separate DEM "prepare" target with its own lifecycle, and folding its bucket broke it on initial load; it stays on the covering-tile signature that worked. Verified on device (Android/OpenGL, Innsbruck vector map): sharp initial view, smoother zoom-in, stuck-tile case resolved, hillshade correct on initial load. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reported on device: a fill-extrusion building behind a hill renders through it instead of being hidden. Recorded under Phase 3 as a depth-test/draw-order gap against the terrain surface (elevated building geometry not depth-testing against the nearer terrain mesh), distinct from the symbol depth-texture occlusion path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ed 512 RenderTerrain::computeMeshCover hardcoded a 512px terrain-cover tile size (gl-js's terrain-RTT convention). For a DEM source whose tiles are 256px, that meshes one zoom level *coarser* than the DEM actually loads, so the fixed 128x128 terrain mesh undersamples the DEM and aliases its relief into chaotic waves - visible on the 256px debug-tiles "terrain-ruffles" DEM, which the earlier expandToDeepestCover (mesh derived from the DEM's own render set) had sampled cleanly. Use the decoded DEM's own tile size (DEMData::dim, exact - the border is a separate stride) as the cover tile size, so the ideal-cover zoom matches the DEM's native granularity: 256px -> one zoom deeper, 512px (e.g. mapterhorn) unchanged. Falls back to 512 until the first DEM tile is decoded. The cover is still the elevation-aware ideal cover from the view, not the DEM's loaded tile set (so the sparse-DEM / draped-road-sharpness fix that motivated computeMeshCover is intact). Fixes a regression from the computeMeshCover rework (58847a1); verified on device (debug-tiles surface renders clean regular ripples again; mapterhorn roads unaffected). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…budget
The progressive tile-build and drape-render budgets trade initial-load sharpness
(and a little progressive fill-in) for smoother interaction on weak GPUs. That
trade is hardware-dependent, so expose it as a per-map API knob rather than a
hardcoded constant or a style property (it does not change the rendered image,
only how the loading work is spread across frames - so it is API, like
TileLodMode, not style-spec).
- mbgl::TerrainLoadMode { Quality, Balanced, Performance } + terrainLoadBudget()
mapping each to a (new-tile, drape-rerender) per-frame budget pair. Quality
(default) is unlimited = the historical sharp snap-load, so nothing regresses;
Balanced = 32/16, Performance = 8/4.
- Plumbed Map::setTerrainLoadMode/getTerrainLoadMode -> UpdateParameters ->
the two budget consumers (RenderOrchestrator new-tile budget, Renderer::Impl
drape budget), replacing the previously hardcoded caps.
- Android binding: MapLibreMap.setTerrainLoadMode / getTerrainLoadMode + the
TerrainLoadMode Java enum (ordinals synced to the native enum) over JNI.
- TerrainVectorMapActivity overflow menu switches modes and toggles terrain, so
the modes are testable without a new sample activity.
Documented in TERRAIN.md, along with a known pre-existing bug the terrain toggle
surfaced: runtime terrain enable/disable does not restore the draped fill/line
layers' render routing (only symbols/hillshade survive until the activity is
re-entered) - to be fixed separately. iOS/macOS binding not yet added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… FPS Terrain re-enable (setTerrain null -> non-null) left the map blank until the view was panned. On the first frame after re-enable, RenderTerrain::update - which resolves the DEM source RenderTerrain::computeMeshCover needs - has not run yet, so the drape cover is empty and no drape targets are created. With nothing else loading the map then idles blank. Two fixes in Renderer::Impl: - Request a bounded number of follow-up frames while an enabled terrain has an empty cover, so the next frame (DEM source now resolved) covers normally. - Force a drape re-bake on the frame the cover reappears: the draped drawables still hold screen-space UBOs from rendering to screen while terrain was off, so the tweakers must re-run before the fresh targets are baked, or the drape signature cache would serve stale (blank) content. Add a shared TerrainTestOptions menu to all five "3D Terrain" test activities: toggle terrain on/off, switch TerrainLoadMode, and toggle the built-in rendering-stats HUD. The controls are also driveable over adb (broadcast + launch extras) for on-device testing. The stats HUD gets an FPS line, its compass is moved aside so it does not cover the HUD, and it is pointed at a font the hosting style's glyph server actually serves (Open Sans, the SymbolLayer default, 404s on OpenFreeMap). The two inline-JSON styles and the inlined debug-tiles style gain a demotiles glyphs endpoint so the HUD can render there too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the "known bug - runtime terrain enable/disable loses the draped vector layers" note with the actual root cause and fix (empty drape cover on the first re-enable frame + the drape signature cache serving stale content), and update the test-menu note: all five 3D Terrain activities now share the adb-driveable TerrainTestOptions menu. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The load mode picked in a 3D terrain test's menu (or via adb) now persists in SharedPreferences and is restored on the next launch of any of the terrain tests, until changed again. Restore happens in onMapReady (after the Context is attached and the map is ready), then invalidateOptionsMenu refreshes the menu's checked state, since onCreateOptionsMenu runs before the async map callback. Prefs must not be touched in the constructor: the controller is built as an activity field initializer, before the base Context is attached, so the handle is lazy and only read from onMapReady onward. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, above-ground check Three additions to make terrain performance measurable on device: - Rendering-stats HUD: worst-frame time, jank count (frames slower than the jankThreshold), and peak single-frame encode time - the frame-time spikes an average FPS hides. A machine-parseable "PERF-HUD ..." log line per interval drives A/B benchmarks. - Zoom-burst driver in TerrainTestOptions (adb burst_on/off/toggle): an eased browsing-zoom loop that forces a tile-build + drape re-render burst on each arrival - the workload that actually makes the TerrainLoadMode budgets bind (smooth panning does not). A/B runs across the modes showed the budgets make no measurable difference on this (fast) device; the dominant per-frame cost is the zoom-transition work (cover recompute / symbol placement), which the budgets do not govern. - Above-ground margin check (RenderTerrain::logAboveGroundMargin): each frame, compares the camera eye's altitude to the rendered (exaggerated) terrain height under it and logs "ABOVE-GROUND ..." when within 1 km of / below the surface - quantifying when the sea-level-anchored camera dips underground (the FPV underground/white artifact, TERRAIN.md Phase 4). Groundwork + a measurable target for the future terrain-anchored camera. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rework the 3D-terrain FPV flight into a drone tour that starts low over Innsbruck (our default scene) and tours the wider Tyrol Alps. The sea-level- anchored camera (TERRAIN.md Phase 4) can otherwise pass through peaks; instead of a fixed zoom sweep, the zoom now follows a baked terrain-following profile: for each point along the loop the zoom is chosen so the eye's altitude clears the terrain (exaggeration applied) over the eye's rear footprint by a terrain-proportional clearance. Result: it dives close (zoom ~14.5, buildings visible) over low/dense terrain like the Innsbruck valley and pulls back to a wide overview (zoom 9) over the high peaks, staying above ground the whole loop (verified on device: 0 underground frames, min clearance ~630 m). Pitch is tied to the zoom: high (~74deg, toward the horizon/sky background) at the zoomed-out overview, looking down at terrain/buildings on the close passes; a slow heading rotation surveys the horizon at the high points. The profile was planned offline from real elevations (a tileserver-gl elevation endpoint) using a device-calibrated altitude<->zoom model; only the baked numbers ship in the app, no elevation URL. RenderTerrain's ABOVE-GROUND diagnostic log gains eye lng/lat, zoom, and pitch to calibrate and verify this. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…0 survey Refine the drone tour so the scenic beats last long enough to see and the whole zoom/altitude/pitch/rotation range is exercised: - The baked zoom profile now overshoots+clamps, so the sweep DWELLS at the wide overview and at the city instead of only touching them. - Pitch is nonlinear vs zoom: it stays moderate (safe altitude) on the mid/close passes and ramps to the max 85deg only at the deep, high-clearance overview - toward the horizon and the (currently black) sky/background beyond the terrain. This also fixes the underground frames that a linear pitch-to-85 caused. - A baked bearing-offset profile sweeps ~360deg during each overview dwell (gentle drift elsewhere; normalized to whole turns so it loops seamlessly), so the high-pitch overview surveys the full panorama. Still starts low over Innsbruck (buildings at zoom ~14.5), stays above ground the whole loop (verified: 0 underground frames), and needs no live elevation query (profiles planned offline). Note: at the max-pitch overview the terrain currently renders as the black background (it stops drawing at extreme pitch/high altitude, a separate rendering limitation this flight surfaces) - kept as an intentional black-sky beat before diving back to terrain. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nostics The RenderTerrain "ABOVE-GROUND ..." clearance log ran on every terrain frame whenever the camera was near the ground - noise for all terrain users, and the per-frame free-camera + elevation sampling had a cost. Put it behind a dedicated off-by-default switch: Map::setDebugAboveGroundLog -> JNI -> MapLibreMap.setDebugAboveGroundLog, exposed in the 3D terrain test menu as "Above-ground log" (and adb: abovelog_on/off/toggle). The gate is at the call site, so the whole check is skipped and costs nothing unless enabled. Also remove the two pre-existing "TEMP diagnostic" drape logs (DRAPE-REDRAW and the missing-layer warning) in render_target.cpp, plus the now-unused <android/log.h> and logging.hpp includes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve fill-extrusion conflicts between upstream's rounded-corner sub-pixel packing (decimals_ed / normal2d attributes) and our terrain elevation feature (per-polygon a_centroid + DEM UBO + get_elevation), keeping both: upstream's normal2d-based normals and decimals_ed packing plus our centroid-driven elevation raise with the gl-js basement drop. Applied across GLSL, WGSL and the C++ bucket/attribute/shader-info paths. GL (Android) compiles and assembles clean. Metal/Vulkan/WebGPU paths were resolved to the same pattern but are not buildable in this environment; generated shader headers were regenerated from the merged sources. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The upstream merge inserted normal2d into the non-instanced layout vertex (pos, decimals_ed, normal2d, centroid), shifting centroid from field a3 to a4, but the centroid attribute binding still read offsetof(..., a3) — i.e. the normal2d bytes. get_elevation then sampled the DEM at a bogus tile coordinate (0,0 for flat-top vertices), so building tops and walls floated at inconsistent, per-tile-uniform heights over the terrain. Point the centroid binding at a4 so each extrusion is raised by its true per-polygon terrain elevation again. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The six-defect occlusion fix (aaa145c) was hand-applied directly to the generated GL shader headers, not to the .glsl sources (which were left with the old drawable_pad4 / gl_Position / hard-depth-test code). When the upstream merge regenerated the GL headers from those stale .glsl, it silently reverted the occlusion fix: labels behind ridges reappeared. Restore the pre-merge (occlusion-correct) versions of the GL symbol and prelude headers. Upstream did not modify these shaders, so the pre-merge content is the correct merged result. get_elevation (needed by fill-extrusion) remains intact in the restored prelude. NOTE: the .glsl sources remain out of sync with these hand-tuned headers, so regenerating GL shaders will revert occlusion again until the fix is mirrored into the .glsl. Same applies to the Metal/Vulkan/WebGPU symbol headers, which carry the fix directly and were not rebuilt here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- context.hpp: use unordered_set::contains() instead of count() for the new-tile-build membership check (readability-container-contains was failing the -warnings-as-errors clang-tidy build on Linux opengl+vulkan). - clang-format (v20.1.8) the merge-resolved fill_extrusion_bucket.hpp, renderer_impl.cpp, and the generator-raw shader_source.cpp. - Add trailing newline to the two terrain render-test style.json (end-of-file-fixer). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- List all five "3D Terrain" test activities (add TerrainFlightActivity, the FPV drone-flight test). - Note the TerrainTestOptions menu also toggles the above-ground-margin debug log. - Testing: the five terrain render tests currently FAIL on Linux CI - the styles match gl-js byte-for-byte, but native's tile-cover requests overview/near tiles that are absent from the fixture DB (and from gl-js's own fixtures), a symptom of the Phase 3 tile-cover / Phase 4 camera work, not a baseline bug. Document the cache-style.db tile mechanism. - Fix the mesh vertex count (129x129 = 16,641, not 128x128). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
metrics/benchmarks/terrain-load-mode-bench.sh runs the FPV-flight activity once per TerrainLoadMode (quality/balanced/performance) over the same deterministic baked path with the stats HUD on, captures a clean steady-state window of the PERF-HUD log, and prints a per-mode summary (fps mean/p5/min, worstMs median/p95/max, jank-per-second, encode p95/max). Built for cross-device comparison of the load-mode budgets. Documented in TERRAIN.md (Testing) with the Adreno 750 baseline: Quality is smoothest on a fast GPU and the throttling modes add jank; the budgets target weaker GPUs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Builds on top of the current
feature/terrain-3d, merges the main branch, and continues adding additional updates to try and make it complete. The PR uses internal DEM decoding we already use for hillshade/terrain and makes features terrain aware. So far most of the testing has been in android/opengl, but i have tried to add the other renderers completely (though they are still very untested). Most of the changes are done by Claude Fable/Opus/Sonnet through comparison with gl-js and nitpicking the results.This is the AI description of the change made.
Draping
into per-tile targets draped over the terrain mesh, on all four backends
(OpenGL, Metal, Vulkan, WebGPU). Targets carry depth and stencil; every
overlapping source tile is drawn and the tile clipping masks resolve
parent/child overlap, so a parent tile stands in for unloaded children instead
of the target rendering black.
changes. Prepare passes (e.g. hillshade) run before the drapes that sample them.
Elevated layers
displaced by the terrain, on all four backends.
Tile cover
elevation, so terrain leaning toward the camera is requested. After
expandToDeepestCoverthe mesh is frustum-culled, so a sparse DEM's largelow-zoom tiles aren't meshed across their off-screen area.
Other
fallback, terrain root-property parsing (
source,exaggeration).terrain_depthshader header included where Metal/Vulkan/WebGPU register it.Remaining work and known issues are in
TERRAIN.md(mesh skirts, drape targetsize, camera-terrain anchoring, a zoom-0 exaggeration bug).
Diff size: against
mainthis is ~255 files. Againstfeature/terrain-3ditshows ~850 because it also brings that branch up to
main— the extra files anddeletions are the upstream catch-up, not terrain changes.