Skip to content

BE-Community-Dev/bedrock-render

Repository files navigation

bedrock-render

English | 简体中文

bedrock-render is a tile renderer for Minecraft Bedrock worlds. It depends on bedrock-world for Bedrock LevelDB, NBT, chunk, subchunk, height, and biome queries. Rendering, palettes, tile planning, image encoding, cancellation, threading, previews, and benchmarks live in this separate crate so the world parser remains lightweight.

This repository is designed to be checked out independently. The current MSRV is Rust 1.88, and the default feature set includes async, webp, and gpu. CPU-only consumers can build with --no-default-features and opt into the image formats they need.

Design

  • Rendering is always an X/Z plane. Y is only a sampling parameter.
  • Tiles are controlled by RenderLayout. The default layout is 16x16 chunks per tile, one block per pixel, producing a 256x256 image. Larger blocks_per_pixel values keep the same world area but render lower-detail images for fast large-world previews.
  • Arbitrary rectangular chunk regions are supported through ChunkRegion.
  • Web-map paths are deterministic and cache-safe: <world>/<signature>/r<renderer>-p<palette>/<dimension>/<mode>/<layout>/<tile_x>/<tile_z>.<ext>.
  • Tile batches use bounded worker threads. Auto resolves from the selected execution profile: Export uses host logical CPUs, Interactive caps work to a smaller foreground-safe pool. Explicit thread counts support 1..=512.
  • Web-map export uses a global chunk-bake queue, per-wave dynamic memory budget, parallel tile compose/encode, and a bounded MPMC writer queue so CPU workers are not serialized behind fs::write.
  • Interactive frontends should create one MapRenderSession per opened world. A session holds the renderer, tile cache, and diagnostics context so panning and zooming do not reopen the world or rebuild cache state for every batch.
  • RenderMemoryBudget::Auto uses a bounded cache budget for chunk bakes and export waves. FixedBytes and Disabled are available for offline tooling.
  • Long operations support explicit cancellation and progress callbacks.

Render Modes

  • RenderMode::Biome { y }: biome color map sampled at the requested Y layer.
  • RenderMode::RawBiomeLayer { y }: diagnostic biome-id color map.
  • RenderMode::LayerBlocks { y }: fixed block layer map at world Y.
  • RenderMode::SurfaceBlocks: main top-down terrain map. Each X/Z column starts from the Bedrock height map, scans down to the highest renderable block, applies biome tint, and blends transparent water over the solid block below.
  • RenderMode::HeightMap: height gradient from Bedrock Data2D/Data3D records.
  • RenderMode::CaveSlice { y }: fixed Y cave diagnostic map for air/solid/water/lava.

SurfaceBlocks follows the same core data flow used by BedrockMap's terrain bake path: use chunk.get_height(x,z), scan downward in that column until a renderable block is found, then bake terrain/biome/height data. The default terrain preview applies lightweight height-normal shading so slopes remain visible; the height map remains a separate diagnostic image. Missing chunk or missing height records are treated as absent terrain, not as gray map pixels. Fixed Y rendering remains available through LayerBlocks { y }. Unknown blocks are rendered as opaque purple diagnostic pixels. Missing chunks, missing height maps, and empty terrain are transparent and counted separately in RenderDiagnostics.

API Sketch

use std::sync::Arc;
use bedrock_render::{
    ChunkRegion, ImageFormat, MapRenderer, RenderExecutionProfile, RenderLayout,
    RenderMemoryBudget, RenderMode, RenderOptions, RenderPalette, RenderThreadingOptions,
};

let renderer = MapRenderer::new(Arc::new(world), RenderPalette::default());
let region = ChunkRegion::new(dimension, -32, -32, 31, 31);
let layout = RenderLayout {
    chunks_per_tile: 16,
    blocks_per_pixel: 1,
    pixels_per_block: 1,
};

let tiles = renderer.render_region_tiles_blocking(
    region,
    RenderMode::HeightMap,
    layout,
    RenderOptions {
        format: ImageFormat::WebP,
        threading: RenderThreadingOptions::Auto,
        execution_profile: RenderExecutionProfile::Export,
        memory_budget: RenderMemoryBudget::Auto,
        ..RenderOptions::default()
    },
)?;

Streaming Session API

MapRenderSession is the high-performance path for GPUI and other interactive map viewers. It streams tile events as work completes, reuses the world handle and tile cache, supports cancellation, and can cull missing chunks with the new bedrock-world render-index APIs before baking.

use std::sync::Arc;
use bedrock_render::{
    ChunkRegion, ImageFormat, MapRenderSession, MapRenderSessionConfig,
    MapRenderer, RenderCachePolicy, RenderCancelFlag, RenderExecutionProfile,
    RenderLayout, RenderMode, RenderOptions, RenderPalette, RenderTilePriority,
    TileStreamEvent,
};

