Terrain 3d color relief#4389
Draft
acalcutt wants to merge 121 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.
The terrain root property could previously only be configured through style JSON. Expose it on the Android SDK following the Light pattern: - org.maplibre.android.style.terrain.Terrain value class (sourceId, exaggeration) - Style.setTerrain(terrain)/getTerrain(), passing null to disable - JNI marshals the two values directly (nativeSetTerrain, nativeRemoveTerrain, nativeGetTerrainSourceId, nativeGetTerrainExaggeration); the core style API and the render orchestrator already handle runtime terrain changes The raster-dem source's TileJSON may carry the encoding field (e.g. Mapterhorn's terrarium), which the tileset parser picks up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… activity Rework the planet vector map example to add the Mapterhorn DEM sources, hillshade layer (inserted below the first symbol layer), and terrain via the new Style.setTerrain API instead of fetching and patching style JSON. Add a '3D Terrain (debug tiles)' activity loading the maplibre-gl-js terrain debug style (demotiles.maplibre.org/debug-tiles), which drapes numbered debug tiles over synthetic terrain - useful for comparing tile zoom variation and draping against the gl-js reference rendering. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The style spec and maplibre-gl-js call the property terrain.source, and core mbgl::style::Terrain exposes getSource()/setSource(). Align the Android Terrain class with both instead of introducing a third name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ats divergence Last night's emulator crashes proved the memTextures assert still fires on the instrumented build, but the forensic dump went to logcat, which did not survive the emulator reboot. On Android, abort through __android_log_assert so the dump becomes the abort message and persists in the tombstone and dropbox entry. Also compare memTextures against the pool's shadow counter on every allocate/free and log the first divergence: the pool is the sole writer of memTextures on the GL backend, so the first mismatch pinpoints whether the corruption is external or in the pool's own bookkeeping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… block The Android emulator logs 'ValidateProgramUniformBlockBindings: No bound buffer' + 'DrawElements: ValidateState() failed' and rejects the draw, silently dropping geometry (seen while testing terrain, where it can look like tiles loading inconsistently). ShaderProgramGL now keeps its uniform block list, and in debug builds DrawableGL::draw checks every block's binding point after binding UBOs, logging the drawable and block name once per pair so the offending layer can be identified. Also applies clang-format to the previous pool forensics commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Renderer::Impl::render binds the global UBOs after drawableTargetsPass,
so every draw issued inside a render target (the terrain draping path)
ran with nothing bound at the global binding points. Validating drivers
such as the Android emulator reject those draws ('DrawElements:
ValidateState() failed'), dropping draped lines and fill outlines, and
other drivers read undefined data. Metal/Vulkan/WebGPU bind globals per
render pass, so their render-target passes were equally affected.
Found via the debug uniform-block check: every draped translucent
drawable (fill-outline, line, lineSDF) reported GlobalPaintParamsUBO
unbound at binding 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drapes the openstreetmap.org raster basemap over Mapterhorn terrain with a hillshade layer, following the maplibre-gl-js 3D Terrain example style. Exercises raster layer draping, which the other terrain activities do not cover. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…form design Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…(GL) Draped drawables were routed into exactly one terrain render target (the deepest ancestor-or-descendant match), so a tile at a lower zoom than the terrain cover - a parent raster/vector tile standing in for unloaded children, or a 256px raster source whose pyramid runs one zoom deeper than the DEM - was drawn into a single arbitrary target while every other target it covers rendered the clear color, producing patchy, per-frame-changing drapes. Rework the drape pass to match gl-js render_to_texture: draped layer groups stay in the orchestrator, and each drape RenderTarget renders every draped drawable whose tile overlaps its target tile (enable- filtered, both passes, style order). The drape projection is orthographic, so placement into a zoom-mismatched target is an affine transform in NDC, applied in the vertex shader (apply_drape_transform in the prelude): the drawable's tile rides in the unused third column of its tile-local drape matrix, and the target tile is carried in a new GlobalPaintParamsUBO::drape_tile field via a per-target copy of the global paint params bound in the target's own render pass. One drawable can now record into every target it covers with no cloning and no per-frame allocations, and the old move-drawables-into-single-tile- layer-groups routing is deleted. Also stop giving terrain drape matrices to layers that are never draped (fill-extrusion, collision, heatmap), which previously picked them up via the default getTileMatrix argument whenever their tile happened to have a render target. GL only so far; Metal/Vulkan/WebGPU shaders keep compiling (a buffer larger than the declared uniform block is valid everywhere) and follow next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
-Wmissing-designated-field-initializers is an error on the Android build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…terrain logging The debug uniform-block check called glGetIntegeri_v per block per draw; on the Android emulator every glGet* is a synchronous round-trip to the host GPU process, and with the drape pass recording draped drawables into multiple render targets it dropped the frame rate to under 1 fps. It already served its purpose (identifying the unbound GlobalPaintParamsUBO in the render-target pass), so gate it behind MLN_VALIDATE_UNIFORM_BLOCK_BINDINGS. Also remove the per-frame Log::Info/Warning terrain debug logging (render_terrain, terrain_layer_tweaker, orchestrator DEM marking), which was on the TERRAIN.md cleanup list; each line is a binder transaction on Android. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rawing all of them The render tile set legitimately overlaps while tiles load (a parent standing in for missing children next to already-loaded children). The main passes resolve this with stencil clipping, but drape targets have no stencil attachment, so the blurry fallback parent drew over its sharp children and stayed there for as long as any child under it was missing - panning the vector map left the drape permanently blurred. Per (target, layer group), enable a single consistent coverage like the gl-js per-terrain-tile tile stack: exact matches if present, else the deepest covering ancestor, else descendants. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s degraded Panning briefly drops tiles from the render set (new edges load, LOD boundaries move), and the drape target would re-render from an ancestor fallback, visibly blurring areas whose full-detail rendering was on screen a frame earlier - and hillshade could disappear entirely while its two-phase prepare catches up. Track the coverage quality baked into each drape target (groups with content, zoom levels lost to ancestor fallbacks) and skip re-rendering when the currently available coverage is strictly worse: the texture keeps the sharper previous rendering until the tiles catch up. A change in the style's draped layer set forces a re-render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…arent fallbacks When the DEM cover fell back to a parent tile during panning, a parent mesh was created whose big low-resolution drape target drew over the sharp meshes of its already-loaded children - a slight pan degraded the whole scene, and where the parent's drape had only partial layer coverage the rest showed as the clear color (the large dark region seen over Hall in Tirol). Like maplibre-gl-js, terrain mesh tiles now always stay at the ideal cover: parent fallback render tiles are expanded into their ideal-zoom children (RenderTerrain::expandToDeepestCover), each with its own drape render target, and only the DEM textures fall back to the closest cached ancestor (via the existing dem_coords machinery) while tiles load. DEM texture decoding is done once per loaded render tile up front; mesh drawables and drape targets are keyed by the expanded set in both RenderTerrain::update and Renderer::Impl. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nd performance target The convergence items are explicitly marked ask-before-doing: the native mechanisms work and may be the better fit for the drawable architecture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The persisted abort message from the emulator finally explained the Texture2DPool 'memTextures >= 0' crashes: all pool-internal counters agreed at 2,241,014,164 bytes (2204 live 514x514 DEM textures, 0 reuses, 73 freed) - the pool bookkeeping was always consistent, but RenderingStats::memTextures is a 32-bit int and wrapped negative past 2GB, tripping the assert. The real defect is RenderTerrain::demTextures growing without bound: ancestor/descendant retention keeps whole zoom chains alive while browsing, eventually OOMing the process (the earlier silent app deaths). - Track lastUsed on each cached DEM texture and evict least-recently- used entries not referenced in the current frame beyond a 96-texture budget (~100MB) - Widen RenderingStats::memTextures to int64_t so the stat cannot wrap (and the pool assert is a genuine corruption detector again) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t type memTextures is int64_t now, so the deduced list type conflicted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-less mesh tiles Tiles are confirmed loading at the HTTP level while large dark regions persist in the drape; these temporary logs name the affected targets (coverage per target) and the terrain state (render tile zoom histogram, mesh/texture/drawable counts) once per ~120 frames. Remove before merging. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The coverage metric counts layer groups with drawables overlapping the target, which cannot distinguish 'layer has no features in this tile' from 'tile not loaded yet'. A target baked during load/churn with transiently inflated coverage could then be skipped indefinitely (current honest coverage always compared worse), locking in stale or near-empty content - seen as persistent dark tiles. Keep degraded- coverage skipping for 2 seconds after a bake, then render whatever is actually available. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e by DEM tier Mesh tiles with no DEM (own or ancestor) were skipped entirely, leaving holes where the screen clear shows through (white areas at the near edge while panning). Render them flat with the 1x1 placeholder DEM so the draped map content still appears, and track a per-tile DEM quality tier (placeholder < ancestor < own) so drawables upgrade as soon as any better DEM becomes available (previously an ancestor arriving after a placeholder did not replace it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Metal: drape_tile added to the GlobalPaintParamsUBO prelude struct with the shared MSL helper; draped vertex functions that lacked the global paint params argument gained it, and every draped position computation goes through apply_drape_transform. Metal binds global buffers per encoder, so the per-target GlobalPaintParams copy works as on GL. Vulkan: the global descriptor set is allocated once per frame-in-flight and guarded by a dirty flag, so swapping the global buffer per drape target would be silently ignored (and rewriting a recorded set is invalid). The target tile instead travels in the push constants next to the consolidation ubo_index (range widened to 32 bytes), fed from the new PaintParameters::currentDrapeTile at draw-record time. Also stop casting offscreen renderables to SurfaceRenderableResource in bindGlobalUniformBuffers (drape targets are not surfaces). WebGPU is documented as pending: no push constants and cached bind groups mean neither channel reaches per-target data; exact-match drapes render correctly there in the meantime. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same per-target GlobalPaintParams mechanism as GL and Metal: the WGSL shaders gain the drape_tile field (and raster/hillshade/color-relief gain the group(0) binding(0) paintParams declaration, which the source-scanning bind group layout reflection picks up), the shared helper lives in the WGSL prelude, and every draped position computation goes through apply_drape_transform. This works because drawables rebuild their bind groups on every draw, so the per-pass global buffer swap is picked up naturally; if bind-group caching is introduced later, drape targets will need per-target bind groups or dynamic offsets. The drape routing rework is now implemented on all four backends. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Following gl-js: the terrain meshes are rendered once per frame with a new TerrainDepthShader into a viewport-sized render target, packing gl_FragCoord.z into RGBA8 (terrain_depth.fragment.glsl, same packing as the gl-js terrain_depth shader), cleared to the far plane. The symbol vertex shaders unpack it in the new calculate_visibility() prelude helper and zero their fade opacity when the symbol's clip position is behind the terrain, so labels no longer show through mountains. - RenderTerrain keeps a depth twin of each mesh drawable in a separate layer group (not in the orchestrator), rendered by renderDepth() after the drape target pass; the existing terrain tweaker fills both groups' UBOs - getDepthTexture() hands symbol tweakers the packed depth texture, or a 1x1 far-plane placeholder (symbols visible) when the pass has not run - also the graceful path on backends without the depth shader yet - Symbol drawables bind it at the new idSymbolDepthTexture slot; the visibility test reuses the dem_enabled flag (occlusion is exactly terrain-enabled) GL only in this commit; Metal/Vulkan/WebGPU keep rendering symbols unoccluded via the placeholder and follow next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The static member definitions in shader_info.cpp need the explicit specialization declared in the header. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ng main The merge from origin/main (07f8f33) recorded the pre-merge submodule commit instead of main's updated one, which enables FastPFOR encodings in MLT and is what the newly merged issue12432 test fixtures depend on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shader code generator emits MBGL_DEFINE_ENUM in a flat, unindented form; the repo commits these generated files clang-formatted (main's version is), but the Phase 2 commit that added TerrainDepthShader to the enum did not run the post-generation clang-format pass. No content change, only the BuiltIn enum block reindented to match clang-format. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…main Reverts the erroneous bump in 4c28990. origin/main records d457bf2b for this submodule; I had mis-read a stale working-tree checkout and advanced it to f493beb6, a newer mlt-cpp whose API diverges from the code main ships (Decoder lost its bool ctor, Feature::getID returns a plain id_t instead of std::optional), breaking the arm64 compile of src/mbgl/tile/vector_mlt_tile_data.cpp. d457bf2b also vendors FastPFOR as a plain static lib with no SIMDe FetchContent, so it does not hit the duplicate-simde-target CMake error on aarch64 that f493beb6 does - no FastPFOR workaround needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diagnostics showed drape targets re-rendering with 0 of N draped layers (dark, over the clear color) and then recovering - the correct->dark-> correct flicker. Two flaws in the keep-baked-content heuristic let the regression through: the skip required the draped-group count to be unchanged, but that count wiggles frame to frame while updateLayers recreates groups during loading (observed 80->82->83->80), bypassing the skip; and a 2-second time bound let the dark re-render through whenever a tile's content gap outlasted it. Replace both with a single rule: keep the previously baked texture whenever the currently available coverage is strictly worse (fewer layers with content, or coarser ancestor fallbacks), re-rendering only when coverage is equal or better. A target never regresses to less than it already shows; its own lifetime bounds staleness (when the terrain tile leaves the cover the target is destroyed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RenderingStats::memTextures is now int64_t; the render-test GfxProbe tracks memory as int, so std::max(stats.memTextures, prev...peak) mixed int64_t and int and failed to deduce. Cast to int at the call site (render tests never approach 2GB). Co-Authored-By: Claude Fable 5 <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.
Merge current main into Terrain 3d and try to update it to support color-relief.