let renderer = MapRenderer::new(Arc::new(world), RenderPalette::default());
let session = MapRenderSession::new(
    renderer,
    MapRenderSessionConfig {
        cache_root: "target/bedrock-render-cache".into(),
        world_id: "viewer".into(),
        world_signature: "leveldb-manifest-and-leveldat-signature".into(),
        cull_missing_chunks: true,
        ..MapRenderSessionConfig::default()
    },
);

let layout = RenderLayout::default();
let region = ChunkRegion::new(dimension, -32, -32, 31, 31);
let planned_tiles = MapRenderer::plan_region_tiles(region, RenderMode::SurfaceBlocks, layout)?;
let cancel = RenderCancelFlag::new();

session.render_web_tiles_streaming_blocking(
    &planned_tiles,
    RenderOptions {
        format: ImageFormat::WebP,
        execution_profile: RenderExecutionProfile::Interactive,
        cache_policy: RenderCachePolicy::Use,
        cancel: Some(cancel.clone()),
        priority: RenderTilePriority::DistanceFrom { tile_x: 0, tile_z: 0 },
        ..RenderOptions::default()
    },
    |event| {
        match event {
            TileStreamEvent::Cached { planned, encoded } => {
                // Decode or hand encoded bytes to the UI image cache.
            }
            TileStreamEvent::Rendered { planned, tile } => {
                // Present tile.rgba immediately and keep tile.encoded for cache.
            }
            TileStreamEvent::Failed { planned, error } => {
                eprintln!("tile failed: {error}");
            }
            TileStreamEvent::Progress(progress) => {
                eprintln!("tiles {}/{}", progress.completed_tiles, progress.total_tiles);
            }
            TileStreamEvent::Complete { diagnostics, stats } => {
                eprintln!("cache hits={} gpu={:?}", stats.cache_hits, stats.resolved_backend);
            }
        }
        Ok(())
    },
)?;

Migration: blocking batch to cancellable streaming

Legacy callers that used render_web_tiles_blocking usually waited for an entire batch before updating UI:

renderer.render_web_tiles_blocking(&planned_tiles, options, |planned, tile| {
    write_tile(planned, tile)?;
    Ok(())
})?;

Prefer a long-lived session and stream tile events into the frontend:

let session = MapRenderSession::new(renderer, MapRenderSessionConfig::default());
let cancel = RenderCancelFlag::new();
session.render_web_tiles_streaming_blocking(
    &planned_tiles,
    RenderOptions {
        cancel: Some(cancel),
        cache_policy: RenderCachePolicy::Use,
        ..options
    },
    |event| {
        enqueue_tile_event(event)?;
        Ok(())
    },
)?;

The old batch APIs remain available for export tools, but new interactive code should treat the session streaming API as the primary entry point.

The render_streaming_session example demonstrates the same event flow against a local world:

cargo run --example render_streaming_session -- <world_path>

Preview Tool

The preview example generates six atlas PNGs plus web-map tile folders:

cargo run --example render_preview --features png

Optional arguments:

render_preview <world_path> <output_dir> <center_tile_x> <center_tile_z> \
  <viewport_tiles> <layer_y> <cave_y> <chunks_per_tile>

Preview output layout:

<output_dir>/
  biome-viewport.png
  raw-biome-viewport.png
  layer-y64-viewport.png
  surface-viewport.png
  heightmap-viewport.png
  cave-y32-viewport.png
  web-tiles/sample/signature/r2-p1/overworld/heightmap/16c-1bpp/21/12.png

Web Map Export

render_web_map exports WebP tiles plus a self-contained static HTML viewer. It is intended for validating the renderer and for generating shareable web-map artifacts without requiring GPUI or a CDN. When --region is omitted, the example discovers and renders the loaded chunk bounds for each selected dimension.

cargo run --example render_web_map -- \
  --world ../bedrock-world/tests/fixtures/sample-bedrock-world \
  --out target/bedrock-web-map \
  --dimensions overworld,nether,end \
  --mode surface,heightmap,biome,layer \
  --chunks-per-tile 16 \
  --chunks-per-region 32 \
  --blocks-per-pixel 4 \
  --threads auto \
  --profile export \
  --memory-budget auto \
  --pipeline-depth 256 \
  --tile-batch-size auto \
  --writer-threads 2 \
  --write-queue-capacity 256 \
  --stats \
  --force

Output layout:

target/bedrock-web-map/
  viewer.html
  map-layout.json
  map-data.js
  tiles/<dimension>/<mode>/<layout>/<tile_z>/<tile_x>.webp

map-layout.json is the automatically generated map layout for tools and external frontends. map-data.js embeds only the minimal layout constants and dynamically provides tileBounds(), tileId(), and tilePath(). Tile locations are derived from the fixed tiles/<dimension>/<mode>/<layout>/<tile_z>/<tile_x>.webp rule, so the HTML viewer can load the data as a normal script from file:// without using fetch() or hitting browser CORS restrictions. Export writes viewer.html, map-layout.json, and map-data.js before opening and scanning the world; as dimension bounds are discovered and each mode finishes, map-layout.json and map-data.js are refreshed with updated layout and stats. The viewer periodically reloads map-data.js and retries visible tiles, so images appear incrementally while export is still running. It supports dimension switching, mode switching, drag panning, wheel zoom, tile coordinates, and load-error reporting. For large worlds, use --region for a bounded export or increase --blocks-per-pixel before exporting the whole loaded chunk bounds. --profile export --threads auto is the recommended default for 8-core/16-thread machines. Use --profile interactive for UI-like previews, and use a fixed thread value up to 512 only for offline exports where the storage device can keep up. --stats prints planned tiles, unique chunks, baked chunks, bake/encode/write time, cache hits, and peak cache bytes so CPU under-utilization can be traced to I/O, encoding, or chunk baking. Missing chunks, missing subchunks, and unloaded fixed-Y areas are transparent. Opaque purple pixels indicate an actual block name that was read from the world but had no palette mapping.

External Palettes

The default palette is embedded in the crate and in compiled binaries. It ships with two auditable JSON sources:

data/colors/bedrock-block-color.json
data/colors/bedrock-biome-color.json

RenderPalette::default() builds from the embedded JSON sources, so normal rendering does not require external palette files. The JSON files remain available through RenderPalette::builtin_block_color_json() and RenderPalette::builtin_biome_color_json() for auditing and tooling. The built-in data is maintained inside this project for Bedrock rendering; the loader also understands legacy object-map palette JSON so applications can import their own licensed color data without depending on another renderer.

For projects that have their own licensed color data, RenderPalette also supports additional user-provided JSON:

--palette-json target/bedrock-block-color.json
--palette-json target/bedrock-biome-color.json

JSON is an import/override format. Tintable blocks use mask colors in the block source and receive their final render color from biome tint data. The JSON loader accepts combined objects with schema_version / sources / blocks / defaults / biomes, object maps such as {"minecraft:stone":"#7d7d7d"}, arrays with name / id plus color, and legacy multi-texture object-map shapes for local user-provided reference data.

Palette source maintenance commands:

cargo run --example palette_tool -- audit --check
cargo run --example palette_tool -- generate-clean-room --check
cargo run --example palette_tool -- normalize --check

Source policy and public references are documented in docs/PALETTE_SOURCES.md.

Latest local reference-palette smoke run:

cargo run --example render_web_map -- \
  --world ../bedrock-world/tests/fixtures/sample-bedrock-world \
  --out target/bedrock-web-map-ref-palette \
  --region 0,0,15,15 \
  --mode surface,heightmap,biome,layer \
  --y 64 \
  --palette-json target/bedrock-block-color.json \
  --palette-json target/bedrock-biome-color.json \
  --force

loaded palette JSON target\bedrock-block-color.json: block_colors=1211 biome_colors=0 skipped=0
loaded palette JSON target\bedrock-biome-color.json: block_colors=0 biome_colors=88 skipped=0
overworld surface tiles=1 missing=0 transparent=0 unknown=0
overworld layer-y64 tiles=1 missing=0 transparent=12544 unknown=0

Latest local debug preview run:

cargo run --example render_preview --features png
surface diagnostics: missing_chunks=0 missing_heightmaps=0 unknown_blocks=0 fallback_pixels=0
preview output: target/bedrock-render-preview

Latest local WebP web-map smoke run:

cargo run --example render_web_map -- \
  --world ../bedrock-world/tests/fixtures/sample-bedrock-world \
  --out target/bedrock-web-map \
  --region 0,0,15,15 \
  --mode surface,heightmap,biome,layer \
  --threads auto \
  --tile-batch-size auto \
  --writer-threads 2 \
  --write-queue-capacity 64 \
  --force

Generated viewer.html, map-layout.json, map-data.js, and WebP tiles for overworld/nether/end.
LayerBlocks transparent pixels are expected for unloaded fixed-Y areas.

Rendered Examples

These images are generated by the preview tool with the current default palette. The terrain view references BedrockMap-style top-down map behavior while remaining part of this standalone public renderer.

Biome { y }

Biome colors sampled on an X/Z plane at the configured Y layer.

Biome map

RawBiomeLayer { y }

Diagnostic biome-id map. Unknown or sparse ids are intentionally visible.

Raw biome map

LayerBlocks { y }

Fixed world-Y block layer rendered as an X/Z plane. This is not a side section.

Fixed Y layer map

SurfaceBlocks

Primary top-down terrain map. Each X/Z column uses the chunk height map, scans down to the highest renderable block, applies biome tint, and blends transparent water with the block below. Lightweight height-normal shading is enabled by default so terrain does not look completely flat. SurfaceRenderOptions::block_boundaries adds a subtle 2D per-block outline and height-contact shadow so cliffs, paths, and same-color blocks remain readable without switching to a 2.5D view. Use HeightMap for elevation analysis.

Surface block map

HeightMap

Height gradient derived from Bedrock Data2D/Data3D height records.

Height map

CaveSlice { y }

Fixed Y cave diagnostic map for air, solid blocks, water, and lava.

Cave slice map

Performance Model

  • Biome, RawBiomeLayer, LayerBlocks, HeightMap, and CaveSlice prefetch only the chunk records needed by the tile.
  • Height data is fetched once per chunk through bedrock-world and cached as a compact 16x16 height array inside the tile context.
  • SurfaceBlocks does not scan missing-height columns from world top to bottom. Missing chunk/height data is counted in diagnostics and emitted as absent terrain.
  • Subchunk access uses SubChunk::block_state_at(local_x, local_y, local_z) so renderer code does not duplicate palette index math.
  • Web export uses a region-first bake pipeline. --chunks-per-region controls the bake cache unit; 32 is the default for large exports, while 16 is better for small bounded validation runs or lower interactive latency.
  • Web export does not keep every baked region for the full map resident at once. The renderer splits baking, composition, and writing into waves bounded by --memory-budget. --pipeline-depth and --write-queue-capacity only bound encoded tiles waiting for disk writes.
  • SurfaceBlocks uses chunk bake: each chunk is reduced to a 16x16 terrain image first, then region planes and WebP tiles are assembled from baked chunk pixels.
  • The static viewer uses RenderLayout auto-scaling. Small worlds default to full detail; larger worlds can use 2/4/8 blocks per pixel without changing the visible tile coverage.
  • Cache keys include world path hash, world file signature, renderer version, palette version, dimension, render mode, Y layer, and layout. All-transparent stale cached tiles are rejected by the UI and regenerated.
  • ImageFormat::Rgba is the lowest-latency UI path. WebP/PNG are intended for cache/export/preview paths.
  • RenderOptions::gpu controls GPU compose scheduling. max_in_flight=0, batch_size=0, batch_pixels=0, submit_workers=0, readback_workers=0, buffer_pool_bytes=0, and staging_pool_bytes=0 use profile-aware defaults; export workloads allow more in-flight GPU work than interactive workloads.
  • RenderOptions::cpu controls bounded CPU queue depth and chunk batch sizing, while RenderOptions::priority lets interactive sessions render the current viewport center before distant tiles.
  • GPU compose runs with bounded concurrent in-flight jobs on one wgpu device. Failed GPU tiles fall back to CPU and report gpu_fallback_reason; stats also expose GPU batch tiles, submit/readback workers, and buffer/staging pool reuse.
  • The static web-map example writes WebP tiles under tiles/<dimension>/<mode>/<layout>/<tile_z>/<tile_x>.webp; it does not leave gaps between tiles and uses transparent pixels for absent world data.
  • Viewer integrations can start from the world's level.dat spawn point, support dimension/custom-dimension switching, and store WebP web-map tiles under an application cache directory.

Tests and Benchmarks

cargo fmt --all -- --check
cargo clippy --all-features --all-targets -- -D warnings
cargo test --all-features
cargo test --no-default-features
cargo doc --no-deps --all-features
cargo rustdoc --lib --all-features -- -D missing_docs
cargo bench --bench render --all-features

The default benchmark suite measures tile rendering, chunk baking, and small batch behavior. Full web-map export benchmarks are opt-in:

$env:BEDROCK_RENDER_FULL_BENCH='1'
cargo bench --bench render --all-features
Remove-Item Env:\BEDROCK_RENDER_FULL_BENCH

More details are in docs/API.md, docs/TESTING.md, and docs/BENCHMARKS.md.

Current Limits

  • Surface rendering is top-down and BedrockMap-style. Web-map export now uses a global chunk-bake queue and bounded GPU compose queue, but cross-process persistent chunk-bake reuse is still a future optimization.
  • V1 does not implement entity markers or labels.

About

该项目从BMCBL独立出来维护

Resources

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENSE-APACHE
MIT
LICENSE-MIT

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages