mirror of
https://github.com/maplibre/maplibre-rs.git
synced 2025-12-08 19:05:57 +00:00
Introduce Plugin API along with Raster Tiles (#253)
* Implement basic raster rendering * Fix web builds for raster rendering (e.g. APC) * Implement plugin system using a "Tile Component System" pattern heavily inspired by bevy * Remove tile_repository and integrate into world structures * Migrate from specific stages to systems * Introduce a RasterPlugin and VectorPlugin * Add feature gate for headless rendering to demo * Remove pipelines for simplicity * Add message tags using Labels * Add run config * Add show bash path for Windows CI * Do not use symlinks for cargo binstall * Introduce DebugPlugin * Introduce HeadlessPlugin * Add more layers to vector processing benchmark --------- Co-authored-by: Quillasp <soulaymane.lamrani@heig-vd.ch> Co-authored-by: Antoine Drabble <antoine.drabble@gmail.com>
This commit is contained in:
parent
f57ddef09f
commit
63da707210
12
.github/actions/cargo-install/action.yml
vendored
12
.github/actions/cargo-install/action.yml
vendored
@ -12,7 +12,7 @@ runs:
|
||||
- name: Install binstall
|
||||
run: |
|
||||
if command -v cargo-binstall &> /dev/null; then
|
||||
echo "binstall already found"
|
||||
echo "binstall already found at $(command -v cargo-binstall)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@ -61,9 +61,13 @@ runs:
|
||||
echo "Skipping binary install"
|
||||
fi
|
||||
|
||||
# Upgrade
|
||||
cargo binstall --no-confirm --force cargo-binstall
|
||||
# Upgrade binstall to latest version
|
||||
# Do not use symlinks because they behave weird on Windows(+Bash)
|
||||
cargo binstall --no-confirm --force --no-symlinks cargo-binstall
|
||||
# Use latest version to reinstall binstall
|
||||
cargo binstall --no-confirm --force --no-symlinks cargo-binstall
|
||||
shell: bash
|
||||
- name: Install ${{ inputs.name }}
|
||||
shell: bash
|
||||
run: cargo binstall --no-confirm --force ${{ inputs.name }}
|
||||
# Do not use symlinks because they behave weird on Windows(+Bash)
|
||||
run: cargo binstall --no-confirm --force --no-symlinks ${{ inputs.name }}
|
||||
|
||||
3
.github/workflows/demo-windows.yml
vendored
3
.github/workflows/demo-windows.yml
vendored
@ -19,6 +19,9 @@ jobs:
|
||||
- name: Show PATH
|
||||
shell: powershell
|
||||
run: Write-Host $env:path
|
||||
- name: Show bash PATH
|
||||
shell: bash
|
||||
run: echo $PATH
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,5 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
@ -28,4 +27,4 @@ perf.data*
|
||||
*flamegraph.svg
|
||||
*flamechart.svg
|
||||
|
||||
frame_*.png
|
||||
frame_*.png
|
||||
|
||||
19
.idea/runConfigurations/Build_maplibre.xml
generated
Normal file
19
.idea/runConfigurations/Build_maplibre.xml
generated
Normal file
@ -0,0 +1,19 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Build maplibre" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="command" value="build --package maplibre" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
||||
<option name="allFeatures" value="false" />
|
||||
<option name="emulateTerminal" value="false" />
|
||||
<option name="withSudo" value="false" />
|
||||
<option name="buildTarget" value="REMOTE" />
|
||||
<option name="backtrace" value="SHORT" />
|
||||
<envs />
|
||||
<option name="isRedirectInput" value="false" />
|
||||
<option name="redirectInputPath" value="" />
|
||||
<method v="2">
|
||||
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@ -1,6 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Run headless demo (debug+enable-tracing) " type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="command" value="run -p maplibre-demo --features trace -- headless" />
|
||||
<option name="command" value="run -p maplibre-demo --features trace,headless -- headless" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
||||
|
||||
@ -4,79 +4,55 @@ use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use maplibre::{
|
||||
benchmarking::io::static_tile_fetcher::StaticTileFetcher,
|
||||
coords::{TileCoords, ZoomLevel},
|
||||
io::{
|
||||
pipeline::{PipelineContext, PipelineProcessor, Processable},
|
||||
tile_pipelines::{ParseTile, TessellateLayer},
|
||||
TileRequest,
|
||||
},
|
||||
io::apc::{Context, IntoMessage, SendError},
|
||||
style::source::TileAddressingScheme,
|
||||
vector::{
|
||||
process_vector_tile, DefaultVectorTransferables, ProcessVectorContext, VectorTileRequest,
|
||||
},
|
||||
};
|
||||
|
||||
// https://tile.openstreetmap.org/15/17425/11365.png
|
||||
const MUNICH_COORDS: TileCoords = TileCoords {
|
||||
x: 17425,
|
||||
y: 11365,
|
||||
z: ZoomLevel::new(15u8),
|
||||
};
|
||||
|
||||
pub struct DummyPipelineProcessor;
|
||||
pub struct DummyContext;
|
||||
|
||||
impl PipelineProcessor for DummyPipelineProcessor {}
|
||||
impl Context for DummyContext {
|
||||
fn send<T: IntoMessage>(&self, _message: T) -> Result<(), SendError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_tile(c: &mut Criterion) {
|
||||
fn bench_process_vector_tile(c: &mut Criterion) {
|
||||
let fetcher = StaticTileFetcher::new();
|
||||
|
||||
c.bench_function("parse", |b| {
|
||||
c.bench_function("process_vector_tile", |b| {
|
||||
let data = fetcher
|
||||
.sync_fetch_tile(&MUNICH_COORDS)
|
||||
.unwrap()
|
||||
.into_boxed_slice();
|
||||
|
||||
b.iter(|| {
|
||||
let request = TileRequest {
|
||||
coords: MUNICH_COORDS
|
||||
.into_world_tile(TileAddressingScheme::XYZ)
|
||||
.unwrap(),
|
||||
layers: HashSet::from(["boundary".to_owned(), "water".to_owned()]),
|
||||
};
|
||||
let data = fetcher
|
||||
.sync_fetch_tile(&MUNICH_COORDS)
|
||||
.unwrap()
|
||||
.into_boxed_slice();
|
||||
ParseTile::default()
|
||||
.process(
|
||||
(request, data),
|
||||
&mut PipelineContext::new(DummyPipelineProcessor),
|
||||
)
|
||||
.unwrap();
|
||||
let _ = process_vector_tile(
|
||||
&data,
|
||||
VectorTileRequest {
|
||||
coords: MUNICH_COORDS
|
||||
.into_world_tile(TileAddressingScheme::XYZ)
|
||||
.unwrap(),
|
||||
layers: HashSet::from([
|
||||
"transportation".to_owned(),
|
||||
"water".to_owned(),
|
||||
"building".to_owned(),
|
||||
]),
|
||||
},
|
||||
&mut ProcessVectorContext::<DefaultVectorTransferables, _>::new(DummyContext),
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn tessellate_tile(c: &mut Criterion) {
|
||||
let fetcher = StaticTileFetcher::new();
|
||||
let request = TileRequest {
|
||||
coords: MUNICH_COORDS
|
||||
.into_world_tile(TileAddressingScheme::XYZ)
|
||||
.unwrap(),
|
||||
layers: HashSet::from(["boundary".to_owned(), "water".to_owned()]),
|
||||
};
|
||||
let data = fetcher
|
||||
.sync_fetch_tile(&MUNICH_COORDS)
|
||||
.unwrap()
|
||||
.into_boxed_slice();
|
||||
let parsed = ParseTile::default()
|
||||
.process(
|
||||
(request, data),
|
||||
&mut PipelineContext::new(DummyPipelineProcessor),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
c.bench_function("tessselate", |b| {
|
||||
b.iter(|| {
|
||||
TessellateLayer::default()
|
||||
.process(
|
||||
parsed.clone(),
|
||||
&mut PipelineContext::new(DummyPipelineProcessor),
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, parse_tile, tessellate_tile);
|
||||
criterion_group!(benches, bench_process_vector_tile);
|
||||
criterion_main!(benches);
|
||||
|
||||
@ -1,33 +1,40 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use maplibre::{
|
||||
coords::{WorldTileCoords, ZoomLevel},
|
||||
headless::{create_headless_renderer, map::HeadlessMap},
|
||||
headless::{create_headless_renderer, map::HeadlessMap, HeadlessPlugin},
|
||||
platform::run_multithreaded,
|
||||
plugin::Plugin,
|
||||
render::RenderPlugin,
|
||||
style::Style,
|
||||
vector::{DefaultVectorTransferables, VectorPlugin},
|
||||
};
|
||||
|
||||
fn headless_render(c: &mut Criterion) {
|
||||
c.bench_function("headless_render", |b| {
|
||||
let (mut map, tile) = run_multithreaded(async {
|
||||
let (mut map, layer) = run_multithreaded(async {
|
||||
let (kernel, renderer) = create_headless_renderer(1000, None).await;
|
||||
let style = Style::default();
|
||||
let map = HeadlessMap::new(style, renderer, kernel, false).unwrap();
|
||||
|
||||
let plugins: Vec<Box<dyn Plugin<_>>> = vec![
|
||||
Box::new(RenderPlugin::default()),
|
||||
Box::new(VectorPlugin::<DefaultVectorTransferables>::default()),
|
||||
Box::new(HeadlessPlugin::new(false)),
|
||||
];
|
||||
|
||||
let map = HeadlessMap::new(style, renderer, kernel, plugins).unwrap();
|
||||
|
||||
let tile = map
|
||||
.fetch_tile(WorldTileCoords::from((0, 0, ZoomLevel::default())))
|
||||
.await
|
||||
.expect("Failed to fetch!");
|
||||
|
||||
let tile = map
|
||||
.process_tile(tile, &["water"])
|
||||
.await
|
||||
.expect("Failed to process!");
|
||||
let tile = map.process_tile(tile, &["water"]).await;
|
||||
|
||||
(map, tile)
|
||||
});
|
||||
|
||||
b.iter(|| {
|
||||
map.render_tile(tile.clone());
|
||||
map.render_tile(layer.clone());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
4
docs/src/development-documents/ecs-design.drawio.svg
Normal file
4
docs/src/development-documents/ecs-design.drawio.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 55 KiB |
@ -26,7 +26,7 @@ impl From<std::io::Error> for WgslError {
|
||||
impl WgslError {
|
||||
pub fn from_parse_err(err: ParseError, src: &str) -> Self {
|
||||
let location = err.location(src);
|
||||
let error = err.emit_to_string(src);
|
||||
let error = err.message().to_string();
|
||||
Self::ParserErr { error, location }
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,10 +13,11 @@ authors.workspace = true
|
||||
[features]
|
||||
web-webgl = ["maplibre/web-webgl"]
|
||||
trace = ["maplibre/trace"]
|
||||
headless = ["maplibre/headless"]
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.10.0"
|
||||
maplibre = { path = "../maplibre", version = "0.1.0", features = ["headless", "thread-safe-futures"] }
|
||||
maplibre = { path = "../maplibre", version = "0.1.0", features = ["thread-safe-futures"] }
|
||||
maplibre-winit = { path = "../maplibre-winit", version = "0.1.0" }
|
||||
|
||||
tile-grid = "0.3"
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
use maplibre::{
|
||||
coords::{LatLon, WorldTileCoords},
|
||||
headless::{create_headless_renderer, map::HeadlessMap},
|
||||
headless::{create_headless_renderer, map::HeadlessMap, HeadlessPlugin},
|
||||
plugin::Plugin,
|
||||
raster::{DefaultRasterTransferables, RasterPlugin},
|
||||
render::RenderPlugin,
|
||||
style::Style,
|
||||
util::grid::google_mercator,
|
||||
vector::{DefaultVectorTransferables, VectorPlugin},
|
||||
};
|
||||
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
|
||||
|
||||
@ -17,7 +21,14 @@ pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
|
||||
.map(|layer| layer.source_layer.as_ref().unwrap().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut map = HeadlessMap::new(style, renderer, kernel, true).unwrap();
|
||||
let plugins: Vec<Box<dyn Plugin<_>>> = vec![
|
||||
Box::new(RenderPlugin::default()),
|
||||
Box::new(VectorPlugin::<DefaultVectorTransferables>::default()),
|
||||
Box::new(RasterPlugin::<DefaultRasterTransferables>::default()),
|
||||
Box::new(HeadlessPlugin::new(true)),
|
||||
];
|
||||
|
||||
let mut map = HeadlessMap::new(style, renderer, kernel, plugins).unwrap();
|
||||
|
||||
let tile_limits = google_mercator().tile_limits(
|
||||
extent_wgs84_to_merc(&Extent {
|
||||
@ -35,7 +46,7 @@ pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
|
||||
|
||||
let tile = map.fetch_tile(coords).await.expect("Failed to fetch!");
|
||||
|
||||
let tile = map
|
||||
let layers = map
|
||||
.process_tile(
|
||||
tile,
|
||||
&requested_layers
|
||||
@ -43,9 +54,8 @@ pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
|
||||
.map(|layer| layer.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to process!");
|
||||
.await;
|
||||
|
||||
map.render_tile(tile);
|
||||
map.render_tile(layers);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use clap::{builder::ValueParser, Parser, Subcommand};
|
||||
use maplibre::{coords::LatLon, platform::run_multithreaded};
|
||||
use clap::{Parser, Subcommand};
|
||||
use maplibre::coords::LatLon;
|
||||
use maplibre_winit::run_headed_map;
|
||||
|
||||
use crate::headless::run_headless;
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
mod headless;
|
||||
|
||||
#[derive(Parser)]
|
||||
@ -36,16 +35,17 @@ fn parse_lat_long(env: &str) -> Result<LatLon, std::io::Error> {
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
Headed {},
|
||||
#[cfg(feature = "headless")]
|
||||
Headless {
|
||||
#[clap(default_value_t = 400)]
|
||||
tile_size: u32,
|
||||
#[clap(
|
||||
value_parser = ValueParser::new(parse_lat_long),
|
||||
value_parser = clap::builder::ValueParser::new(parse_lat_long),
|
||||
default_value_t = LatLon::new(48.0345697188, 11.3475219363)
|
||||
)]
|
||||
min: LatLon,
|
||||
#[clap(
|
||||
value_parser = ValueParser::new(parse_lat_long),
|
||||
value_parser = clap::builder::ValueParser::new(parse_lat_long),
|
||||
default_value_t = LatLon::new(48.255861, 11.7917815798)
|
||||
)]
|
||||
max: LatLon,
|
||||
@ -64,12 +64,15 @@ fn main() {
|
||||
// matches just as you would the top level cmd
|
||||
match &cli.command {
|
||||
Commands::Headed {} => run_headed_map(None),
|
||||
#[cfg(feature = "headless")]
|
||||
Commands::Headless {
|
||||
tile_size,
|
||||
min,
|
||||
max,
|
||||
} => {
|
||||
run_multithreaded(async { run_headless(*tile_size, *min, *max).await });
|
||||
maplibre::platform::run_multithreaded(async {
|
||||
headless::run_headless(*tile_size, *min, *max).await
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::{EuclideanSpace, Point3, Vector2, Vector3, Zero};
|
||||
use maplibre::{context::MapContext, render::camera::Camera, world::World};
|
||||
use maplibre::{context::MapContext, render::camera::Camera};
|
||||
use winit::event::{ElementState, MouseButton};
|
||||
|
||||
use super::UpdateState;
|
||||
@ -15,14 +15,7 @@ pub struct PanHandler {
|
||||
}
|
||||
|
||||
impl UpdateState for PanHandler {
|
||||
fn update_state(
|
||||
&mut self,
|
||||
MapContext {
|
||||
world: World { view_state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
_dt: Duration,
|
||||
) {
|
||||
fn update_state(&mut self, MapContext { view_state, .. }: &mut MapContext, _dt: Duration) {
|
||||
if !self.is_panning {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::Vector2;
|
||||
use maplibre::{
|
||||
context::MapContext, coords::WorldCoords, io::geometry_index::IndexedGeometry, world::World,
|
||||
};
|
||||
use maplibre::{context::MapContext, coords::WorldCoords, io::geometry_index::IndexedGeometry};
|
||||
use winit::event::{ElementState, MouseButton};
|
||||
|
||||
use crate::input::UpdateState;
|
||||
@ -58,13 +56,7 @@ impl UpdateState for QueryHandler {
|
||||
fn update_state(
|
||||
&mut self,
|
||||
MapContext {
|
||||
world:
|
||||
World {
|
||||
view_state,
|
||||
geometry_index,
|
||||
..
|
||||
},
|
||||
..
|
||||
view_state, world, ..
|
||||
}: &mut MapContext,
|
||||
_dt: Duration,
|
||||
) {
|
||||
@ -81,7 +73,9 @@ impl UpdateState for QueryHandler {
|
||||
&inverted_view_proj,
|
||||
false,
|
||||
) {
|
||||
if let Some(geometries) = geometry_index
|
||||
if let Some(geometries) = world
|
||||
.tiles
|
||||
.geometry_index
|
||||
.query_point(
|
||||
&WorldCoords {
|
||||
x: coordinates.x,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::{Vector3, Zero};
|
||||
use maplibre::{context::MapContext, world::World};
|
||||
use maplibre::context::MapContext;
|
||||
|
||||
use super::UpdateState;
|
||||
|
||||
@ -13,14 +13,7 @@ pub struct ShiftHandler {
|
||||
}
|
||||
|
||||
impl UpdateState for ShiftHandler {
|
||||
fn update_state(
|
||||
&mut self,
|
||||
MapContext {
|
||||
world: World { view_state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
dt: Duration,
|
||||
) {
|
||||
fn update_state(&mut self, MapContext { view_state, .. }: &mut MapContext, dt: Duration) {
|
||||
let dt = dt.as_secs_f64() * (1.0 / self.speed);
|
||||
|
||||
let delta = self.camera_translate * dt;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::{Deg, Zero};
|
||||
use maplibre::{context::MapContext, world::World};
|
||||
use maplibre::context::MapContext;
|
||||
|
||||
use super::UpdateState;
|
||||
|
||||
@ -13,14 +13,7 @@ pub struct TiltHandler {
|
||||
}
|
||||
|
||||
impl UpdateState for TiltHandler {
|
||||
fn update_state(
|
||||
&mut self,
|
||||
MapContext {
|
||||
world: World { view_state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
dt: Duration,
|
||||
) {
|
||||
fn update_state(&mut self, MapContext { view_state, .. }: &mut MapContext, dt: Duration) {
|
||||
let dt = dt.as_secs_f64() * (1.0 / self.speed);
|
||||
|
||||
let delta = self.delta_pitch * dt;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::{Vector2, Vector3};
|
||||
use maplibre::{context::MapContext, coords::Zoom, world::World};
|
||||
use maplibre::{context::MapContext, coords::Zoom};
|
||||
|
||||
use super::UpdateState;
|
||||
|
||||
@ -12,14 +12,7 @@ pub struct ZoomHandler {
|
||||
}
|
||||
|
||||
impl UpdateState for ZoomHandler {
|
||||
fn update_state(
|
||||
&mut self,
|
||||
MapContext {
|
||||
world: World { view_state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
_dt: Duration,
|
||||
) {
|
||||
fn update_state(&mut self, MapContext { view_state, .. }: &mut MapContext, _dt: Duration) {
|
||||
if let Some(zoom_delta) = self.zoom_delta {
|
||||
if let Some(window_position) = self.window_position {
|
||||
let current_zoom = view_state.zoom();
|
||||
|
||||
@ -4,7 +4,7 @@ use std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
use instant::Instant;
|
||||
use maplibre::{
|
||||
environment::Environment,
|
||||
environment::{Environment, OffscreenKernelEnvironment},
|
||||
event_loop::{EventLoop, EventLoopProxy, SendEventError},
|
||||
io::{apc::AsyncProcedureCall, scheduler::Scheduler, source_client::HttpClient},
|
||||
map::Map,
|
||||
@ -185,18 +185,31 @@ impl<ET: 'static> EventLoopProxy<ET> for WinitEventLoopProxy<ET> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WinitEnvironment<S: Scheduler, HC: HttpClient, APC: AsyncProcedureCall<HC>, ET> {
|
||||
pub struct WinitEnvironment<
|
||||
S: Scheduler,
|
||||
HC: HttpClient,
|
||||
K: OffscreenKernelEnvironment,
|
||||
APC: AsyncProcedureCall<K>,
|
||||
ET,
|
||||
> {
|
||||
phantom_s: PhantomData<S>,
|
||||
phantom_hc: PhantomData<HC>,
|
||||
phantom_k: PhantomData<K>,
|
||||
phantom_apc: PhantomData<APC>,
|
||||
phantom_et: PhantomData<ET>,
|
||||
}
|
||||
|
||||
impl<S: Scheduler, HC: HttpClient, APC: AsyncProcedureCall<HC>, ET: 'static> Environment
|
||||
for WinitEnvironment<S, HC, APC, ET>
|
||||
impl<
|
||||
S: Scheduler,
|
||||
HC: HttpClient,
|
||||
K: OffscreenKernelEnvironment,
|
||||
APC: AsyncProcedureCall<K>,
|
||||
ET: 'static,
|
||||
> Environment for WinitEnvironment<S, HC, K, APC, ET>
|
||||
{
|
||||
type MapWindowConfig = WinitMapWindowConfig<ET>;
|
||||
type AsyncProcedureCall = APC;
|
||||
type Scheduler = S;
|
||||
type HttpClient = HC;
|
||||
type OffscreenKernelEnvironment = K;
|
||||
}
|
||||
|
||||
@ -6,12 +6,17 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use maplibre::{
|
||||
debug::DebugPlugin,
|
||||
event_loop::EventLoop,
|
||||
io::apc::SchedulerAsyncProcedureCall,
|
||||
kernel::{Kernel, KernelBuilder},
|
||||
map::Map,
|
||||
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
||||
render::{builder::RendererBuilder, settings::WgpuSettings},
|
||||
platform::{
|
||||
http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler,
|
||||
ReqwestOffscreenKernelEnvironment,
|
||||
},
|
||||
raster::{DefaultRasterTransferables, RasterPlugin},
|
||||
render::{builder::RendererBuilder, settings::WgpuSettings, RenderPlugin},
|
||||
style::Style,
|
||||
window::{MapWindow, MapWindowConfig, WindowSize},
|
||||
};
|
||||
@ -72,14 +77,15 @@ impl<ET: 'static> MapWindowConfig for WinitMapWindowConfig<ET> {
|
||||
|
||||
pub fn run_headed_map(cache_path: Option<String>) {
|
||||
run_multithreaded(async {
|
||||
type Environment<S, HC, APC> =
|
||||
WinitEnvironment<S, HC, ReqwestOffscreenKernelEnvironment, APC, ()>;
|
||||
|
||||
let client = ReqwestHttpClient::new(cache_path);
|
||||
let kernel: Kernel<WinitEnvironment<_, _, _, ()>> = KernelBuilder::new()
|
||||
|
||||
let kernel: Kernel<Environment<_, _, _>> = KernelBuilder::new()
|
||||
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
|
||||
.with_http_client(client.clone())
|
||||
.with_apc(SchedulerAsyncProcedureCall::new(
|
||||
client,
|
||||
TokioScheduler::new(),
|
||||
))
|
||||
.with_apc(SchedulerAsyncProcedureCall::new(TokioScheduler::new()))
|
||||
.with_scheduler(TokioScheduler::new())
|
||||
.build();
|
||||
|
||||
@ -88,7 +94,18 @@ pub fn run_headed_map(cache_path: Option<String>) {
|
||||
..WgpuSettings::default()
|
||||
});
|
||||
|
||||
let mut map = Map::new(Style::default(), kernel, renderer_builder).unwrap();
|
||||
let mut map = Map::new(
|
||||
Style::default(),
|
||||
kernel,
|
||||
renderer_builder,
|
||||
vec![
|
||||
Box::new(RenderPlugin::default()),
|
||||
//Box::new(VectorPlugin::<DefaultVectorTransferables>::default()),
|
||||
Box::new(RasterPlugin::<DefaultRasterTransferables>::default()),
|
||||
Box::new(DebugPlugin::default()),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
|
||||
@ -12,13 +12,15 @@ keywords.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# FIXME tcs: Remove raster from default
|
||||
default = ["raster"]
|
||||
web-webgl = ["wgpu/webgl"]
|
||||
# Enable tracing using tracy on desktop/mobile and the chrome profiler on web
|
||||
trace = ["tracing-subscriber", "tracing-tracy"]
|
||||
thread-safe-futures = []
|
||||
embed-static-tiles = ["maplibre-build-tools/sqlite"]
|
||||
headless = ["png"]
|
||||
raster = ["image"]
|
||||
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android", target_os = "windows"))'.dependencies]
|
||||
@ -84,6 +86,7 @@ smallvec = "1.9.0"
|
||||
|
||||
# Headless
|
||||
png = { version = "0.17.5", optional = true }
|
||||
image = { version = "0.24", default-features = false, features = ["jpeg", "webp", "png"], optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
maplibre-build-tools = { path = "../maplibre-build-tools", version = "0.1.0" }
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
use crate::{render::Renderer, style::Style, world::World};
|
||||
use crate::{render::Renderer, style::Style, tcs::world::World, view_state::ViewState};
|
||||
|
||||
/// Stores the context of the map.
|
||||
///
|
||||
/// This struct should not depend on the [`crate::environment::Environment`] trait. Else types
|
||||
/// throughout the crate get messy quickly.
|
||||
pub struct MapContext {
|
||||
pub style: Style,
|
||||
pub world: World,
|
||||
pub view_state: ViewState,
|
||||
pub renderer: Renderer,
|
||||
}
|
||||
|
||||
impl MapContext {
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.world.view_state.resize(width, height);
|
||||
self.renderer.resize(width, height)
|
||||
self.view_state.resize(width, height);
|
||||
self.renderer.resize_surface(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,7 +464,7 @@ impl WorldTileCoords {
|
||||
const CASES: u8 = 4;
|
||||
let z = u8::from(self.z);
|
||||
match (self.x % 2 == 0, self.y % 2 == 0) {
|
||||
(true, true) => 0 + z * CASES,
|
||||
(true, true) => z * CASES,
|
||||
(true, false) => 1 + z * CASES,
|
||||
(false, true) => 2 + z * CASES,
|
||||
(false, false) => 3 + z * CASES,
|
||||
|
||||
11
maplibre/src/debug/cleanup_system.rs
Normal file
11
maplibre/src/debug/cleanup_system.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use crate::{context::MapContext, debug::TileDebugItem, render::render_phase::RenderPhase};
|
||||
|
||||
pub fn cleanup_system(MapContext { world, .. }: &mut MapContext) {
|
||||
let Some(debug_tile_phase) = world
|
||||
.resources
|
||||
.query_mut::<
|
||||
&mut RenderPhase<TileDebugItem>,
|
||||
>() else { return; };
|
||||
|
||||
debug_tile_phase.clear();
|
||||
}
|
||||
@ -1,12 +1,15 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::render::{
|
||||
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
|
||||
render_commands::DrawDebugOutlines,
|
||||
render_phase::RenderCommand,
|
||||
resource::TrackedRenderPass,
|
||||
Eventually::Initialized,
|
||||
RenderState,
|
||||
use crate::{
|
||||
debug::TileDebugItem,
|
||||
render::{
|
||||
eventually::Eventually::Initialized,
|
||||
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
|
||||
render_phase::RenderPhase,
|
||||
resource::TrackedRenderPass,
|
||||
RenderResources,
|
||||
},
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
/// Pass which renders debug information on top of the map.
|
||||
@ -23,15 +26,16 @@ impl Node for DebugPassNode {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn update(&mut self, _state: &mut RenderState) {}
|
||||
fn update(&mut self, _state: &mut RenderResources) {}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
resources: &RenderResources,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let Initialized(render_target) = &state.render_target else {
|
||||
let Initialized(render_target) = &resources.render_target else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
@ -49,15 +53,21 @@ impl Node for DebugPassNode {
|
||||
render_context
|
||||
.command_encoder
|
||||
.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
label: Some("debug_pass"),
|
||||
color_attachments: &[Some(color_attachment)],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
let mut tracked_pass = TrackedRenderPass::new(render_pass);
|
||||
|
||||
for item in &state.mask_phase.items {
|
||||
DrawDebugOutlines::render(state, item, &mut tracked_pass);
|
||||
if let Some(debug_items) = world.resources.get::<RenderPhase<TileDebugItem>>() {
|
||||
log::trace!(
|
||||
"RenderPhase<TileDebugItem>::size() = {}",
|
||||
debug_items.size()
|
||||
);
|
||||
for item in debug_items {
|
||||
item.draw_function.draw(&mut tracked_pass, world, item);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
93
maplibre/src/debug/mod.rs
Normal file
93
maplibre/src/debug/mod.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use std::{ops::Deref, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
debug::{
|
||||
cleanup_system::cleanup_system, debug_pass::DebugPassNode, queue_system::queue_system,
|
||||
resource_system::resource_system,
|
||||
},
|
||||
environment::Environment,
|
||||
kernel::Kernel,
|
||||
plugin::Plugin,
|
||||
render::{
|
||||
eventually::Eventually,
|
||||
graph::RenderGraph,
|
||||
render_phase::{Draw, PhaseItem, RenderPhase},
|
||||
tile_view_pattern::TileShape,
|
||||
RenderStageLabel,
|
||||
},
|
||||
schedule::Schedule,
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
mod cleanup_system;
|
||||
mod debug_pass;
|
||||
mod queue_system;
|
||||
mod render_commands;
|
||||
mod resource_system;
|
||||
|
||||
/// Labels for the "draw" graph
|
||||
mod draw_graph {
|
||||
pub const NAME: &str = "draw";
|
||||
// Labels for input nodes
|
||||
pub mod input {}
|
||||
// Labels for non-input nodes
|
||||
pub mod node {
|
||||
pub const MAIN_PASS: &str = "main_pass";
|
||||
pub const DEBUG_PASS: &str = "debug_pass";
|
||||
}
|
||||
}
|
||||
|
||||
struct DebugPipeline(wgpu::RenderPipeline);
|
||||
impl Deref for DebugPipeline {
|
||||
type Target = wgpu::RenderPipeline;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
struct TileDebugItem {
|
||||
pub draw_function: Box<dyn Draw<TileDebugItem>>,
|
||||
pub source_shape: TileShape,
|
||||
}
|
||||
|
||||
impl PhaseItem for TileDebugItem {
|
||||
type SortKey = u32;
|
||||
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
0
|
||||
}
|
||||
|
||||
fn draw_function(&self) -> &dyn Draw<TileDebugItem> {
|
||||
self.draw_function.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DebugPlugin;
|
||||
|
||||
impl<E: Environment> Plugin<E> for DebugPlugin {
|
||||
fn build(
|
||||
&self,
|
||||
schedule: &mut Schedule,
|
||||
_kernel: Rc<Kernel<E>>,
|
||||
world: &mut World,
|
||||
graph: &mut RenderGraph,
|
||||
) {
|
||||
let resources = &mut world.resources;
|
||||
|
||||
let draw_graph = graph.get_sub_graph_mut(draw_graph::NAME).unwrap();
|
||||
draw_graph.add_node(draw_graph::node::DEBUG_PASS, DebugPassNode::new());
|
||||
|
||||
draw_graph
|
||||
.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::DEBUG_PASS)
|
||||
.unwrap();
|
||||
|
||||
resources.init::<RenderPhase<TileDebugItem>>();
|
||||
resources.insert(Eventually::<DebugPipeline>::Uninitialized);
|
||||
|
||||
schedule.add_system_to_stage(RenderStageLabel::Prepare, resource_system);
|
||||
schedule.add_system_to_stage(RenderStageLabel::Queue, queue_system);
|
||||
schedule.add_system_to_stage(RenderStageLabel::Cleanup, cleanup_system);
|
||||
}
|
||||
}
|
||||
34
maplibre/src/debug/queue_system.rs
Normal file
34
maplibre/src/debug/queue_system.rs
Normal file
@ -0,0 +1,34 @@
|
||||
//! Queues [PhaseItems](crate::render::render_phase::PhaseItem) for rendering.
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
debug::{render_commands::DrawDebugOutlines, TileDebugItem},
|
||||
render::{
|
||||
eventually::{Eventually, Eventually::Initialized},
|
||||
render_phase::{DrawState, RenderPhase},
|
||||
tile_view_pattern::WgpuTileViewPattern,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn queue_system(MapContext { world, .. }: &mut MapContext) {
|
||||
let Some((
|
||||
Initialized(tile_view_pattern),
|
||||
tile_debug_phase,
|
||||
)) = world.resources.query_mut::<(
|
||||
&mut Eventually<WgpuTileViewPattern>,
|
||||
&mut RenderPhase<TileDebugItem>,
|
||||
)>() else { return; };
|
||||
|
||||
for view_tile in tile_view_pattern.iter() {
|
||||
let coords = &view_tile.coords();
|
||||
tracing::trace!("Drawing debug at {coords}");
|
||||
|
||||
// draw tile normal or the source e.g. parent or children
|
||||
view_tile.render(|source_shape| {
|
||||
// Draw masks for all source_shapes
|
||||
tile_debug_phase.add(TileDebugItem {
|
||||
draw_function: Box::new(DrawState::<TileDebugItem, DrawDebugOutlines>::new()),
|
||||
source_shape: source_shape.clone(),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
58
maplibre/src/debug/render_commands.rs
Normal file
58
maplibre/src/debug/render_commands.rs
Normal file
@ -0,0 +1,58 @@
|
||||
//! Specifies the instructions which are going to be sent to the GPU. Render commands can be concatenated
|
||||
//! into a new render command which executes multiple instruction sets.
|
||||
use crate::{
|
||||
debug::{DebugPipeline, TileDebugItem},
|
||||
render::{
|
||||
eventually::{Eventually, Eventually::Initialized},
|
||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
|
||||
resource::TrackedRenderPass,
|
||||
tile_view_pattern::WgpuTileViewPattern,
|
||||
},
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
pub struct SetDebugPipeline;
|
||||
impl<P: PhaseItem> RenderCommand<P> for SetDebugPipeline {
|
||||
fn render<'w>(
|
||||
world: &'w World,
|
||||
_item: &P,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Some(Initialized(pipeline)) = world
|
||||
.resources
|
||||
.get::<Eventually<DebugPipeline>>() else { return RenderCommandResult::Failure; };
|
||||
|
||||
pass.set_render_pipeline(pipeline);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DrawDebugOutline;
|
||||
impl RenderCommand<TileDebugItem> for DrawDebugOutline {
|
||||
fn render<'w>(
|
||||
world: &'w World,
|
||||
item: &TileDebugItem,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Some(Initialized(tile_view_pattern)) = world
|
||||
.resources
|
||||
.get::<Eventually<WgpuTileViewPattern>>() else { return RenderCommandResult::Failure; };
|
||||
|
||||
let source_shape = &item.source_shape;
|
||||
|
||||
let tile_view_pattern_buffer = source_shape
|
||||
.buffer_range()
|
||||
.expect("tile_view_pattern needs to be uploaded first"); // FIXME tcs
|
||||
pass.set_vertex_buffer(
|
||||
0,
|
||||
tile_view_pattern.buffer().slice(tile_view_pattern_buffer),
|
||||
);
|
||||
|
||||
const TILE_MASK_SHADER_VERTICES: u32 = 24;
|
||||
pass.draw(0..TILE_MASK_SHADER_VERTICES, 0..1);
|
||||
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub type DrawDebugOutlines = (SetDebugPipeline, DrawDebugOutline);
|
||||
56
maplibre/src/debug/resource_system.rs
Normal file
56
maplibre/src/debug/resource_system.rs
Normal file
@ -0,0 +1,56 @@
|
||||
//! Prepares GPU-owned resources by initializing them if they are uninitialized or out-of-date.
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
debug::DebugPipeline,
|
||||
render::{
|
||||
eventually::Eventually,
|
||||
resource::{RenderPipeline, TilePipeline},
|
||||
shaders,
|
||||
shaders::Shader,
|
||||
RenderResources, Renderer,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn resource_system(
|
||||
MapContext {
|
||||
world,
|
||||
renderer:
|
||||
Renderer {
|
||||
device,
|
||||
resources: RenderResources { surface, .. },
|
||||
settings,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let Some(
|
||||
debug_pipeline
|
||||
) = world.resources.query_mut::<
|
||||
&mut Eventually<DebugPipeline>,
|
||||
>() else { return; };
|
||||
|
||||
debug_pipeline.initialize(|| {
|
||||
let mask_shader = shaders::TileMaskShader {
|
||||
format: surface.surface_format(),
|
||||
draw_colors: true,
|
||||
debug_lines: true,
|
||||
};
|
||||
|
||||
let pipeline = TilePipeline::new(
|
||||
"debug_pipeline".into(),
|
||||
*settings,
|
||||
mask_shader.describe_vertex(),
|
||||
mask_shader.describe_fragment(),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.describe_render_pipeline()
|
||||
.initialize(device);
|
||||
DebugPipeline(pipeline)
|
||||
});
|
||||
}
|
||||
@ -1,5 +1,9 @@
|
||||
use crate::{
|
||||
io::{apc::AsyncProcedureCall, scheduler::Scheduler, source_client::HttpClient},
|
||||
io::{
|
||||
apc::AsyncProcedureCall,
|
||||
scheduler::Scheduler,
|
||||
source_client::{HttpClient, SourceClient},
|
||||
},
|
||||
window::MapWindowConfig,
|
||||
};
|
||||
|
||||
@ -14,9 +18,18 @@ use crate::{
|
||||
pub trait Environment: 'static {
|
||||
type MapWindowConfig: MapWindowConfig;
|
||||
|
||||
type AsyncProcedureCall: AsyncProcedureCall<Self::HttpClient>;
|
||||
type AsyncProcedureCall: AsyncProcedureCall<Self::OffscreenKernelEnvironment>;
|
||||
|
||||
type Scheduler: Scheduler;
|
||||
|
||||
type HttpClient: HttpClient;
|
||||
|
||||
type OffscreenKernelEnvironment: OffscreenKernelEnvironment;
|
||||
}
|
||||
|
||||
pub trait OffscreenKernelEnvironment: Send + Sync + 'static {
|
||||
type HttpClient: HttpClient;
|
||||
fn create() -> Self;
|
||||
|
||||
fn source_client(&self) -> SourceClient<Self::HttpClient>;
|
||||
}
|
||||
|
||||
@ -2,14 +2,19 @@ use crate::{
|
||||
environment::Environment,
|
||||
headless::window::HeadlessMapWindowConfig,
|
||||
io::apc::SchedulerAsyncProcedureCall,
|
||||
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
|
||||
platform::{
|
||||
http_client::ReqwestHttpClient, scheduler::TokioScheduler,
|
||||
ReqwestOffscreenKernelEnvironment,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct HeadlessEnvironment;
|
||||
|
||||
impl Environment for HeadlessEnvironment {
|
||||
type MapWindowConfig = HeadlessMapWindowConfig;
|
||||
type AsyncProcedureCall = SchedulerAsyncProcedureCall<Self::HttpClient, Self::Scheduler>;
|
||||
type AsyncProcedureCall =
|
||||
SchedulerAsyncProcedureCall<Self::OffscreenKernelEnvironment, Self::Scheduler>;
|
||||
type Scheduler = TokioScheduler;
|
||||
type HttpClient = ReqwestHttpClient;
|
||||
type OffscreenKernelEnvironment = ReqwestOffscreenKernelEnvironment;
|
||||
}
|
||||
|
||||
@ -1,27 +1,24 @@
|
||||
use crate::render::{
|
||||
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
|
||||
resource::Head,
|
||||
RenderState,
|
||||
use crate::{
|
||||
render::{
|
||||
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
|
||||
resource::Head,
|
||||
RenderResources,
|
||||
},
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
/// Node which copies the contents of the GPU-side texture in [`BufferedTextureHead`] to an
|
||||
/// unmapped GPU-side buffer. This buffer will be mapped in
|
||||
/// [`crate::render::stages::write_surface_buffer_stage::WriteSurfaceBufferStage`].
|
||||
#[derive(Default)]
|
||||
pub struct CopySurfaceBufferNode {}
|
||||
|
||||
impl CopySurfaceBufferNode {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
pub struct CopySurfaceBufferNode;
|
||||
|
||||
impl Node for CopySurfaceBufferNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn update(&mut self, _state: &mut RenderState) {}
|
||||
fn update(&mut self, _state: &mut RenderResources) {}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
@ -29,7 +26,8 @@ impl Node for CopySurfaceBufferNode {
|
||||
RenderContext {
|
||||
command_encoder, ..
|
||||
}: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
state: &RenderResources,
|
||||
_world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let surface = state.surface();
|
||||
match surface.head() {
|
||||
|
||||
@ -1,33 +1,31 @@
|
||||
use std::collections::HashSet;
|
||||
use std::{cell::RefCell, ops::Deref, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::{WorldCoords, WorldTileCoords, Zoom, TILE_SIZE},
|
||||
headless::{
|
||||
environment::HeadlessEnvironment, graph_node::CopySurfaceBufferNode,
|
||||
stage::WriteSurfaceBufferStage,
|
||||
},
|
||||
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel, TILE_SIZE},
|
||||
headless::environment::HeadlessEnvironment,
|
||||
io::{
|
||||
pipeline::{PipelineContext, PipelineError, PipelineProcessor, Processable},
|
||||
apc::{Context, IntoMessage, Message, SendError},
|
||||
source_client::SourceFetchError,
|
||||
tile_pipelines::build_vector_tile_pipeline,
|
||||
tile_repository::{StoredLayer, StoredTile},
|
||||
RawLayer, TileRequest,
|
||||
source_type::{SourceType, TessellateSource},
|
||||
},
|
||||
kernel::Kernel,
|
||||
map::MapError,
|
||||
render::{
|
||||
create_default_render_graph, draw_graph, eventually::Eventually,
|
||||
register_default_render_stages, stages::RenderStageLabel, Renderer, ShaderVertex,
|
||||
},
|
||||
plugin::Plugin,
|
||||
render::{eventually::Eventually, Renderer},
|
||||
schedule::{Schedule, Stage},
|
||||
style::Style,
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
world::World,
|
||||
tcs::world::World,
|
||||
vector::{
|
||||
process_vector_tile, AvailableVectorLayerData, DefaultVectorTransferables,
|
||||
LayerTessellated, ProcessVectorContext, VectorBufferPool, VectorLayerData,
|
||||
VectorLayersDataComponent, VectorTileRequest, VectorTransferables,
|
||||
},
|
||||
view_state::ViewState,
|
||||
};
|
||||
|
||||
pub struct HeadlessMap {
|
||||
kernel: Kernel<HeadlessEnvironment>,
|
||||
kernel: Rc<Kernel<HeadlessEnvironment>>,
|
||||
schedule: Schedule,
|
||||
map_context: MapContext,
|
||||
}
|
||||
@ -35,39 +33,38 @@ pub struct HeadlessMap {
|
||||
impl HeadlessMap {
|
||||
pub fn new(
|
||||
style: Style,
|
||||
renderer: Renderer,
|
||||
mut renderer: Renderer,
|
||||
kernel: Kernel<HeadlessEnvironment>,
|
||||
write_to_disk: bool,
|
||||
plugins: Vec<Box<dyn Plugin<HeadlessEnvironment>>>,
|
||||
) -> Result<Self, MapError> {
|
||||
let window_size = renderer.state().surface().size();
|
||||
|
||||
let world = World::new(
|
||||
let view_state = ViewState::new(
|
||||
window_size,
|
||||
WorldCoords::from((TILE_SIZE / 2., TILE_SIZE / 2.)),
|
||||
Zoom::default(),
|
||||
cgmath::Deg(0.0),
|
||||
cgmath::Deg(110.0),
|
||||
);
|
||||
|
||||
let mut graph = create_default_render_graph().map_err(MapError::RenderGraphInit)?;
|
||||
let draw_graph = graph
|
||||
.get_sub_graph_mut(draw_graph::NAME)
|
||||
.expect("Subgraph does not exist");
|
||||
draw_graph.add_node(draw_graph::node::COPY, CopySurfaceBufferNode::default());
|
||||
draw_graph
|
||||
.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY)
|
||||
.unwrap(); // TODO: remove unwrap
|
||||
|
||||
let mut world = World::default();
|
||||
let mut schedule = Schedule::default();
|
||||
register_default_render_stages(graph, &mut schedule);
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Cleanup,
|
||||
WriteSurfaceBufferStage::new(write_to_disk),
|
||||
);
|
||||
let kernel = Rc::new(kernel);
|
||||
|
||||
for plugin in &plugins {
|
||||
plugin.build(
|
||||
&mut schedule,
|
||||
kernel.clone(),
|
||||
&mut world,
|
||||
&mut renderer.render_graph,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
kernel,
|
||||
map_context: MapContext {
|
||||
style,
|
||||
view_state,
|
||||
world,
|
||||
renderer,
|
||||
},
|
||||
@ -75,78 +72,99 @@ impl HeadlessMap {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render_tile(&mut self, tile: StoredTile) {
|
||||
pub fn render_tile(
|
||||
&mut self,
|
||||
layers: Vec<Box<<DefaultVectorTransferables as VectorTransferables>::LayerTessellated>>,
|
||||
) {
|
||||
let context = &mut self.map_context;
|
||||
let tiles = &mut context.world.tiles;
|
||||
|
||||
if let Eventually::Initialized(pool) = context.renderer.state.buffer_pool_mut() {
|
||||
pool.clear();
|
||||
} else {
|
||||
// TODO return error
|
||||
}
|
||||
tiles
|
||||
.spawn_mut((0, 0, ZoomLevel::default()).into())
|
||||
.expect("unable to spawn tile")
|
||||
.insert(VectorLayersDataComponent {
|
||||
done: true,
|
||||
layers: layers
|
||||
.into_iter()
|
||||
.map(|layer| {
|
||||
VectorLayerData::Available(AvailableVectorLayerData {
|
||||
coords: layer.coords,
|
||||
source_layer: layer.layer_data.name,
|
||||
buffer: layer.buffer,
|
||||
feature_indices: layer.feature_indices,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
});
|
||||
|
||||
context.world.tile_repository.clear();
|
||||
self.schedule.run(context);
|
||||
|
||||
context.world.tile_repository.put_tile(tile);
|
||||
let resources = &mut context.world.resources;
|
||||
let tiles = &mut context.world.tiles;
|
||||
|
||||
self.schedule.run(&mut self.map_context);
|
||||
tiles.clear();
|
||||
|
||||
let pool = resources
|
||||
.query_mut::<&mut Eventually<VectorBufferPool>>() // FIXME tcs: we access internals of the vector plugin here
|
||||
.expect("VectorBufferPool not found")
|
||||
.expect_initialized_mut("VectorBufferPool not initialized");
|
||||
|
||||
pool.clear();
|
||||
}
|
||||
|
||||
pub async fn fetch_tile(&self, coords: WorldTileCoords) -> Result<Box<[u8]>, SourceFetchError> {
|
||||
let source_client = self.kernel.source_client();
|
||||
|
||||
Ok(source_client.fetch(&coords).await?.into_boxed_slice())
|
||||
let data = source_client
|
||||
.fetch(
|
||||
&coords,
|
||||
&SourceType::Tessellate(TessellateSource::default()),
|
||||
)
|
||||
.await?
|
||||
.into_boxed_slice();
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub async fn process_tile(
|
||||
&self,
|
||||
tile_data: Box<[u8]>,
|
||||
source_layers: &[&str],
|
||||
) -> Result<StoredTile, PipelineError> {
|
||||
let mut pipeline_context = PipelineContext::new(HeadlessPipelineProcessor::default());
|
||||
let pipeline = build_vector_tile_pipeline();
|
||||
) -> Vec<Box<<DefaultVectorTransferables as VectorTransferables>::LayerTessellated>> {
|
||||
let context = HeadlessContext::default();
|
||||
let mut processor =
|
||||
ProcessVectorContext::<DefaultVectorTransferables, HeadlessContext>::new(context);
|
||||
|
||||
let target_coords = WorldTileCoords::default(); // load to 0,0,0
|
||||
pipeline.process(
|
||||
(
|
||||
TileRequest {
|
||||
coords: target_coords,
|
||||
layers: source_layers
|
||||
.iter()
|
||||
.map(|layer| layer.to_string())
|
||||
.collect::<HashSet<String>>(),
|
||||
},
|
||||
tile_data,
|
||||
),
|
||||
&mut pipeline_context,
|
||||
)?;
|
||||
process_vector_tile(
|
||||
&tile_data,
|
||||
VectorTileRequest {
|
||||
coords: target_coords,
|
||||
layers: source_layers
|
||||
.iter()
|
||||
.map(|layer| layer.to_string())
|
||||
.collect(),
|
||||
},
|
||||
&mut processor,
|
||||
)
|
||||
.expect("Failed to process!");
|
||||
|
||||
let processor = pipeline_context
|
||||
.take_processor::<HeadlessPipelineProcessor>()
|
||||
.expect("Unable to get processor");
|
||||
let messages = processor.take_context().messages.deref().take();
|
||||
let layers = messages.into_iter()
|
||||
.filter(|message| message.tag() == <DefaultVectorTransferables as VectorTransferables>::LayerTessellated::message_tag())
|
||||
.map(|message| message.into_transferable::<<DefaultVectorTransferables as VectorTransferables>::LayerTessellated>())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(StoredTile::success(target_coords, processor.layers))
|
||||
layers
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HeadlessPipelineProcessor {
|
||||
pub layers: Vec<StoredLayer>,
|
||||
#[derive(Default, Clone)]
|
||||
pub struct HeadlessContext {
|
||||
pub messages: Rc<RefCell<Vec<Message>>>,
|
||||
}
|
||||
|
||||
impl PipelineProcessor for HeadlessPipelineProcessor {
|
||||
fn layer_tesselation_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: RawLayer,
|
||||
) -> Result<(), PipelineError> {
|
||||
self.layers.push(StoredLayer::TessellatedLayer {
|
||||
coords: *coords,
|
||||
layer_name: layer_data.name,
|
||||
buffer,
|
||||
feature_indices,
|
||||
});
|
||||
impl Context for HeadlessContext {
|
||||
fn send<T: IntoMessage>(&self, message: T) -> Result<(), SendError> {
|
||||
self.messages.deref().borrow_mut().push(message.into());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,27 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
headless::{
|
||||
environment::HeadlessEnvironment,
|
||||
graph_node::CopySurfaceBufferNode,
|
||||
system::WriteSurfaceBufferSystem,
|
||||
window::{HeadlessMapWindow, HeadlessMapWindowConfig},
|
||||
},
|
||||
io::apc::SchedulerAsyncProcedureCall,
|
||||
kernel::{Kernel, KernelBuilder},
|
||||
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
|
||||
render::{builder::RendererBuilder, Renderer},
|
||||
plugin::Plugin,
|
||||
render::{
|
||||
builder::RendererBuilder, graph::RenderGraph, tile_view_pattern::ViewTileSources,
|
||||
RenderStageLabel, Renderer,
|
||||
},
|
||||
schedule::Schedule,
|
||||
tcs::{system::SystemContainer, world::World},
|
||||
window::{MapWindowConfig, WindowSize},
|
||||
};
|
||||
|
||||
mod graph_node;
|
||||
mod stage;
|
||||
mod system;
|
||||
|
||||
pub mod environment;
|
||||
pub mod map;
|
||||
@ -27,10 +37,7 @@ pub async fn create_headless_renderer(
|
||||
WindowSize::new(tile_size, tile_size).unwrap(),
|
||||
))
|
||||
.with_http_client(client.clone())
|
||||
.with_apc(SchedulerAsyncProcedureCall::new(
|
||||
client,
|
||||
TokioScheduler::new(),
|
||||
))
|
||||
.with_apc(SchedulerAsyncProcedureCall::new(TokioScheduler::new()))
|
||||
.with_scheduler(TokioScheduler::new())
|
||||
.build();
|
||||
|
||||
@ -45,3 +52,54 @@ pub async fn create_headless_renderer(
|
||||
|
||||
(kernel, renderer)
|
||||
}
|
||||
|
||||
/// Labels for the "draw" graph
|
||||
mod draw_graph {
|
||||
pub const NAME: &str = "draw";
|
||||
// Labels for input nodes
|
||||
pub mod input {}
|
||||
// Labels for non-input nodes
|
||||
pub mod node {
|
||||
pub const MAIN_PASS: &str = "main_pass";
|
||||
pub const COPY: &str = "copy_pass";
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HeadlessPlugin {
|
||||
write_to_disk: bool,
|
||||
}
|
||||
|
||||
impl HeadlessPlugin {
|
||||
pub fn new(write_to_disk: bool) -> Self {
|
||||
Self { write_to_disk }
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin<HeadlessEnvironment> for HeadlessPlugin {
|
||||
fn build(
|
||||
&self,
|
||||
schedule: &mut Schedule,
|
||||
_kernel: Rc<Kernel<HeadlessEnvironment>>,
|
||||
world: &mut World,
|
||||
graph: &mut RenderGraph,
|
||||
) {
|
||||
let resources = &mut world.resources;
|
||||
|
||||
let draw_graph = graph
|
||||
.get_sub_graph_mut(draw_graph::NAME)
|
||||
.expect("Subgraph does not exist");
|
||||
draw_graph.add_node(draw_graph::node::COPY, CopySurfaceBufferNode::default());
|
||||
draw_graph
|
||||
.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY)
|
||||
.unwrap(); // TODO: remove unwrap
|
||||
|
||||
schedule.add_system_to_stage(
|
||||
RenderStageLabel::Cleanup,
|
||||
SystemContainer::new(WriteSurfaceBufferSystem::new(self.write_to_disk)),
|
||||
);
|
||||
|
||||
// FIXME tcs: Is this good style?
|
||||
schedule.remove_stage(RenderStageLabel::Extract);
|
||||
resources.get_mut::<ViewTileSources>().unwrap().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
@ -6,17 +6,17 @@ use crate::{
|
||||
resource::{BufferedTextureHead, Head},
|
||||
Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
tcs::system::System,
|
||||
};
|
||||
|
||||
/// Stage which writes the current contents of the GPU/CPU buffer in [`BufferedTextureHead`]
|
||||
/// to disk as PNG.
|
||||
pub struct WriteSurfaceBufferStage {
|
||||
pub struct WriteSurfaceBufferSystem {
|
||||
frame: u64,
|
||||
write_to_disk: bool,
|
||||
}
|
||||
|
||||
impl WriteSurfaceBufferStage {
|
||||
impl WriteSurfaceBufferSystem {
|
||||
pub fn new(write_to_disk: bool) -> Self {
|
||||
Self {
|
||||
frame: 0,
|
||||
@ -25,11 +25,20 @@ impl WriteSurfaceBufferStage {
|
||||
}
|
||||
}
|
||||
|
||||
impl Stage for WriteSurfaceBufferStage {
|
||||
impl System for WriteSurfaceBufferSystem {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
"write_surfaced_buffer".into()
|
||||
}
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
renderer: Renderer { state, device, .. },
|
||||
renderer:
|
||||
Renderer {
|
||||
resources: state,
|
||||
device,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
@ -1,41 +1,78 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
sync::{
|
||||
mpsc,
|
||||
mpsc::{Receiver, Sender},
|
||||
},
|
||||
vec::IntoIter,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::io::{
|
||||
scheduler::Scheduler,
|
||||
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||
transferables::{DefaultTransferables, Transferables},
|
||||
TileRequest,
|
||||
use crate::{
|
||||
coords::WorldTileCoords, define_label, environment::OffscreenKernelEnvironment,
|
||||
io::scheduler::Scheduler, style::Style,
|
||||
};
|
||||
|
||||
define_label!(MessageTag);
|
||||
|
||||
impl MessageTag for u32 {
|
||||
fn dyn_clone(&self) -> Box<dyn MessageTag> {
|
||||
Box::new(*self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MessageError {
|
||||
#[error("the message did not contain the expected data")]
|
||||
CastError(Box<dyn Any>),
|
||||
}
|
||||
|
||||
/// The result of the tessellation of a tile. This is sent as a message from a worker to the caller
|
||||
/// of an [`AsyncProcedure`].
|
||||
///
|
||||
/// * `TessellatedLayer` contains the result of the tessellation for a specific layer.
|
||||
/// * `UnavailableLayer` is sent if a requested layer is not found.
|
||||
/// * `TileTessellated` is sent if processing of a tile finished.
|
||||
#[derive(Clone)]
|
||||
pub enum Message<T: Transferables> {
|
||||
TileTessellated(T::TileTessellated),
|
||||
LayerUnavailable(T::LayerUnavailable),
|
||||
LayerTessellated(T::LayerTessellated),
|
||||
#[derive(Debug)]
|
||||
pub struct Message {
|
||||
tag: &'static dyn MessageTag,
|
||||
transferable: Box<dyn Any + Send>,
|
||||
}
|
||||
|
||||
LayerIndexed(T::LayerIndexed),
|
||||
impl Message {
|
||||
pub fn new(tag: &'static dyn MessageTag, transferable: Box<dyn Any + Send>) -> Self {
|
||||
Self { tag, transferable }
|
||||
}
|
||||
|
||||
pub fn into_transferable<T: 'static>(self) -> Box<T> {
|
||||
self.transferable
|
||||
.downcast::<T>()
|
||||
.expect("message has wrong tag")
|
||||
}
|
||||
|
||||
pub fn has_tag(&self, tag: &'static dyn MessageTag) -> bool {
|
||||
self.tag == tag
|
||||
}
|
||||
|
||||
pub fn tag(&self) -> &'static dyn MessageTag {
|
||||
self.tag
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoMessage {
|
||||
fn into(self) -> Message;
|
||||
}
|
||||
|
||||
/// Inputs for an [`AsyncProcedure`]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum Input {
|
||||
TileRequest(TileRequest),
|
||||
TileRequest {
|
||||
coords: WorldTileCoords,
|
||||
style: Style, // TODO
|
||||
},
|
||||
NotYetImplemented, // TODO: Placeholder, should be removed when second input is added
|
||||
}
|
||||
|
||||
@ -46,11 +83,9 @@ pub enum SendError {
|
||||
}
|
||||
|
||||
/// Allows sending messages from workers to back to the caller.
|
||||
pub trait Context<T: Transferables, HC: HttpClient>: Send + 'static {
|
||||
pub trait Context: 'static {
|
||||
/// Send a message back to the caller.
|
||||
fn send(&self, data: Message<T>) -> Result<(), SendError>;
|
||||
|
||||
fn source_client(&self) -> &SourceClient<HC>;
|
||||
fn send<T: IntoMessage>(&self, message: T) -> Result<(), SendError>;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@ -79,13 +114,15 @@ pub enum CallError {
|
||||
Serialize(Box<dyn std::error::Error>),
|
||||
#[error("deserializing failed")]
|
||||
Deserialize(Box<dyn std::error::Error>),
|
||||
#[error("deserializing input failed")]
|
||||
DeserializeInput(Box<dyn std::error::Error>),
|
||||
}
|
||||
|
||||
/// Type definitions for asynchronous procedure calls. These functions can be called in an
|
||||
/// [`AsyncProcedureCall`]. Functions of this type are required to be statically available at
|
||||
/// compile time. It is explicitly not possible to use closures, as they would require special
|
||||
/// serialization which is currently not supported.
|
||||
pub type AsyncProcedure<C> = fn(input: Input, context: C) -> AsyncProcedureFuture;
|
||||
pub type AsyncProcedure<K, C> = fn(input: Input, context: C, kernel: K) -> AsyncProcedureFuture;
|
||||
|
||||
/// APCs define an interface for performing work asynchronously.
|
||||
/// This work can be implemented through procedures which can be called asynchronously, hence the
|
||||
@ -123,87 +160,123 @@ pub type AsyncProcedure<C> = fn(input: Input, context: C) -> AsyncProcedureFutur
|
||||
/// [`PassingAsyncProcedureCall`]. This implementation does not depend on shared-memory compared to
|
||||
/// [`SchedulerAsyncProcedureCall`]. In fact, on the web we are currently not depending on
|
||||
/// shared-memory because that feature is hidden behind feature flags in browsers
|
||||
/// (see [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).
|
||||
/// (see [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)).
|
||||
///
|
||||
///
|
||||
// TODO: Rename to AsyncProcedureCaller?
|
||||
pub trait AsyncProcedureCall<HC: HttpClient>: 'static {
|
||||
type Context: Context<Self::Transferables, HC> + Send;
|
||||
type Transferables: Transferables;
|
||||
pub trait AsyncProcedureCall<K: OffscreenKernelEnvironment>: 'static {
|
||||
type Context: Context + Send + Clone;
|
||||
|
||||
type ReceiveIterator<F: FnMut(&Message) -> bool>: Iterator<Item = Message>;
|
||||
|
||||
/// Try to receive a message non-blocking.
|
||||
fn receive(&self) -> Option<Message<Self::Transferables>>;
|
||||
fn receive<F: FnMut(&Message) -> bool>(&self, filter: F) -> Self::ReceiveIterator<F>;
|
||||
|
||||
/// Call an [`AsyncProcedure`] using some [`Input`]. This function is non-blocking and
|
||||
/// returns immediately.
|
||||
fn call(&self, input: Input, procedure: AsyncProcedure<Self::Context>)
|
||||
-> Result<(), CallError>;
|
||||
fn call(
|
||||
&self,
|
||||
input: Input,
|
||||
procedure: AsyncProcedure<K, Self::Context>,
|
||||
) -> Result<(), CallError>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SchedulerContext<T: Transferables, HC: HttpClient> {
|
||||
sender: Sender<Message<T>>,
|
||||
source_client: SourceClient<HC>,
|
||||
pub struct SchedulerContext {
|
||||
sender: Sender<Message>,
|
||||
}
|
||||
|
||||
impl<T: Transferables, HC: HttpClient> Context<T, HC> for SchedulerContext<T, HC> {
|
||||
fn send(&self, data: Message<T>) -> Result<(), SendError> {
|
||||
self.sender.send(data).map_err(|_e| SendError::Transmission)
|
||||
}
|
||||
|
||||
fn source_client(&self) -> &SourceClient<HC> {
|
||||
&self.source_client
|
||||
impl Context for SchedulerContext {
|
||||
fn send<T: IntoMessage>(&self, message: T) -> Result<(), SendError> {
|
||||
self.sender
|
||||
.send(message.into())
|
||||
.map_err(|_e| SendError::Transmission)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SchedulerAsyncProcedureCall<HC: HttpClient, S: Scheduler> {
|
||||
channel: (
|
||||
Sender<Message<DefaultTransferables>>,
|
||||
Receiver<Message<DefaultTransferables>>,
|
||||
),
|
||||
http_client: HC,
|
||||
pub struct SchedulerAsyncProcedureCall<K: OffscreenKernelEnvironment, S: Scheduler> {
|
||||
channel: (Sender<Message>, Receiver<Message>),
|
||||
buffer: RefCell<Vec<Message>>,
|
||||
scheduler: S,
|
||||
phantom_k: PhantomData<K>,
|
||||
}
|
||||
|
||||
impl<HC: HttpClient, S: Scheduler> SchedulerAsyncProcedureCall<HC, S> {
|
||||
pub fn new(http_client: HC, scheduler: S) -> Self {
|
||||
impl<K: OffscreenKernelEnvironment, S: Scheduler> SchedulerAsyncProcedureCall<K, S> {
|
||||
pub fn new(scheduler: S) -> Self {
|
||||
Self {
|
||||
channel: mpsc::channel(),
|
||||
http_client,
|
||||
buffer: RefCell::new(Vec::new()),
|
||||
phantom_k: PhantomData::default(),
|
||||
scheduler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<HC> for SchedulerAsyncProcedureCall<HC, S> {
|
||||
type Context = SchedulerContext<Self::Transferables, HC>;
|
||||
type Transferables = DefaultTransferables;
|
||||
impl<K: OffscreenKernelEnvironment, S: Scheduler> AsyncProcedureCall<K>
|
||||
for SchedulerAsyncProcedureCall<K, S>
|
||||
{
|
||||
type Context = SchedulerContext;
|
||||
type ReceiveIterator<F: FnMut(&Message) -> bool> = IntoIter<Message>;
|
||||
|
||||
fn receive(&self) -> Option<Message<DefaultTransferables>> {
|
||||
let transferred = self.channel.1.try_recv().ok()?;
|
||||
Some(transferred)
|
||||
fn receive<F: FnMut(&Message) -> bool>(&self, mut filter: F) -> Self::ReceiveIterator<F> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
let mut ret = Vec::new();
|
||||
|
||||
// FIXME tcs: Verify this!
|
||||
let mut index = 0usize;
|
||||
let mut max_len = buffer.len();
|
||||
while index < max_len {
|
||||
if filter(&buffer[index]) {
|
||||
ret.push(buffer.swap_remove(index));
|
||||
max_len -= 1;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
// TODO: (optimize) Using while instead of if means that we are processing all that is
|
||||
// TODO: available this might cause frame drops.
|
||||
while let Ok(message) = self.channel.1.try_recv() {
|
||||
tracing::debug!("Data reached main thread: {:?}", &message);
|
||||
log::debug!("Data reached main thread: {:?}", &message);
|
||||
|
||||
if filter(&message) {
|
||||
ret.push(message);
|
||||
} else {
|
||||
buffer.push(message)
|
||||
}
|
||||
}
|
||||
|
||||
ret.into_iter()
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
input: Input,
|
||||
procedure: AsyncProcedure<Self::Context>,
|
||||
procedure: AsyncProcedure<K, Self::Context>,
|
||||
) -> Result<(), CallError> {
|
||||
let sender = self.channel.0.clone();
|
||||
let client = self.http_client.clone(); // TODO (perf): do not clone each time
|
||||
|
||||
self.scheduler
|
||||
.schedule(move || async move {
|
||||
procedure(
|
||||
input,
|
||||
SchedulerContext {
|
||||
sender,
|
||||
source_client: SourceClient::new(HttpSourceClient::new(client)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
log::info!("Processing on thread: {:?}", std::thread::current().name());
|
||||
|
||||
procedure(input, SchedulerContext { sender }, K::create())
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
.map_err(|_e| CallError::Schedule)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use crate::io::apc::{Context, IntoMessage, SendError};
|
||||
|
||||
pub struct DummyContext;
|
||||
|
||||
impl Context for DummyContext {
|
||||
fn send<T: IntoMessage>(&self, _message: T) -> Result<(), SendError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ use geozero::{
|
||||
error::GeozeroError, geo_types::GeoWriter, ColumnValue, FeatureProcessor, GeomProcessor,
|
||||
PropertyProcessor,
|
||||
};
|
||||
use log::warn;
|
||||
use log::debug;
|
||||
use rstar::{Envelope, PointDistance, RTree, RTreeObject, AABB};
|
||||
|
||||
use crate::{
|
||||
@ -312,19 +312,19 @@ impl FeatureProcessor for IndexProcessor {
|
||||
IndexedGeometry::from_linestring(linestring, self.properties.take().unwrap())
|
||||
.unwrap(),
|
||||
),
|
||||
Some(Geometry::Point(_)) => warn!("Unsupported Point geometry in index"),
|
||||
Some(Geometry::Line(_)) => warn!("Unsupported Line geometry in index"),
|
||||
Some(Geometry::MultiPoint(_)) => warn!("Unsupported MultiPoint geometry in index"),
|
||||
Some(Geometry::Point(_)) => debug!("Unsupported Point geometry in index"),
|
||||
Some(Geometry::Line(_)) => debug!("Unsupported Line geometry in index"),
|
||||
Some(Geometry::MultiPoint(_)) => debug!("Unsupported MultiPoint geometry in index"),
|
||||
Some(Geometry::MultiLineString(_)) => {
|
||||
warn!("Unsupported MultiLineString geometry in index")
|
||||
debug!("Unsupported MultiLineString geometry in index")
|
||||
}
|
||||
Some(Geometry::MultiPolygon(_)) => warn!("Unsupported MultiPolygon geometry in index"),
|
||||
Some(Geometry::MultiPolygon(_)) => debug!("Unsupported MultiPolygon geometry in index"),
|
||||
Some(Geometry::GeometryCollection(_)) => {
|
||||
warn!("Unsupported GeometryCollection geometry in index")
|
||||
debug!("Unsupported GeometryCollection geometry in index")
|
||||
}
|
||||
Some(Geometry::Rect(_)) => warn!("Unsupported Rect geometry in index"),
|
||||
Some(Geometry::Triangle(_)) => warn!("Unsupported Triangle geometry in index"),
|
||||
None => warn!("No geometry in index"),
|
||||
Some(Geometry::Rect(_)) => debug!("Unsupported Rect geometry in index"),
|
||||
Some(Geometry::Triangle(_)) => debug!("Unsupported Triangle geometry in index"),
|
||||
None => debug!("No geometry in index"),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -1,33 +1,11 @@
|
||||
//! Handles IO related processing as well as multithreading.
|
||||
|
||||
use std::{collections::HashSet, fmt};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::coords::WorldTileCoords;
|
||||
|
||||
pub mod apc;
|
||||
pub mod geometry_index;
|
||||
pub mod pipeline;
|
||||
pub mod scheduler;
|
||||
pub mod source_client;
|
||||
#[cfg(feature = "embed-static-tiles")]
|
||||
pub mod static_tile_fetcher;
|
||||
pub mod tile_pipelines;
|
||||
pub mod tile_repository;
|
||||
pub mod transferables;
|
||||
|
||||
pub use geozero::mvt::tile::Layer as RawLayer;
|
||||
|
||||
/// A request for a tile at the given coordinates and in the given layers.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct TileRequest {
|
||||
pub coords: WorldTileCoords,
|
||||
pub layers: HashSet<String>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for TileRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "TileRequest({}, {:?})", &self.coords, &self.layers)
|
||||
}
|
||||
}
|
||||
pub mod apc;
|
||||
pub mod geometry_index;
|
||||
pub mod scheduler;
|
||||
pub mod source_client;
|
||||
pub mod source_type;
|
||||
#[cfg(feature = "embed-static-tiles")]
|
||||
pub mod static_tile_fetcher;
|
||||
|
||||
@ -1,288 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use downcast_rs::Downcast;
|
||||
use geozero::mvt::tile;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
io::{apc::SendError, geometry_index::IndexedGeometry},
|
||||
render::ShaderVertex,
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PipelineError {
|
||||
/// Sending of results failed
|
||||
#[error("sending data back from pipeline failed")]
|
||||
SendError(SendError),
|
||||
/// Error during processing of the pipeline
|
||||
#[error("processing data in pipeline failed")]
|
||||
Processing(Box<dyn std::error::Error>),
|
||||
}
|
||||
|
||||
/// Processes events which happen during the pipeline execution
|
||||
pub trait PipelineProcessor: Downcast {
|
||||
fn tile_finished(&mut self, _coords: &WorldTileCoords) -> Result<(), PipelineError> {
|
||||
Ok(())
|
||||
}
|
||||
fn layer_unavailable(
|
||||
&mut self,
|
||||
_coords: &WorldTileCoords,
|
||||
_layer_name: &str,
|
||||
) -> Result<(), PipelineError> {
|
||||
Ok(())
|
||||
}
|
||||
fn layer_tesselation_finished(
|
||||
&mut self,
|
||||
_coords: &WorldTileCoords,
|
||||
_buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
_feature_indices: Vec<u32>,
|
||||
_layer_data: tile::Layer,
|
||||
) -> Result<(), PipelineError> {
|
||||
Ok(())
|
||||
}
|
||||
fn layer_indexing_finished(
|
||||
&mut self,
|
||||
_coords: &WorldTileCoords,
|
||||
_geometries: Vec<IndexedGeometry<f64>>,
|
||||
) -> Result<(), PipelineError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Context which is available to each step within a [`DataPipeline`]
|
||||
pub struct PipelineContext {
|
||||
processor: Box<dyn PipelineProcessor>,
|
||||
}
|
||||
|
||||
impl PipelineContext {
|
||||
pub fn new<P>(processor: P) -> Self
|
||||
where
|
||||
P: PipelineProcessor,
|
||||
{
|
||||
Self {
|
||||
processor: Box::new(processor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_processor<P>(self) -> Option<Box<P>>
|
||||
where
|
||||
P: PipelineProcessor,
|
||||
{
|
||||
self.processor.into_any().downcast::<P>().ok()
|
||||
}
|
||||
|
||||
pub fn processor_mut(&mut self) -> &mut dyn PipelineProcessor {
|
||||
self.processor.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Processable {
|
||||
type Input;
|
||||
type Output;
|
||||
|
||||
fn process(
|
||||
&self,
|
||||
input: Self::Input,
|
||||
context: &mut PipelineContext,
|
||||
) -> Result<Self::Output, PipelineError>;
|
||||
}
|
||||
|
||||
/// A pipeline which consists of multiple steps. Steps are [`Processable`] workloads. Later steps
|
||||
/// depend on previous ones.
|
||||
pub struct DataPipeline<P, N>
|
||||
where
|
||||
P: Processable,
|
||||
N: Processable<Input = P::Output>,
|
||||
{
|
||||
step: P,
|
||||
next_step: N,
|
||||
}
|
||||
|
||||
impl<P, N> DataPipeline<P, N>
|
||||
where
|
||||
P: Processable,
|
||||
N: Processable<Input = P::Output>,
|
||||
{
|
||||
pub fn new(step: P, next_step: N) -> Self {
|
||||
Self { step, next_step }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, N> Processable for DataPipeline<P, N>
|
||||
where
|
||||
P: Processable,
|
||||
N: Processable<Input = P::Output>,
|
||||
{
|
||||
type Input = P::Input;
|
||||
type Output = N::Output;
|
||||
|
||||
fn process(
|
||||
&self,
|
||||
input: Self::Input,
|
||||
context: &mut PipelineContext,
|
||||
) -> Result<Self::Output, PipelineError> {
|
||||
let output = self.step.process(input, context)?;
|
||||
self.next_step.process(output, context)
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the end of a [`DataPipeline`]
|
||||
pub struct PipelineEnd<I> {
|
||||
phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<I> Default for PipelineEnd<I> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Processable for PipelineEnd<I> {
|
||||
type Input = I;
|
||||
type Output = I;
|
||||
|
||||
fn process(
|
||||
&self,
|
||||
input: Self::Input,
|
||||
_context: &mut PipelineContext,
|
||||
) -> Result<Self::Output, PipelineError> {
|
||||
Ok(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> Processable for &fn(input: I, context: &mut PipelineContext) -> O {
|
||||
type Input = I;
|
||||
type Output = O;
|
||||
|
||||
fn process(
|
||||
&self,
|
||||
input: Self::Input,
|
||||
context: &mut PipelineContext,
|
||||
) -> Result<Self::Output, PipelineError> {
|
||||
Ok((self)(input, context))
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> Processable for fn(input: I, context: &mut PipelineContext) -> O {
|
||||
type Input = I;
|
||||
type Output = O;
|
||||
|
||||
fn process(
|
||||
&self,
|
||||
input: Self::Input,
|
||||
context: &mut PipelineContext,
|
||||
) -> Result<Self::Output, PipelineError> {
|
||||
Ok((self)(input, context))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implementing Processable directly on Fn is not possible for some strange reason:
|
||||
// https://github.com/rust-lang/rust/issues/25041
|
||||
pub struct ClosureProcessable<F, I, O>
|
||||
where
|
||||
F: Fn(I, &mut PipelineContext) -> O,
|
||||
{
|
||||
func: F,
|
||||
phantom_i: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<F, I, O> From<F> for ClosureProcessable<F, I, O>
|
||||
where
|
||||
F: Fn(I, &mut PipelineContext) -> O,
|
||||
{
|
||||
fn from(func: F) -> Self {
|
||||
ClosureProcessable {
|
||||
func,
|
||||
phantom_i: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, I, O> Processable for ClosureProcessable<F, I, O>
|
||||
where
|
||||
F: Fn(I, &mut PipelineContext) -> O,
|
||||
{
|
||||
type Input = I;
|
||||
type Output = O;
|
||||
|
||||
fn process(
|
||||
&self,
|
||||
input: Self::Input,
|
||||
context: &mut PipelineContext,
|
||||
) -> Result<Self::Output, PipelineError> {
|
||||
Ok((self.func)(input, context))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::io::pipeline::{
|
||||
ClosureProcessable, DataPipeline, PipelineContext, PipelineEnd, PipelineProcessor,
|
||||
Processable,
|
||||
};
|
||||
|
||||
pub struct DummyPipelineProcessor;
|
||||
|
||||
impl PipelineProcessor for DummyPipelineProcessor {}
|
||||
|
||||
fn add_one(input: u32, _context: &mut PipelineContext) -> u8 {
|
||||
input as u8 + 1
|
||||
}
|
||||
|
||||
fn add_two(input: u8, _context: &mut PipelineContext) -> u32 {
|
||||
input as u32 + 2
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fn_pointer() {
|
||||
let mut context = PipelineContext::new(DummyPipelineProcessor);
|
||||
let output: u32 = DataPipeline::new(
|
||||
add_two as fn(u8, &mut PipelineContext) -> u32,
|
||||
PipelineEnd::default(),
|
||||
)
|
||||
.process(5u8, &mut context)
|
||||
.unwrap();
|
||||
assert_eq!(output, 7);
|
||||
|
||||
let output: u32 = DataPipeline::new(
|
||||
add_one as fn(u32, &mut PipelineContext) -> u8,
|
||||
DataPipeline::new(
|
||||
add_two as fn(u8, &mut PipelineContext) -> u32,
|
||||
PipelineEnd::default(),
|
||||
),
|
||||
)
|
||||
.process(5u32, &mut context)
|
||||
.unwrap();
|
||||
assert_eq!(output, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_closure() {
|
||||
let mut context = PipelineContext::new(DummyPipelineProcessor);
|
||||
let outer_value = 3;
|
||||
|
||||
// using from()
|
||||
let closure =
|
||||
ClosureProcessable::from(|input: u8, _context: &mut PipelineContext| -> u32 {
|
||||
input as u32 + 2 + outer_value
|
||||
});
|
||||
let output: u32 = DataPipeline::new(closure, PipelineEnd::default())
|
||||
.process(5u8, &mut context)
|
||||
.unwrap();
|
||||
assert_eq!(output, 10);
|
||||
|
||||
// with into()
|
||||
let output: u32 = DataPipeline::<ClosureProcessable<_, u8, u32>, _>::new(
|
||||
(|input: u8, _context: &mut PipelineContext| -> u32 { input as u32 + 2 + outer_value })
|
||||
.into(),
|
||||
PipelineEnd::<u32>::default(),
|
||||
)
|
||||
.process(5u8, &mut context)
|
||||
.unwrap();
|
||||
assert_eq!(output, 10);
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
use async_trait::async_trait;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{coords::WorldTileCoords, style::source::TileAddressingScheme};
|
||||
use crate::{coords::WorldTileCoords, io::source_type::SourceType};
|
||||
|
||||
/// A closure that returns a HTTP client.
|
||||
pub type HTTPClientFactory<HC> = dyn Fn() -> HC;
|
||||
@ -41,7 +41,7 @@ pub struct SourceClient<HC>
|
||||
where
|
||||
HC: HttpClient,
|
||||
{
|
||||
http: HttpSourceClient<HC>, // TODO: mbtiles: Mbtiles
|
||||
http: HttpSourceClient<HC>,
|
||||
}
|
||||
|
||||
impl<HC> SourceClient<HC>
|
||||
@ -52,8 +52,12 @@ where
|
||||
Self { http }
|
||||
}
|
||||
|
||||
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, SourceFetchError> {
|
||||
self.http.fetch(coords).await
|
||||
pub async fn fetch(
|
||||
&self,
|
||||
coords: &WorldTileCoords,
|
||||
source_type: &SourceType,
|
||||
) -> Result<Vec<u8>, SourceFetchError> {
|
||||
self.http.fetch(coords, source_type).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,18 +71,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, SourceFetchError> {
|
||||
let tile_coords = coords.into_tile(TileAddressingScheme::XYZ).unwrap();
|
||||
pub async fn fetch(
|
||||
&self,
|
||||
coords: &WorldTileCoords,
|
||||
source_type: &SourceType,
|
||||
) -> Result<Vec<u8>, SourceFetchError> {
|
||||
self.inner_client
|
||||
.fetch(
|
||||
format!(
|
||||
"https://maps.tuerantuer.org/europe_germany/{z}/{x}/{y}",
|
||||
x = tile_coords.x,
|
||||
y = tile_coords.y,
|
||||
z = tile_coords.z
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.fetch(source_type.format(coords).as_str())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
92
maplibre/src/io/source_type.rs
Normal file
92
maplibre/src/io/source_type.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use crate::{coords::WorldTileCoords, style::source::TileAddressingScheme};
|
||||
|
||||
/// Represents a source from which the vector tile are fetched.
|
||||
#[derive(Clone)]
|
||||
pub struct TessellateSource {
|
||||
pub url: String,
|
||||
pub filetype: String,
|
||||
}
|
||||
|
||||
impl TessellateSource {
|
||||
pub fn new(url: &str, filetype: &str) -> Self {
|
||||
Self {
|
||||
url: url.to_string(),
|
||||
filetype: filetype.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format(&self, coords: &WorldTileCoords) -> String {
|
||||
let tile_coords = coords.into_tile(TileAddressingScheme::XYZ).unwrap();
|
||||
format!(
|
||||
"{url}/{z}/{x}/{y}.{filetype}",
|
||||
url = self.url,
|
||||
z = tile_coords.z,
|
||||
x = tile_coords.x,
|
||||
y = tile_coords.y,
|
||||
filetype = self.filetype,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TessellateSource {
|
||||
fn default() -> Self {
|
||||
Self::new("https://maps.tuerantuer.org/europe_germany", "pbf")
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a source from which the raster tile are fetched.
|
||||
#[derive(Clone)]
|
||||
pub struct RasterSource {
|
||||
pub url: String,
|
||||
pub filetype: String,
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
impl RasterSource {
|
||||
pub fn new(url: &str, filetype: &str, key: &str) -> Self {
|
||||
Self {
|
||||
url: url.to_string(),
|
||||
filetype: filetype.to_string(),
|
||||
key: key.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format(&self, coords: &WorldTileCoords) -> String {
|
||||
let tile_coords = coords.into_tile(TileAddressingScheme::XYZ).unwrap();
|
||||
format!(
|
||||
"{url}/{z}/{x}/{y}.{filetype}?key={key}",
|
||||
url = self.url,
|
||||
z = tile_coords.z,
|
||||
x = tile_coords.x,
|
||||
y = tile_coords.y,
|
||||
filetype = self.filetype,
|
||||
key = self.key,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RasterSource {
|
||||
fn default() -> Self {
|
||||
Self::new(
|
||||
"https://api.maptiler.com/tiles/satellite-v2",
|
||||
"jpg",
|
||||
"qnePkfbGpMsLCi3KFBs3",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the tiles' different types of source.
|
||||
#[derive(Clone)]
|
||||
pub enum SourceType {
|
||||
Raster(RasterSource),
|
||||
Tessellate(TessellateSource),
|
||||
}
|
||||
|
||||
impl SourceType {
|
||||
pub fn format(&self, coords: &WorldTileCoords) -> String {
|
||||
match self {
|
||||
SourceType::Raster(raster_source) => raster_source.format(coords),
|
||||
SourceType::Tessellate(tessellate_source) => tessellate_source.format(coords),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@ use include_dir::include_dir;
|
||||
use include_dir::Dir;
|
||||
|
||||
use crate::coords::TileCoords;
|
||||
|
||||
#[cfg(static_tiles_found)]
|
||||
static TILES: Dir = include_dir!("$OUT_DIR/extracted-tiles");
|
||||
#[cfg(not(static_tiles_found))]
|
||||
|
||||
@ -1,177 +0,0 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use geozero::{mvt::Message, GeozeroDatasource};
|
||||
|
||||
use crate::{
|
||||
io::{
|
||||
geometry_index::IndexProcessor,
|
||||
pipeline::{DataPipeline, PipelineContext, PipelineEnd, PipelineError, Processable},
|
||||
TileRequest,
|
||||
},
|
||||
tessellation::{zero_tessellator::ZeroTessellator, IndexDataType},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ParseTile;
|
||||
|
||||
impl Processable for ParseTile {
|
||||
type Input = (TileRequest, Box<[u8]>);
|
||||
type Output = (TileRequest, geozero::mvt::Tile);
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn process(
|
||||
&self,
|
||||
(tile_request, data): Self::Input,
|
||||
_context: &mut PipelineContext,
|
||||
) -> Result<Self::Output, PipelineError> {
|
||||
let tile = geozero::mvt::Tile::decode(data.as_ref()).expect("failed to load tile");
|
||||
Ok((tile_request, tile))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct IndexLayer;
|
||||
|
||||
impl Processable for IndexLayer {
|
||||
type Input = (TileRequest, geozero::mvt::Tile);
|
||||
type Output = (TileRequest, geozero::mvt::Tile);
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn process(
|
||||
&self,
|
||||
(tile_request, mut tile): Self::Input,
|
||||
context: &mut PipelineContext,
|
||||
) -> Result<Self::Output, PipelineError> {
|
||||
let mut index = IndexProcessor::new();
|
||||
|
||||
for layer in &mut tile.layers {
|
||||
layer.process(&mut index).unwrap();
|
||||
}
|
||||
|
||||
for layer in &mut tile.layers {
|
||||
layer.process(&mut index).unwrap();
|
||||
}
|
||||
|
||||
context
|
||||
.processor_mut()
|
||||
.layer_indexing_finished(&tile_request.coords, index.get_geometries())?;
|
||||
Ok((tile_request, tile))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TessellateLayer;
|
||||
|
||||
impl Processable for TessellateLayer {
|
||||
type Input = (TileRequest, geozero::mvt::Tile);
|
||||
type Output = (TileRequest, geozero::mvt::Tile);
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn process(
|
||||
&self,
|
||||
(tile_request, mut tile): Self::Input,
|
||||
context: &mut PipelineContext,
|
||||
) -> Result<Self::Output, PipelineError> {
|
||||
let coords = &tile_request.coords;
|
||||
|
||||
for layer in &mut tile.layers {
|
||||
let cloned_layer = layer.clone();
|
||||
let layer_name: &str = &cloned_layer.name;
|
||||
if !tile_request.layers.contains(layer_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::info!("layer {} at {} ready", layer_name, coords);
|
||||
|
||||
let mut tessellator = ZeroTessellator::<IndexDataType>::default();
|
||||
if let Err(e) = layer.process(&mut tessellator) {
|
||||
context
|
||||
.processor_mut()
|
||||
.layer_unavailable(coords, layer_name)?;
|
||||
|
||||
tracing::error!(
|
||||
"layer {} at {} tesselation failed {:?}",
|
||||
layer_name,
|
||||
&coords,
|
||||
e
|
||||
);
|
||||
} else {
|
||||
context.processor_mut().layer_tesselation_finished(
|
||||
coords,
|
||||
tessellator.buffer.into(),
|
||||
tessellator.feature_indices,
|
||||
cloned_layer,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let available_layers: HashSet<_> = tile
|
||||
.layers
|
||||
.iter()
|
||||
.map(|layer| layer.name.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for missing_layer in tile_request.layers.difference(&available_layers) {
|
||||
context
|
||||
.processor_mut()
|
||||
.layer_unavailable(coords, missing_layer)?;
|
||||
|
||||
tracing::info!(
|
||||
"requested layer {} at {} not found in tile",
|
||||
missing_layer,
|
||||
&coords
|
||||
);
|
||||
}
|
||||
|
||||
tracing::info!("tile tessellated at {} finished", &tile_request.coords);
|
||||
|
||||
context
|
||||
.processor_mut()
|
||||
.tile_finished(&tile_request.coords)?;
|
||||
|
||||
Ok((tile_request, tile))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_vector_tile_pipeline() -> impl Processable<Input = <ParseTile as Processable>::Input> {
|
||||
DataPipeline::new(
|
||||
ParseTile,
|
||||
DataPipeline::new(
|
||||
TessellateLayer,
|
||||
DataPipeline::new(IndexLayer, PipelineEnd::default()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::build_vector_tile_pipeline;
|
||||
use crate::{
|
||||
coords::ZoomLevel,
|
||||
io::{
|
||||
pipeline::{PipelineContext, PipelineProcessor, Processable},
|
||||
TileRequest,
|
||||
},
|
||||
};
|
||||
pub struct DummyPipelineProcessor;
|
||||
|
||||
impl PipelineProcessor for DummyPipelineProcessor {}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test() {
|
||||
let mut context = PipelineContext::new(DummyPipelineProcessor);
|
||||
|
||||
let pipeline = build_vector_tile_pipeline();
|
||||
let _output = pipeline.process(
|
||||
(
|
||||
TileRequest {
|
||||
coords: (0, 0, ZoomLevel::default()).into(),
|
||||
layers: Default::default(),
|
||||
},
|
||||
Box::new([0]), // TODO: Add proper tile byte array
|
||||
),
|
||||
&mut context,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,228 +0,0 @@
|
||||
//! Tile cache.
|
||||
|
||||
use std::collections::{btree_map, btree_map::Entry, BTreeMap};
|
||||
|
||||
use bytemuck::Pod;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
coords::{Quadkey, WorldTileCoords},
|
||||
render::{
|
||||
resource::{BufferPool, Queue},
|
||||
ShaderVertex,
|
||||
},
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
};
|
||||
|
||||
/// A layer which is stored for future use.
|
||||
#[derive(Clone)]
|
||||
pub enum StoredLayer {
|
||||
UnavailableLayer {
|
||||
coords: WorldTileCoords,
|
||||
layer_name: String,
|
||||
},
|
||||
TessellatedLayer {
|
||||
coords: WorldTileCoords,
|
||||
layer_name: String,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
/// Holds for each feature the count of indices.
|
||||
feature_indices: Vec<u32>,
|
||||
},
|
||||
}
|
||||
|
||||
impl StoredLayer {
|
||||
pub fn get_coords(&self) -> WorldTileCoords {
|
||||
match self {
|
||||
StoredLayer::UnavailableLayer { coords, .. } => *coords,
|
||||
StoredLayer::TessellatedLayer { coords, .. } => *coords,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer_name(&self) -> &str {
|
||||
match self {
|
||||
StoredLayer::UnavailableLayer { layer_name, .. } => layer_name.as_str(),
|
||||
StoredLayer::TessellatedLayer { layer_name, .. } => layer_name.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum TileStatus {
|
||||
Pending,
|
||||
Failed,
|
||||
Success,
|
||||
}
|
||||
|
||||
/// Stores multiple [StoredLayers](StoredLayer).
|
||||
#[derive(Clone)]
|
||||
pub struct StoredTile {
|
||||
coords: WorldTileCoords,
|
||||
layers: Vec<StoredLayer>,
|
||||
status: TileStatus,
|
||||
}
|
||||
|
||||
impl StoredTile {
|
||||
pub fn pending(coords: WorldTileCoords) -> Self {
|
||||
Self {
|
||||
coords,
|
||||
layers: vec![],
|
||||
status: TileStatus::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn success(coords: WorldTileCoords, layers: Vec<StoredLayer>) -> Self {
|
||||
Self {
|
||||
coords,
|
||||
layers,
|
||||
status: TileStatus::Success,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn failed(coords: WorldTileCoords) -> Self {
|
||||
Self {
|
||||
coords,
|
||||
layers: vec![],
|
||||
status: TileStatus::Failed,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MarkError {
|
||||
#[error("no pending tile at coords")]
|
||||
NoPendingTile,
|
||||
#[error("unable to construct quadkey")]
|
||||
QuadKey,
|
||||
}
|
||||
|
||||
/// Stores and provides access to a quad tree of cached tiles with world tile coords.
|
||||
#[derive(Default)]
|
||||
pub struct TileRepository {
|
||||
tree: BTreeMap<Quadkey, StoredTile>,
|
||||
}
|
||||
|
||||
impl TileRepository {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tree: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a tessellated layer into the quad tree at its world tile coords.
|
||||
/// If the space is vacant, the tessellated layer is inserted into a new
|
||||
/// [crate::io::tile_repository::StoredLayer].
|
||||
/// If the space is occupied, the tessellated layer is added to the current
|
||||
/// [crate::io::tile_repository::StoredLayer].
|
||||
pub fn put_layer(&mut self, layer: StoredLayer) {
|
||||
if let Some(entry) = layer
|
||||
.get_coords()
|
||||
.build_quad_key()
|
||||
.map(|key| self.tree.entry(key))
|
||||
{
|
||||
match entry {
|
||||
btree_map::Entry::Vacant(_entry) => {
|
||||
panic!("Can not add a tessellated layer at {} if no request has been started before. \
|
||||
We might received a tile which was not requested.", layer.get_coords())
|
||||
}
|
||||
btree_map::Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().layers.push(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of tessellated layers at the given world tile coords. None if tile is
|
||||
/// missing from the cache.
|
||||
pub fn iter_layers_at(
|
||||
&self,
|
||||
coords: &WorldTileCoords,
|
||||
) -> Option<impl Iterator<Item = &StoredLayer> + '_> {
|
||||
coords
|
||||
.build_quad_key()
|
||||
.and_then(|key| self.tree.get(&key))
|
||||
.and_then(|tile| {
|
||||
if tile.status == TileStatus::Success {
|
||||
Some(tile)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|tile| tile.layers.iter())
|
||||
}
|
||||
|
||||
/// Returns the list of tessellated layers at the given world tile coords, which are loaded in
|
||||
/// the BufferPool
|
||||
pub fn iter_loaded_layers_at<Q: Queue<B>, B, V: Pod, I: Pod, TM: Pod, FM: Pod>(
|
||||
&self,
|
||||
buffer_pool: &BufferPool<Q, B, V, I, TM, FM>,
|
||||
coords: &WorldTileCoords,
|
||||
) -> Option<Vec<&StoredLayer>> {
|
||||
let loaded_layers = buffer_pool.get_loaded_layers_at(coords).unwrap_or_default();
|
||||
|
||||
self.iter_layers_at(coords).map(|layers| {
|
||||
layers
|
||||
.filter(|result| !loaded_layers.contains(&result.layer_name()))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks fetching of a tile has been started
|
||||
pub fn is_tile_pending_or_done(&self, coords: &WorldTileCoords) -> bool {
|
||||
if coords
|
||||
.build_quad_key()
|
||||
.and_then(|key| self.tree.get(&key))
|
||||
.is_some()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Mark the tile at `coords` pending in this tile repository.
|
||||
pub fn mark_tile_pending(&mut self, coords: WorldTileCoords) -> Result<(), MarkError> {
|
||||
let Some(key) = coords.build_quad_key() else { return Err(MarkError::QuadKey); };
|
||||
|
||||
match self.tree.entry(key) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(StoredTile::pending(coords));
|
||||
}
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().status = TileStatus::Pending;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark the tile at `coords` succeeded in this tile repository. Only succeeds if there is a
|
||||
/// pending tile at `coords`.
|
||||
pub fn mark_tile_succeeded(&mut self, coords: &WorldTileCoords) -> Result<(), MarkError> {
|
||||
self.mark_tile(coords, TileStatus::Success)
|
||||
}
|
||||
|
||||
/// Mark the tile at `coords` failed in this tile repository. Only succeeds if there is a
|
||||
/// pending tile at `coords`.
|
||||
pub fn mark_tile_failed(&mut self, coords: &WorldTileCoords) -> Result<(), MarkError> {
|
||||
self.mark_tile(coords, TileStatus::Failed)
|
||||
}
|
||||
|
||||
fn mark_tile(&mut self, coords: &WorldTileCoords, status: TileStatus) -> Result<(), MarkError> {
|
||||
let Some(key) = coords.build_quad_key() else { return Err(MarkError::QuadKey); };
|
||||
|
||||
if let Entry::Occupied(mut entry) = self.tree.entry(key) {
|
||||
entry.get_mut().status = status;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(MarkError::NoPendingTile)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put_tile(&mut self, tile: StoredTile) {
|
||||
if let Some(key) = tile.coords.build_quad_key() {
|
||||
self.tree.insert(key, tile);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.tree.clear();
|
||||
}
|
||||
}
|
||||
@ -1,172 +0,0 @@
|
||||
use geozero::mvt::tile::Layer;
|
||||
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
io::{geometry_index::TileIndex, tile_repository::StoredLayer},
|
||||
render::ShaderVertex,
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
};
|
||||
|
||||
pub trait TileTessellated: Send {
|
||||
fn build_from(coords: WorldTileCoords) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn coords(&self) -> WorldTileCoords;
|
||||
}
|
||||
|
||||
pub trait LayerUnavailable: Send {
|
||||
fn build_from(coords: WorldTileCoords, layer_name: String) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn coords(&self) -> WorldTileCoords;
|
||||
fn layer_name(&self) -> &str;
|
||||
|
||||
fn to_stored_layer(self) -> StoredLayer;
|
||||
}
|
||||
|
||||
pub trait LayerTessellated: Send {
|
||||
fn build_from(
|
||||
coords: WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: Layer,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn coords(&self) -> WorldTileCoords;
|
||||
|
||||
fn is_empty(&self) -> bool;
|
||||
|
||||
fn to_stored_layer(self) -> StoredLayer;
|
||||
}
|
||||
|
||||
pub trait LayerIndexed: Send {
|
||||
fn build_from(coords: WorldTileCoords, index: TileIndex) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn coords(&self) -> WorldTileCoords;
|
||||
|
||||
fn to_tile_index(self) -> TileIndex;
|
||||
}
|
||||
|
||||
pub struct DefaultTileTessellated {
|
||||
pub coords: WorldTileCoords,
|
||||
}
|
||||
|
||||
impl TileTessellated for DefaultTileTessellated {
|
||||
fn build_from(coords: WorldTileCoords) -> Self {
|
||||
Self { coords }
|
||||
}
|
||||
|
||||
fn coords(&self) -> WorldTileCoords {
|
||||
self.coords
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultLayerUnavailable {
|
||||
pub coords: WorldTileCoords,
|
||||
pub layer_name: String,
|
||||
}
|
||||
|
||||
impl LayerUnavailable for DefaultLayerUnavailable {
|
||||
fn build_from(coords: WorldTileCoords, layer_name: String) -> Self {
|
||||
Self { coords, layer_name }
|
||||
}
|
||||
|
||||
fn coords(&self) -> WorldTileCoords {
|
||||
self.coords
|
||||
}
|
||||
|
||||
fn layer_name(&self) -> &str {
|
||||
&self.layer_name
|
||||
}
|
||||
|
||||
fn to_stored_layer(self) -> StoredLayer {
|
||||
StoredLayer::UnavailableLayer {
|
||||
coords: self.coords,
|
||||
layer_name: self.layer_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DefaultLayerTesselated {
|
||||
pub coords: WorldTileCoords,
|
||||
pub buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
/// Holds for each feature the count of indices.
|
||||
pub feature_indices: Vec<u32>,
|
||||
pub layer_data: Layer, // FIXME (perf): Introduce a better structure for this
|
||||
}
|
||||
|
||||
impl LayerTessellated for DefaultLayerTesselated {
|
||||
fn build_from(
|
||||
coords: WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: Layer,
|
||||
) -> Self {
|
||||
Self {
|
||||
coords,
|
||||
buffer,
|
||||
feature_indices,
|
||||
layer_data,
|
||||
}
|
||||
}
|
||||
|
||||
fn coords(&self) -> WorldTileCoords {
|
||||
self.coords
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.buffer.usable_indices == 0
|
||||
}
|
||||
|
||||
fn to_stored_layer(self) -> StoredLayer {
|
||||
StoredLayer::TessellatedLayer {
|
||||
coords: self.coords,
|
||||
layer_name: self.layer_data.name,
|
||||
buffer: self.buffer,
|
||||
feature_indices: self.feature_indices,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultLayerIndexed {
|
||||
coords: WorldTileCoords,
|
||||
index: TileIndex,
|
||||
}
|
||||
|
||||
impl LayerIndexed for DefaultLayerIndexed {
|
||||
fn build_from(coords: WorldTileCoords, index: TileIndex) -> Self {
|
||||
Self { coords, index }
|
||||
}
|
||||
|
||||
fn coords(&self) -> WorldTileCoords {
|
||||
self.coords
|
||||
}
|
||||
|
||||
fn to_tile_index(self) -> TileIndex {
|
||||
self.index
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Transferables: 'static {
|
||||
type TileTessellated: TileTessellated;
|
||||
type LayerUnavailable: LayerUnavailable;
|
||||
type LayerTessellated: LayerTessellated;
|
||||
type LayerIndexed: LayerIndexed;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct DefaultTransferables;
|
||||
|
||||
impl Transferables for DefaultTransferables {
|
||||
type TileTessellated = DefaultTileTessellated;
|
||||
type LayerUnavailable = DefaultLayerUnavailable;
|
||||
type LayerTessellated = DefaultLayerTesselated;
|
||||
type LayerIndexed = DefaultLayerIndexed;
|
||||
}
|
||||
@ -18,6 +18,11 @@
|
||||
|
||||
#![deny(unused_imports)]
|
||||
|
||||
extern crate core;
|
||||
|
||||
// Export tile format
|
||||
pub use geozero::mvt::tile; // Used in transferables.rs in web/singlethreaded
|
||||
|
||||
// Internal modules
|
||||
pub(crate) mod tessellation;
|
||||
|
||||
@ -35,8 +40,6 @@ pub mod util;
|
||||
pub mod window;
|
||||
// Exposed because of doc-strings
|
||||
pub mod schedule;
|
||||
// Exposed because of SharedThreadState
|
||||
pub mod stages;
|
||||
|
||||
pub mod environment;
|
||||
|
||||
@ -46,7 +49,11 @@ pub mod benchmarking;
|
||||
pub mod event_loop;
|
||||
pub mod kernel;
|
||||
pub mod map;
|
||||
pub mod world;
|
||||
pub mod plugin;
|
||||
pub mod tcs;
|
||||
pub mod view_state;
|
||||
|
||||
// Export tile format
|
||||
pub use geozero::mvt::tile;
|
||||
// Plugins
|
||||
pub mod debug;
|
||||
pub mod raster;
|
||||
pub mod vector;
|
||||
|
||||
@ -4,23 +4,22 @@ use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::{LatLon, Zoom},
|
||||
coords::{LatLon, WorldCoords, Zoom},
|
||||
environment::Environment,
|
||||
kernel::Kernel,
|
||||
plugin::Plugin,
|
||||
render::{
|
||||
builder::{
|
||||
InitializationResult, InitializedRenderer, RendererBuilder, UninitializedRenderer,
|
||||
},
|
||||
create_default_render_graph,
|
||||
error::RenderError,
|
||||
graph::RenderGraphError,
|
||||
register_default_render_stages,
|
||||
},
|
||||
schedule::{Schedule, Stage},
|
||||
stages::register_stages,
|
||||
style::Style,
|
||||
tcs::world::World,
|
||||
view_state::ViewState,
|
||||
window::{HeadedMapWindow, MapWindow, MapWindowConfig},
|
||||
world::World,
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@ -34,7 +33,7 @@ pub enum MapError {
|
||||
DeviceInit(RenderError),
|
||||
}
|
||||
|
||||
pub enum MapContextState {
|
||||
pub enum CurrentMapContext {
|
||||
Ready(MapContext),
|
||||
Pending {
|
||||
style: Style,
|
||||
@ -45,8 +44,10 @@ pub enum MapContextState {
|
||||
pub struct Map<E: Environment> {
|
||||
kernel: Rc<Kernel<E>>,
|
||||
schedule: Schedule,
|
||||
map_context: MapContextState,
|
||||
map_context: CurrentMapContext,
|
||||
window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
|
||||
|
||||
plugins: Vec<Box<dyn Plugin<E>>>,
|
||||
}
|
||||
|
||||
impl<E: Environment> Map<E>
|
||||
@ -57,34 +58,31 @@ where
|
||||
style: Style,
|
||||
kernel: Kernel<E>,
|
||||
renderer_builder: RendererBuilder,
|
||||
plugins: Vec<Box<dyn Plugin<E>>>,
|
||||
) -> Result<Self, MapError> {
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
let graph = create_default_render_graph().unwrap(); // TODO: Remove unwrap
|
||||
register_default_render_stages(graph, &mut schedule);
|
||||
let schedule = Schedule::default();
|
||||
|
||||
let kernel = Rc::new(kernel);
|
||||
|
||||
register_stages::<E>(&mut schedule, kernel.clone());
|
||||
|
||||
let window = kernel.map_window_config().create();
|
||||
|
||||
let map = Self {
|
||||
kernel,
|
||||
map_context: MapContextState::Pending {
|
||||
schedule,
|
||||
map_context: CurrentMapContext::Pending {
|
||||
style,
|
||||
renderer_builder,
|
||||
},
|
||||
schedule,
|
||||
window,
|
||||
plugins,
|
||||
};
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
pub async fn initialize_renderer(&mut self) -> Result<(), MapError> {
|
||||
match &mut self.map_context {
|
||||
MapContextState::Ready(_) => Err(MapError::RendererAlreadySet),
|
||||
MapContextState::Pending {
|
||||
CurrentMapContext::Ready(_) => Err(MapError::RendererAlreadySet),
|
||||
CurrentMapContext::Pending {
|
||||
style,
|
||||
renderer_builder,
|
||||
} => {
|
||||
@ -98,18 +96,33 @@ where
|
||||
let window_size = self.window.size();
|
||||
|
||||
let center = style.center.unwrap_or_default();
|
||||
|
||||
let world = World::new_at(
|
||||
let initial_zoom = style.zoom.map(Zoom::new).unwrap_or_default();
|
||||
let view_state = ViewState::new(
|
||||
window_size,
|
||||
LatLon::new(center[0], center[1]),
|
||||
style.zoom.map(Zoom::new).unwrap_or_default(),
|
||||
WorldCoords::from_lat_lon(LatLon::new(center[0], center[1]), initial_zoom),
|
||||
initial_zoom,
|
||||
cgmath::Deg::<f64>(style.pitch.unwrap_or_default()),
|
||||
cgmath::Deg(110.0),
|
||||
);
|
||||
|
||||
let mut world = World::default();
|
||||
|
||||
match init_result {
|
||||
InitializationResult::Initialized(InitializedRenderer { renderer, .. }) => {
|
||||
self.map_context = MapContextState::Ready(MapContext {
|
||||
InitializationResult::Initialized(InitializedRenderer {
|
||||
mut renderer, ..
|
||||
}) => {
|
||||
for plugin in &self.plugins {
|
||||
plugin.build(
|
||||
&mut self.schedule,
|
||||
self.kernel.clone(),
|
||||
&mut world,
|
||||
&mut renderer.render_graph,
|
||||
);
|
||||
}
|
||||
|
||||
self.map_context = CurrentMapContext::Ready(MapContext {
|
||||
world,
|
||||
view_state,
|
||||
style: std::mem::take(style),
|
||||
renderer,
|
||||
});
|
||||
@ -131,33 +144,33 @@ where
|
||||
|
||||
pub fn has_renderer(&self) -> bool {
|
||||
match &self.map_context {
|
||||
MapContextState::Ready(_) => true,
|
||||
MapContextState::Pending { .. } => false,
|
||||
CurrentMapContext::Ready(_) => true,
|
||||
CurrentMapContext::Pending { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "update_and_redraw", skip_all)]
|
||||
pub fn run_schedule(&mut self) -> Result<(), MapError> {
|
||||
match &mut self.map_context {
|
||||
MapContextState::Ready(map_context) => {
|
||||
CurrentMapContext::Ready(map_context) => {
|
||||
self.schedule.run(map_context);
|
||||
Ok(())
|
||||
}
|
||||
MapContextState::Pending { .. } => Err(MapError::RendererAlreadySet),
|
||||
CurrentMapContext::Pending { .. } => Err(MapError::RendererAlreadySet),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context(&self) -> Result<&MapContext, MapError> {
|
||||
match &self.map_context {
|
||||
MapContextState::Ready(map_context) => Ok(map_context),
|
||||
MapContextState::Pending { .. } => Err(MapError::RendererAlreadySet),
|
||||
CurrentMapContext::Ready(map_context) => Ok(map_context),
|
||||
CurrentMapContext::Pending { .. } => Err(MapError::RendererAlreadySet),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context_mut(&mut self) -> Result<&mut MapContext, MapError> {
|
||||
match &mut self.map_context {
|
||||
MapContextState::Ready(map_context) => Ok(map_context),
|
||||
MapContextState::Pending { .. } => Err(MapError::RendererAlreadySet),
|
||||
CurrentMapContext::Ready(map_context) => Ok(map_context),
|
||||
CurrentMapContext::Pending { .. } => Err(MapError::RendererAlreadySet),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
//! Handles platform specific code. Depending on the compilation target, different
|
||||
//! parts of this module are used.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use noweb::run_multithreaded;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use noweb::ReqwestOffscreenKernelEnvironment;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod noweb;
|
||||
|
||||
@ -21,9 +26,6 @@ pub mod trace {
|
||||
pub use super::noweb::trace::*;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use noweb::run_multithreaded;
|
||||
|
||||
/// Minimum WebGPU buffer size
|
||||
///
|
||||
/// FIXME: This limit is enforced by WebGL. Actually this makes sense!
|
||||
|
||||
@ -53,6 +53,7 @@ impl HttpClient for ReqwestHttpClient {
|
||||
}
|
||||
|
||||
let body = response.bytes().await?;
|
||||
|
||||
Ok(Vec::from(body.as_ref()))
|
||||
}
|
||||
Err(e) => Err(SourceFetchError(Box::new(e))),
|
||||
|
||||
@ -5,6 +5,12 @@ use std::{
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
environment::OffscreenKernelEnvironment,
|
||||
io::source_client::{HttpSourceClient, SourceClient},
|
||||
platform::http_client::ReqwestHttpClient,
|
||||
};
|
||||
|
||||
pub mod http_client;
|
||||
pub mod scheduler;
|
||||
pub mod trace;
|
||||
@ -28,3 +34,17 @@ pub fn run_multithreaded<F: Future>(future: F) -> F::Output {
|
||||
.unwrap()
|
||||
.block_on(future)
|
||||
}
|
||||
|
||||
pub struct ReqwestOffscreenKernelEnvironment;
|
||||
|
||||
impl OffscreenKernelEnvironment for ReqwestOffscreenKernelEnvironment {
|
||||
type HttpClient = ReqwestHttpClient;
|
||||
|
||||
fn create() -> Self {
|
||||
ReqwestOffscreenKernelEnvironment
|
||||
}
|
||||
|
||||
fn source_client(&self) -> SourceClient<Self::HttpClient> {
|
||||
SourceClient::new(HttpSourceClient::new(ReqwestHttpClient::new(None)))
|
||||
}
|
||||
}
|
||||
|
||||
16
maplibre/src/plugin/mod.rs
Normal file
16
maplibre/src/plugin/mod.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
environment::Environment, kernel::Kernel, render::graph::RenderGraph, schedule::Schedule,
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
pub trait Plugin<E: Environment> {
|
||||
fn build(
|
||||
&self,
|
||||
schedule: &mut Schedule,
|
||||
kernel: Rc<Kernel<E>>,
|
||||
world: &mut World,
|
||||
graph: &mut RenderGraph,
|
||||
);
|
||||
}
|
||||
96
maplibre/src/raster/mod.rs
Normal file
96
maplibre/src/raster/mod.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use std::{marker::PhantomData, rc::Rc};
|
||||
|
||||
use image::RgbaImage;
|
||||
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
environment::Environment,
|
||||
kernel::Kernel,
|
||||
plugin::Plugin,
|
||||
raster::{
|
||||
populate_world_system::PopulateWorldSystem, queue_system::queue_system,
|
||||
request_system::RequestSystem, resource::RasterResources, resource_system::resource_system,
|
||||
upload_system::upload_system,
|
||||
},
|
||||
render::{eventually::Eventually, tile_view_pattern::ViewTileSources, RenderStageLabel},
|
||||
schedule::Schedule,
|
||||
tcs::{system::SystemContainer, tiles::TileComponent, world::World},
|
||||
};
|
||||
|
||||
mod populate_world_system;
|
||||
mod process_raster;
|
||||
mod queue_system;
|
||||
mod render_commands;
|
||||
mod request_system;
|
||||
mod resource;
|
||||
mod resource_system;
|
||||
mod transferables;
|
||||
mod upload_system;
|
||||
|
||||
pub use transferables::{
|
||||
DefaultRasterTransferables, LayerRaster, LayerRasterMissing, RasterTransferables,
|
||||
};
|
||||
|
||||
use crate::render::graph::RenderGraph;
|
||||
|
||||
pub struct RasterPlugin<T>(PhantomData<T>);
|
||||
|
||||
impl<T: RasterTransferables> Default for RasterPlugin<T> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Environment, T: RasterTransferables> Plugin<E> for RasterPlugin<T> {
|
||||
fn build(
|
||||
&self,
|
||||
schedule: &mut Schedule,
|
||||
kernel: Rc<Kernel<E>>,
|
||||
world: &mut World,
|
||||
_graph: &mut RenderGraph,
|
||||
) {
|
||||
world
|
||||
.resources
|
||||
.insert(Eventually::<RasterResources>::Uninitialized);
|
||||
|
||||
world
|
||||
.resources
|
||||
.get_or_init_mut::<ViewTileSources>()
|
||||
.add_resource_query::<&Eventually<RasterResources>>();
|
||||
|
||||
schedule.add_system_to_stage(
|
||||
RenderStageLabel::Extract,
|
||||
SystemContainer::new(RequestSystem::<E, T>::new(&kernel)),
|
||||
);
|
||||
schedule.add_system_to_stage(
|
||||
RenderStageLabel::Extract,
|
||||
SystemContainer::new(PopulateWorldSystem::<E, T>::new(&kernel)),
|
||||
);
|
||||
schedule.add_system_to_stage(RenderStageLabel::Prepare, resource_system);
|
||||
schedule.add_system_to_stage(RenderStageLabel::Queue, upload_system);
|
||||
schedule.add_system_to_stage(RenderStageLabel::Queue, queue_system); // FIXME tcs: Upload updates the TileView in tileviewpattern -> upload most run before prepare
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AvailableRasterLayerData {
|
||||
pub coords: WorldTileCoords,
|
||||
pub source_layer: String,
|
||||
pub image: RgbaImage,
|
||||
}
|
||||
|
||||
pub struct MissingRasterLayerData {
|
||||
pub coords: WorldTileCoords,
|
||||
pub source_layer: String,
|
||||
}
|
||||
|
||||
pub enum RasterLayerData {
|
||||
Available(AvailableRasterLayerData),
|
||||
Missing(MissingRasterLayerData),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RasterLayersDataComponent {
|
||||
pub layers: Vec<RasterLayerData>,
|
||||
}
|
||||
|
||||
impl TileComponent for RasterLayersDataComponent {}
|
||||
61
maplibre/src/raster/populate_world_system.rs
Normal file
61
maplibre/src/raster/populate_world_system.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use std::{borrow::Cow, marker::PhantomData, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
environment::Environment,
|
||||
io::apc::{AsyncProcedureCall, Message},
|
||||
kernel::Kernel,
|
||||
raster::{
|
||||
transferables::{LayerRaster, LayerRasterMissing, RasterTransferables},
|
||||
RasterLayerData, RasterLayersDataComponent,
|
||||
},
|
||||
tcs::system::System,
|
||||
};
|
||||
|
||||
pub struct PopulateWorldSystem<E: Environment, T> {
|
||||
kernel: Rc<Kernel<E>>,
|
||||
phantom_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<E: Environment, T> PopulateWorldSystem<E, T> {
|
||||
pub fn new(kernel: &Rc<Kernel<E>>) -> Self {
|
||||
Self {
|
||||
kernel: kernel.clone(),
|
||||
phantom_t: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Environment, T: RasterTransferables> System for PopulateWorldSystem<E, T> {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
"populate_world_system".into()
|
||||
}
|
||||
|
||||
fn run(&mut self, MapContext { world, .. }: &mut MapContext) {
|
||||
for message in self.kernel.apc().receive(|message| {
|
||||
message.has_tag(T::LayerRaster::message_tag())
|
||||
|| message.has_tag(T::LayerRasterMissing::message_tag())
|
||||
}) {
|
||||
let message: Message = message;
|
||||
if message.has_tag(T::LayerRaster::message_tag()) {
|
||||
let message = message.into_transferable::<T::LayerRaster>();
|
||||
let Some(component) = world
|
||||
.tiles
|
||||
.query_mut::<&mut RasterLayersDataComponent>(message.coords()) else { continue; };
|
||||
|
||||
component
|
||||
.layers
|
||||
.push(RasterLayerData::Available(message.to_layer()));
|
||||
} else if message.has_tag(T::LayerRaster::message_tag()) {
|
||||
let message = message.into_transferable::<T::LayerRasterMissing>();
|
||||
let Some(component) = world
|
||||
.tiles
|
||||
.query_mut::<&mut RasterLayersDataComponent>(message.coords()) else { continue; };
|
||||
|
||||
component
|
||||
.layers
|
||||
.push(RasterLayerData::Missing(message.to_layer()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
maplibre/src/raster/process_raster.rs
Normal file
86
maplibre/src/raster/process_raster.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use image::RgbaImage;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
io::apc::Context,
|
||||
raster::transferables::{LayerRaster, RasterTransferables},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ProcessRasterError {
|
||||
/// Error during processing of the pipeline
|
||||
#[error("processing data in pipeline failed")]
|
||||
Processing(Box<dyn std::error::Error>),
|
||||
}
|
||||
|
||||
pub struct RasterTileRequest {
|
||||
pub coords: WorldTileCoords,
|
||||
}
|
||||
|
||||
pub fn process_raster_tile<T: RasterTransferables, C: Context>(
|
||||
data: &[u8],
|
||||
tile_request: RasterTileRequest,
|
||||
context: &mut ProcessRasterContext<T, C>,
|
||||
) -> Result<(), ProcessRasterError> {
|
||||
let coords = &tile_request.coords;
|
||||
let img = image::load_from_memory(data).unwrap();
|
||||
let rgba = img.to_rgba8();
|
||||
|
||||
context.layer_raster_finished(coords, "raster".to_string(), rgba)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub struct ProcessRasterContext<T: RasterTransferables, C: Context> {
|
||||
context: C,
|
||||
phantom_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: RasterTransferables, C: Context> ProcessRasterContext<T, C> {
|
||||
pub fn new(context: C) -> Self {
|
||||
Self {
|
||||
context,
|
||||
phantom_t: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RasterTransferables, C: Context> ProcessRasterContext<T, C> {
|
||||
fn layer_raster_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
layer_name: String,
|
||||
image_data: RgbaImage,
|
||||
) -> Result<(), ProcessRasterError> {
|
||||
self.context
|
||||
.send(T::LayerRaster::build_from(*coords, layer_name, image_data))
|
||||
.map_err(|e| ProcessRasterError::Processing(Box::new(e)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::process_raster_tile;
|
||||
use crate::{
|
||||
coords::ZoomLevel,
|
||||
io::apc::tests::DummyContext,
|
||||
raster::{
|
||||
process_raster::{ProcessRasterContext, RasterTileRequest},
|
||||
DefaultRasterTransferables,
|
||||
},
|
||||
};
|
||||
|
||||
#[test] // TODO: Add proper tile byte array
|
||||
#[ignore]
|
||||
fn test() {
|
||||
let _output = process_raster_tile(
|
||||
&[0],
|
||||
RasterTileRequest {
|
||||
coords: (0, 0, ZoomLevel::default()).into(),
|
||||
},
|
||||
&mut ProcessRasterContext::<DefaultRasterTransferables, _>::new(DummyContext),
|
||||
);
|
||||
}
|
||||
}
|
||||
58
maplibre/src/raster/queue_system.rs
Normal file
58
maplibre/src/raster/queue_system.rs
Normal file
@ -0,0 +1,58 @@
|
||||
//! Queues [PhaseItems](crate::render::render_phase::PhaseItem) for rendering.
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
raster::render_commands::DrawRasterTiles,
|
||||
render::{
|
||||
eventually::{Eventually, Eventually::Initialized},
|
||||
render_commands::DrawMasks,
|
||||
render_phase::{DrawState, LayerItem, RenderPhase, TileMaskItem},
|
||||
tile_view_pattern::WgpuTileViewPattern,
|
||||
},
|
||||
tcs::tiles::Tile,
|
||||
};
|
||||
|
||||
pub fn queue_system(MapContext { world, .. }: &mut MapContext) {
|
||||
let Some((
|
||||
Initialized(tile_view_pattern),
|
||||
)) = world.resources.query::<(
|
||||
&Eventually<WgpuTileViewPattern>,
|
||||
)>() else { return; };
|
||||
|
||||
let mut items = Vec::new();
|
||||
|
||||
for view_tile in tile_view_pattern.iter() {
|
||||
let coords = &view_tile.coords();
|
||||
tracing::trace!("Drawing tile at {coords}");
|
||||
|
||||
// draw tile normal or the source e.g. parent or children
|
||||
view_tile.render(|source_shape| {
|
||||
// FIXME if raster_resources.has_tile(source_shape.coords(), world) {
|
||||
items.push((
|
||||
LayerItem {
|
||||
draw_function: Box::new(DrawState::<LayerItem, DrawRasterTiles>::new()),
|
||||
index: 0,
|
||||
style_layer: "raster".to_string(),
|
||||
tile: Tile {
|
||||
coords: source_shape.coords(),
|
||||
},
|
||||
source_shape: source_shape.clone(),
|
||||
},
|
||||
// FIXME tsc: Tile masks are currently drawn twice by each plugin
|
||||
TileMaskItem {
|
||||
draw_function: Box::new(DrawState::<TileMaskItem, DrawMasks>::new()),
|
||||
source_shape: source_shape.clone(),
|
||||
},
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
let Some((layer_item_phase, tile_mask_phase)) = world
|
||||
.resources
|
||||
.query_mut::<(&mut RenderPhase<LayerItem>, &mut RenderPhase<TileMaskItem>,)>() else { return; };
|
||||
|
||||
for (layer, mask) in items {
|
||||
layer_item_phase.add(layer);
|
||||
tile_mask_phase.add(mask);
|
||||
}
|
||||
}
|
||||
93
maplibre/src/raster/render_commands.rs
Normal file
93
maplibre/src/raster/render_commands.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use crate::{
|
||||
raster::resource::RasterResources,
|
||||
render::{
|
||||
eventually::{Eventually, Eventually::Initialized},
|
||||
render_phase::{LayerItem, PhaseItem, RenderCommand, RenderCommandResult},
|
||||
resource::TrackedRenderPass,
|
||||
tile_view_pattern::WgpuTileViewPattern,
|
||||
},
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
pub struct SetRasterTilePipeline;
|
||||
impl<P: PhaseItem> RenderCommand<P> for SetRasterTilePipeline {
|
||||
fn render<'w>(
|
||||
world: &'w World,
|
||||
_item: &P,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Some(Initialized(raster_resources)) = world
|
||||
.resources
|
||||
.get::<Eventually<RasterResources>>() else { return RenderCommandResult::Failure; };
|
||||
|
||||
pass.set_render_pipeline(raster_resources.pipeline());
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetRasterViewBindGroup<const I: usize>;
|
||||
impl<const I: usize> RenderCommand<LayerItem> for SetRasterViewBindGroup<I> {
|
||||
fn render<'w>(
|
||||
world: &'w World,
|
||||
item: &LayerItem,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Some(Initialized(raster_resources)) = world
|
||||
.resources
|
||||
.get::<Eventually<RasterResources>>() else { return RenderCommandResult::Failure; };
|
||||
|
||||
let Some(bind_group) = raster_resources
|
||||
.get_bound_texture(&item.tile.coords) else { return RenderCommandResult::Failure; };
|
||||
|
||||
pass.set_bind_group(0, bind_group, &[]);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DrawRasterTile;
|
||||
impl RenderCommand<LayerItem> for DrawRasterTile {
|
||||
fn render<'w>(
|
||||
world: &'w World,
|
||||
item: &LayerItem,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Some(Initialized(tile_view_pattern)) = world
|
||||
.resources
|
||||
.get::<Eventually<WgpuTileViewPattern>>() else { return RenderCommandResult::Failure; };
|
||||
|
||||
let source_shape = &item.source_shape;
|
||||
|
||||
let reference = source_shape.coords().stencil_reference_value_3d() as u32;
|
||||
|
||||
pass.set_stencil_reference(reference);
|
||||
|
||||
let tile_view_pattern_buffer = source_shape
|
||||
.buffer_range()
|
||||
.expect("tile_view_pattern needs to be uploaded first"); // FIXME tcs
|
||||
pass.set_vertex_buffer(
|
||||
0,
|
||||
tile_view_pattern.buffer().slice(tile_view_pattern_buffer),
|
||||
);
|
||||
|
||||
let tile_view_pattern_buffer = source_shape
|
||||
.buffer_range()
|
||||
.expect("tile_view_pattern needs to be uploaded first"); // FIXME tcs
|
||||
|
||||
// FIXME tcs: I passin random data here right now, but instead we need the correct metadata here
|
||||
pass.set_vertex_buffer(
|
||||
1,
|
||||
tile_view_pattern.buffer().slice(tile_view_pattern_buffer),
|
||||
);
|
||||
|
||||
const TILE_MASK_SHADER_VERTICES: u32 = 6;
|
||||
pass.draw(0..TILE_MASK_SHADER_VERTICES, 0..1);
|
||||
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub type DrawRasterTiles = (
|
||||
SetRasterTilePipeline,
|
||||
SetRasterViewBindGroup<0>,
|
||||
DrawRasterTile,
|
||||
);
|
||||
158
maplibre/src/raster/request_system.rs
Normal file
158
maplibre/src/raster/request_system.rs
Normal file
@ -0,0 +1,158 @@
|
||||
//! Requests tiles which are currently in view
|
||||
|
||||
use std::{borrow::Cow, collections::HashSet, marker::PhantomData, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
environment::{Environment, OffscreenKernelEnvironment},
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, ProcedureError},
|
||||
source_type::{RasterSource, SourceType},
|
||||
},
|
||||
kernel::Kernel,
|
||||
raster::{
|
||||
process_raster::{process_raster_tile, ProcessRasterContext, RasterTileRequest},
|
||||
transferables::{LayerRasterMissing, RasterTransferables},
|
||||
RasterLayersDataComponent,
|
||||
},
|
||||
style::layer::LayerPaint,
|
||||
tcs::system::System,
|
||||
};
|
||||
|
||||
pub struct RequestSystem<E: Environment, T: RasterTransferables> {
|
||||
kernel: Rc<Kernel<E>>,
|
||||
phantom_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<E: Environment, T: RasterTransferables> RequestSystem<E, T> {
|
||||
pub fn new(kernel: &Rc<Kernel<E>>) -> Self {
|
||||
Self {
|
||||
kernel: kernel.clone(),
|
||||
phantom_t: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Environment, T: RasterTransferables> System for RequestSystem<E, T> {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
"raster_request".into()
|
||||
}
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
style,
|
||||
view_state,
|
||||
world,
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let _tiles = &mut world.tiles;
|
||||
let view_region = view_state.create_view_region();
|
||||
|
||||
if view_state.did_camera_change() || view_state.did_zoom_change() {
|
||||
if let Some(view_region) = &view_region {
|
||||
// TODO: We also need to request tiles from layers above if we are over the maximum zoom level
|
||||
|
||||
for coords in view_region.iter() {
|
||||
if coords.build_quad_key().is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Make tesselation depend on style? So maybe we need to request even if it exists
|
||||
if world
|
||||
.tiles
|
||||
.query::<&RasterLayersDataComponent>(coords)
|
||||
.is_some()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
world
|
||||
.tiles
|
||||
.spawn_mut(coords)
|
||||
.unwrap()
|
||||
.insert(RasterLayersDataComponent::default());
|
||||
|
||||
tracing::event!(tracing::Level::ERROR, %coords, "tile request started: {}", &coords);
|
||||
log::info!("tile request started: {}", &coords);
|
||||
|
||||
self.kernel
|
||||
.apc()
|
||||
.call(
|
||||
Input::TileRequest {
|
||||
coords,
|
||||
style: style.clone(), // TODO: Avoid cloning whole style
|
||||
},
|
||||
fetch_raster_apc::<
|
||||
E::OffscreenKernelEnvironment,
|
||||
T,
|
||||
<E::AsyncProcedureCall as AsyncProcedureCall<
|
||||
E::OffscreenKernelEnvironment,
|
||||
>>::Context,
|
||||
>,
|
||||
)
|
||||
.unwrap(); // TODO: Remove unwrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view_state.update_references();
|
||||
}
|
||||
}
|
||||
pub fn fetch_raster_apc<
|
||||
K: OffscreenKernelEnvironment,
|
||||
T: RasterTransferables,
|
||||
C: Context + Clone + Send,
|
||||
>(
|
||||
input: Input,
|
||||
context: C,
|
||||
kernel: K,
|
||||
) -> AsyncProcedureFuture {
|
||||
Box::pin(async move {
|
||||
let Input::TileRequest {coords, style} = input else {
|
||||
return Err(ProcedureError::IncompatibleInput)
|
||||
};
|
||||
|
||||
let raster_layers: HashSet<String> = style
|
||||
.layers
|
||||
.iter()
|
||||
.filter_map(|layer| {
|
||||
if matches!(layer.paint, Some(LayerPaint::Raster(_))) {
|
||||
layer.source_layer.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let client = kernel.source_client();
|
||||
|
||||
if !raster_layers.is_empty() {
|
||||
let context = context.clone();
|
||||
let source = SourceType::Raster(RasterSource::default());
|
||||
|
||||
match client.fetch(&coords, &source).await {
|
||||
Ok(data) => {
|
||||
let data = data.into_boxed_slice();
|
||||
|
||||
let mut process_context = ProcessRasterContext::<T, C>::new(context);
|
||||
|
||||
process_raster_tile(&data, RasterTileRequest { coords }, &mut process_context)
|
||||
.map_err(|e| ProcedureError::Execution(Box::new(e)))?;
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{:?}", &e);
|
||||
|
||||
context
|
||||
.send(<T as RasterTransferables>::LayerRasterMissing::build_from(
|
||||
coords,
|
||||
))
|
||||
.map_err(ProcedureError::Send)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
3
maplibre/src/raster/resource/mod.rs
Normal file
3
maplibre/src/raster/resource/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub use raster::*;
|
||||
|
||||
mod raster;
|
||||
91
maplibre/src/raster/resource/raster.rs
Normal file
91
maplibre/src/raster/resource/raster.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
render::{resource::Texture, settings::Msaa, tile_view_pattern::HasTile},
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
/// Holds the resources necessary for the raster tiles such as the
|
||||
/// * sampler
|
||||
/// * texture
|
||||
/// * pipeline
|
||||
/// * bindgroups
|
||||
pub struct RasterResources {
|
||||
sampler: wgpu::Sampler,
|
||||
msaa: Msaa,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
bound_textures: HashMap<WorldTileCoords, wgpu::BindGroup>,
|
||||
}
|
||||
|
||||
impl RasterResources {
|
||||
pub fn new(msaa: Msaa, device: &wgpu::Device, pipeline: wgpu::RenderPipeline) -> Self {
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
Self {
|
||||
sampler,
|
||||
msaa,
|
||||
pipeline,
|
||||
bound_textures: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_texture(
|
||||
&mut self,
|
||||
label: wgpu::Label,
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
width: u32,
|
||||
height: u32,
|
||||
usage: wgpu::TextureUsages,
|
||||
) -> Texture {
|
||||
Texture::new(label, device, format, width, height, self.msaa, usage)
|
||||
}
|
||||
|
||||
pub fn get_bound_texture(&self, coords: &WorldTileCoords) -> Option<&wgpu::BindGroup> {
|
||||
self.bound_textures.get(coords)
|
||||
}
|
||||
|
||||
/// Creates a bind group for each fetched raster tile and store it inside a hashmap.
|
||||
pub fn bind_texture(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
coords: &WorldTileCoords,
|
||||
texture: Texture,
|
||||
) {
|
||||
self.bound_textures.insert(
|
||||
*coords,
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &self.pipeline.get_bind_group_layout(0),
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&texture.view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn pipeline(&self) -> &wgpu::RenderPipeline {
|
||||
&self.pipeline
|
||||
}
|
||||
}
|
||||
|
||||
impl HasTile for RasterResources {
|
||||
fn has_tile(&self, coords: WorldTileCoords, _world: &World) -> bool {
|
||||
self.bound_textures.contains_key(&coords)
|
||||
}
|
||||
}
|
||||
56
maplibre/src/raster/resource_system.rs
Normal file
56
maplibre/src/raster/resource_system.rs
Normal file
@ -0,0 +1,56 @@
|
||||
//! Prepares GPU-owned resources by initializing them if they are uninitialized or out-of-date.
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
raster::resource::RasterResources,
|
||||
render::{
|
||||
eventually::Eventually,
|
||||
resource::{RenderPipeline, TilePipeline},
|
||||
settings::Msaa,
|
||||
shaders,
|
||||
shaders::Shader,
|
||||
RenderResources, Renderer,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn resource_system(
|
||||
MapContext {
|
||||
world,
|
||||
renderer:
|
||||
Renderer {
|
||||
device,
|
||||
resources: RenderResources { surface, .. },
|
||||
settings,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let Some(raster_resources) = world
|
||||
.resources
|
||||
.query_mut::<&mut Eventually<RasterResources>>() else { return; };
|
||||
|
||||
raster_resources.initialize(|| {
|
||||
let shader = shaders::RasterTileShader {
|
||||
format: surface.surface_format(),
|
||||
};
|
||||
|
||||
RasterResources::new(
|
||||
Msaa { samples: 1 },
|
||||
device,
|
||||
TilePipeline::new(
|
||||
"raster_pipeline".into(),
|
||||
*settings,
|
||||
shader.describe_vertex(),
|
||||
shader.describe_fragment(),
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.describe_render_pipeline()
|
||||
.initialize(device),
|
||||
)
|
||||
});
|
||||
}
|
||||
135
maplibre/src/raster/transferables.rs
Normal file
135
maplibre/src/raster/transferables.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
use image::RgbaImage;
|
||||
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
io::apc::{IntoMessage, Message, MessageTag},
|
||||
raster::{AvailableRasterLayerData, MissingRasterLayerData},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum RasterMessageTag {
|
||||
LayerRaster,
|
||||
LayerRasterMissing,
|
||||
}
|
||||
|
||||
impl MessageTag for RasterMessageTag {
|
||||
fn dyn_clone(&self) -> Box<dyn MessageTag> {
|
||||
Box::new(*self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LayerRaster: IntoMessage + Debug + Send {
|
||||
fn message_tag() -> &'static dyn MessageTag;
|
||||
|
||||
fn build_from(coords: WorldTileCoords, layer_name: String, image: RgbaImage) -> Self;
|
||||
|
||||
fn coords(&self) -> WorldTileCoords;
|
||||
|
||||
fn to_layer(self) -> AvailableRasterLayerData;
|
||||
}
|
||||
|
||||
pub trait LayerRasterMissing: IntoMessage + Debug + Send {
|
||||
fn message_tag() -> &'static dyn MessageTag;
|
||||
|
||||
fn build_from(coords: WorldTileCoords) -> Self;
|
||||
|
||||
fn coords(&self) -> WorldTileCoords;
|
||||
|
||||
fn to_layer(self) -> MissingRasterLayerData;
|
||||
}
|
||||
|
||||
pub struct DefaultLayerRaster {
|
||||
pub coords: WorldTileCoords,
|
||||
pub layer_name: String,
|
||||
pub image: RgbaImage,
|
||||
}
|
||||
|
||||
impl Debug for DefaultLayerRaster {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "DefaultRasterLayer({})", self.coords)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMessage for DefaultLayerRaster {
|
||||
fn into(self) -> Message {
|
||||
Message::new(Self::message_tag(), Box::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerRaster for DefaultLayerRaster {
|
||||
fn message_tag() -> &'static dyn MessageTag {
|
||||
&RasterMessageTag::LayerRaster
|
||||
}
|
||||
|
||||
fn build_from(coords: WorldTileCoords, layer_name: String, image: RgbaImage) -> Self {
|
||||
Self {
|
||||
coords,
|
||||
layer_name,
|
||||
image,
|
||||
}
|
||||
}
|
||||
|
||||
fn coords(&self) -> WorldTileCoords {
|
||||
self.coords
|
||||
}
|
||||
|
||||
fn to_layer(self) -> AvailableRasterLayerData {
|
||||
AvailableRasterLayerData {
|
||||
coords: self.coords,
|
||||
source_layer: "raster".to_string(),
|
||||
image: self.image,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultLayerRasterMissing {
|
||||
pub coords: WorldTileCoords,
|
||||
}
|
||||
|
||||
impl Debug for DefaultLayerRasterMissing {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "DefaultRasterLayerMissing({})", self.coords)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMessage for DefaultLayerRasterMissing {
|
||||
fn into(self) -> Message {
|
||||
Message::new(Self::message_tag(), Box::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerRasterMissing for DefaultLayerRasterMissing {
|
||||
fn message_tag() -> &'static dyn MessageTag {
|
||||
&RasterMessageTag::LayerRasterMissing
|
||||
}
|
||||
|
||||
fn build_from(coords: WorldTileCoords) -> Self {
|
||||
Self { coords }
|
||||
}
|
||||
|
||||
fn coords(&self) -> WorldTileCoords {
|
||||
self.coords
|
||||
}
|
||||
|
||||
fn to_layer(self) -> MissingRasterLayerData {
|
||||
MissingRasterLayerData {
|
||||
coords: self.coords,
|
||||
source_layer: "raster".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RasterTransferables: Copy + Clone + 'static {
|
||||
type LayerRaster: LayerRaster;
|
||||
type LayerRasterMissing: LayerRasterMissing;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct DefaultRasterTransferables;
|
||||
|
||||
impl RasterTransferables for DefaultRasterTransferables {
|
||||
type LayerRaster = DefaultLayerRaster;
|
||||
type LayerRasterMissing = DefaultLayerRasterMissing;
|
||||
}
|
||||
105
maplibre/src/raster/upload_system.rs
Normal file
105
maplibre/src/raster/upload_system.rs
Normal file
@ -0,0 +1,105 @@
|
||||
//! Uploads data to the GPU which is needed for rendering.
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::ViewRegion,
|
||||
raster::{
|
||||
resource::RasterResources, AvailableRasterLayerData, RasterLayerData,
|
||||
RasterLayersDataComponent,
|
||||
},
|
||||
render::{
|
||||
eventually::{Eventually, Eventually::Initialized},
|
||||
Renderer,
|
||||
},
|
||||
style::Style,
|
||||
tcs::tiles::Tiles,
|
||||
};
|
||||
|
||||
pub fn upload_system(
|
||||
MapContext {
|
||||
world,
|
||||
style,
|
||||
view_state,
|
||||
renderer: Renderer { device, queue, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let Some(Initialized(raster_resources)) = world
|
||||
.resources
|
||||
.query_mut::<&mut Eventually<RasterResources>>() else { return; };
|
||||
let view_region = view_state.create_view_region();
|
||||
|
||||
if let Some(view_region) = &view_region {
|
||||
upload_raster_layer(
|
||||
raster_resources,
|
||||
device,
|
||||
queue,
|
||||
&world.tiles,
|
||||
style,
|
||||
view_region,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn upload_raster_layer(
|
||||
raster_resources: &mut RasterResources,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
tiles: &Tiles,
|
||||
style: &Style,
|
||||
view_region: &ViewRegion,
|
||||
) {
|
||||
for coords in view_region.iter() {
|
||||
if raster_resources.get_bound_texture(&coords).is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(raster_layers) =
|
||||
tiles.query::<&RasterLayersDataComponent>(coords) else { continue; };
|
||||
|
||||
for style_layer in &style.layers {
|
||||
let style_source_layer = style_layer.source_layer.as_ref().unwrap(); // FIXME: Remove unwrap
|
||||
|
||||
let Some(AvailableRasterLayerData {
|
||||
coords,
|
||||
image,
|
||||
..
|
||||
}) = raster_layers.layers
|
||||
.iter()
|
||||
.flat_map(|data| match data {
|
||||
RasterLayerData::Available(data) => Some(data),
|
||||
RasterLayerData::Missing(_) => None,
|
||||
})
|
||||
.find(|layer| style_source_layer.as_str() == layer.source_layer) else { continue; };
|
||||
|
||||
let (width, height) = image.dimensions();
|
||||
|
||||
let texture = raster_resources.create_texture(
|
||||
None,
|
||||
device,
|
||||
wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
width,
|
||||
height,
|
||||
wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
);
|
||||
|
||||
queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
texture: &texture.texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
},
|
||||
image,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: std::num::NonZeroU32::new(4 * width),
|
||||
rows_per_image: std::num::NonZeroU32::new(height),
|
||||
},
|
||||
texture.size,
|
||||
);
|
||||
|
||||
raster_resources.bind_texture(device, coords, texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
use std::mem;
|
||||
|
||||
use crate::{coords::WorldTileCoords, render::tile_view_pattern::HasTile, tcs::world::World};
|
||||
|
||||
/// Wrapper around a resource which can be initialized or uninitialized.
|
||||
/// Uninitialized resourced can be initialized by calling [`Eventually::initialize()`].
|
||||
pub enum Eventually<T> {
|
||||
@ -54,6 +56,13 @@ impl<T> Eventually<T> {
|
||||
pub fn take(&mut self) -> Eventually<T> {
|
||||
mem::replace(self, Eventually::Uninitialized)
|
||||
}
|
||||
|
||||
pub fn expect_initialized_mut(&mut self, message: &str) -> &mut T {
|
||||
match self {
|
||||
Eventually::Initialized(value) => value,
|
||||
Eventually::Uninitialized => panic!("{}", message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Eventually<T> {
|
||||
@ -61,3 +70,15 @@ impl<T> Default for Eventually<T> {
|
||||
Eventually::Uninitialized
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> HasTile for Eventually<T>
|
||||
where
|
||||
T: HasTile,
|
||||
{
|
||||
fn has_tile(&self, coords: WorldTileCoords, world: &World) -> bool {
|
||||
match self {
|
||||
Eventually::Initialized(value) => value.has_tile(coords, world),
|
||||
Eventually::Uninitialized => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,10 @@ use super::{
|
||||
Edge, EdgeExistence, Node, NodeId, NodeLabel, NodeRunError, NodeState, RenderGraphContext,
|
||||
RenderGraphError, SlotInfo, SlotLabel,
|
||||
};
|
||||
use crate::render::{graph::RenderContext, RenderState};
|
||||
use crate::{
|
||||
render::{graph::RenderContext, RenderResources},
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
/// The render graph configures the modular, parallel and re-usable render logic.
|
||||
/// It is a retained and stateless (nodes itself my have their internal state) structure,
|
||||
@ -26,12 +29,17 @@ use crate::render::{graph::RenderContext, RenderState};
|
||||
/// Here is a simple render graph example with two nodes connected by a node edge.
|
||||
/// ```
|
||||
/// #
|
||||
/// # use maplibre::render::graph::{Node, NodeRunError, RenderContext, RenderGraph, RenderGraphContext};
|
||||
/// # use maplibre::render::{RenderState};
|
||||
/// # use maplibre::tcs::world::World;
|
||||
/// use maplibre::render::graph::{Node, NodeRunError, RenderContext, RenderGraph, RenderGraphContext};
|
||||
/// # use maplibre::render::{RenderResources};
|
||||
/// # struct MyNode;
|
||||
/// #
|
||||
/// # impl Node for MyNode {
|
||||
/// # fn run(&self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, state: &RenderState) -> Result<(), NodeRunError> {
|
||||
/// # fn run(&self,
|
||||
/// # graph: &mut RenderGraphContext,
|
||||
/// # render_context: &mut RenderContext,
|
||||
/// # state: &RenderResources,
|
||||
/// # world: &World) -> Result<(), NodeRunError> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # }
|
||||
@ -56,7 +64,7 @@ impl RenderGraph {
|
||||
pub const INPUT_NODE_NAME: &'static str = "GraphInputNode";
|
||||
|
||||
/// Updates all nodes and sub graphs of the render graph. Should be called before executing it.
|
||||
pub fn update(&mut self, state: &mut RenderState) {
|
||||
pub fn update(&mut self, state: &mut RenderResources) {
|
||||
for node in self.nodes.values_mut() {
|
||||
node.node.update(state);
|
||||
}
|
||||
@ -556,7 +564,8 @@ impl Node for GraphInputNode {
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
_render_context: &mut RenderContext,
|
||||
_state: &RenderState,
|
||||
_state: &RenderResources,
|
||||
_world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
for i in 0..graph.inputs().len() {
|
||||
let input = graph.inputs()[i].clone();
|
||||
@ -574,9 +583,12 @@ mod tests {
|
||||
Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError,
|
||||
SlotInfo,
|
||||
};
|
||||
use crate::render::{
|
||||
graph::{RenderContext, SlotType},
|
||||
RenderState,
|
||||
use crate::{
|
||||
render::{
|
||||
graph::{RenderContext, SlotType},
|
||||
RenderResources,
|
||||
},
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -611,7 +623,8 @@ mod tests {
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
_render_context: &mut RenderContext,
|
||||
_state: &RenderState,
|
||||
_state: &RenderResources,
|
||||
_world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
Ok(())
|
||||
}
|
||||
@ -684,7 +697,8 @@ mod tests {
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
_render_context: &mut RenderContext,
|
||||
_state: &RenderState,
|
||||
_state: &RenderResources,
|
||||
_world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,9 +1,3 @@
|
||||
mod context;
|
||||
mod edge;
|
||||
mod graph;
|
||||
mod node;
|
||||
mod node_slot;
|
||||
|
||||
pub use context::*;
|
||||
pub use edge::*;
|
||||
pub use graph::*;
|
||||
@ -11,6 +5,12 @@ pub use node::*;
|
||||
pub use node_slot::*;
|
||||
use thiserror::Error;
|
||||
|
||||
mod context;
|
||||
mod edge;
|
||||
mod graph;
|
||||
mod node;
|
||||
mod node_slot;
|
||||
|
||||
#[derive(Error, Debug, Eq, PartialEq)]
|
||||
pub enum RenderGraphError {
|
||||
#[error("node does not exist")]
|
||||
|
||||
@ -7,7 +7,7 @@ use super::{
|
||||
Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError, RunSubGraphError,
|
||||
SlotInfo, SlotInfos,
|
||||
};
|
||||
use crate::render::RenderState;
|
||||
use crate::{render::RenderResources, tcs::world::World};
|
||||
|
||||
/// The context with all information required to interact with the GPU.
|
||||
///
|
||||
@ -57,8 +57,8 @@ pub trait Node: Downcast + Send + Sync + 'static {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Updates internal node state using the current [`RenderState`] prior to the run method.
|
||||
fn update(&mut self, _state: &mut RenderState) {}
|
||||
/// Updates internal node state using the current [`RenderResources`] prior to the run method.
|
||||
fn update(&mut self, _state: &mut RenderResources) {}
|
||||
|
||||
/// Runs the graph node logic, issues draw calls, updates the output slots and
|
||||
/// optionally queues up subgraphs for execution. The graph data, input and output values are
|
||||
@ -67,7 +67,8 @@ pub trait Node: Downcast + Send + Sync + 'static {
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
resources: &RenderResources,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError>;
|
||||
}
|
||||
|
||||
@ -329,7 +330,8 @@ impl Node for EmptyNode {
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
_render_context: &mut RenderContext,
|
||||
_state: &RenderState,
|
||||
_state: &RenderResources,
|
||||
_world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -9,12 +9,15 @@ use log::error;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::render::{
|
||||
graph::{
|
||||
Edge, NodeId, NodeRunError, NodeState, RenderContext, RenderGraph, RenderGraphContext,
|
||||
SlotLabel, SlotType, SlotValue,
|
||||
use crate::{
|
||||
render::{
|
||||
graph::{
|
||||
Edge, NodeId, NodeRunError, NodeState, RenderContext, RenderGraph, RenderGraphContext,
|
||||
SlotLabel, SlotType, SlotValue,
|
||||
},
|
||||
RenderResources,
|
||||
},
|
||||
RenderState,
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
pub(crate) struct RenderGraphRunner;
|
||||
@ -49,7 +52,8 @@ impl RenderGraphRunner {
|
||||
graph: &RenderGraph,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
state: &RenderState,
|
||||
state: &RenderResources,
|
||||
world: &World,
|
||||
) -> Result<(), RenderGraphRunnerError> {
|
||||
let command_encoder =
|
||||
device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
|
||||
@ -58,7 +62,7 @@ impl RenderGraphRunner {
|
||||
command_encoder,
|
||||
};
|
||||
|
||||
Self::run_graph(graph, None, &mut render_context, state, &[])?;
|
||||
Self::run_graph(graph, None, &mut render_context, state, world, &[])?;
|
||||
{
|
||||
#[cfg(feature = "trace")]
|
||||
let _span = tracing::info_span!("submit_graph_commands").entered();
|
||||
@ -71,7 +75,8 @@ impl RenderGraphRunner {
|
||||
graph: &RenderGraph,
|
||||
graph_name: Option<Cow<'static, str>>,
|
||||
render_context: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
state: &RenderResources,
|
||||
world: &World,
|
||||
inputs: &[SlotValue],
|
||||
) -> Result<(), RenderGraphRunnerError> {
|
||||
let mut node_outputs: HashMap<NodeId, SmallVec<[SlotValue; 4]>> = HashMap::default();
|
||||
@ -173,7 +178,9 @@ impl RenderGraphRunner {
|
||||
#[cfg(feature = "trace")]
|
||||
let _span = tracing::info_span!("node", name = node_state.type_name).entered();
|
||||
|
||||
node_state.node.run(&mut context, render_context, state)?;
|
||||
node_state
|
||||
.node
|
||||
.run(&mut context, render_context, state, world)?;
|
||||
}
|
||||
|
||||
for run_sub_graph in context.finish() {
|
||||
@ -185,6 +192,7 @@ impl RenderGraphRunner {
|
||||
Some(run_sub_graph.name),
|
||||
render_context,
|
||||
state,
|
||||
world,
|
||||
&run_sub_graph.inputs,
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -5,14 +5,16 @@
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::render::{
|
||||
draw_graph,
|
||||
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
|
||||
render_commands::{DrawMasks, DrawTiles},
|
||||
render_phase::RenderCommand,
|
||||
resource::TrackedRenderPass,
|
||||
Eventually::Initialized,
|
||||
RenderState,
|
||||
use crate::{
|
||||
render::{
|
||||
draw_graph,
|
||||
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
|
||||
render_phase::{LayerItem, RenderPhase, TileMaskItem},
|
||||
resource::TrackedRenderPass,
|
||||
Eventually::Initialized,
|
||||
RenderResources,
|
||||
},
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
pub struct MainPassNode {}
|
||||
@ -28,13 +30,14 @@ impl Node for MainPassNode {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn update(&mut self, _state: &mut RenderState) {}
|
||||
fn update(&mut self, _state: &mut RenderResources) {}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
state: &RenderResources,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let Initialized(render_target) = &state.render_target else {
|
||||
return Ok(());
|
||||
@ -70,7 +73,7 @@ impl Node for MainPassNode {
|
||||
render_context
|
||||
.command_encoder
|
||||
.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
label: Some("main_pass"),
|
||||
color_attachments: &[Some(color_attachment)],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &depth_texture.view,
|
||||
@ -87,12 +90,18 @@ impl Node for MainPassNode {
|
||||
|
||||
let mut tracked_pass = TrackedRenderPass::new(render_pass);
|
||||
|
||||
for item in &state.mask_phase.items {
|
||||
DrawMasks::render(state, item, &mut tracked_pass);
|
||||
if let Some(mask_items) = world.resources.get::<RenderPhase<TileMaskItem>>() {
|
||||
log::trace!("RenderPhase<TileMaskItem>::size() = {}", mask_items.size());
|
||||
for item in mask_items {
|
||||
item.draw_function.draw(&mut tracked_pass, world, item);
|
||||
}
|
||||
}
|
||||
|
||||
for item in &state.tile_phase.items {
|
||||
DrawTiles::render(state, item, &mut tracked_pass);
|
||||
if let Some(layer_items) = world.resources.get::<RenderPhase<LayerItem>>() {
|
||||
log::trace!("RenderPhase<LayerItem>::size() = {}", layer_items.size());
|
||||
for item in layer_items {
|
||||
item.draw_function.draw(&mut tracked_pass, world, item);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -106,7 +115,8 @@ impl Node for MainPassDriverNode {
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
_render_context: &mut RenderContext,
|
||||
_state: &RenderState,
|
||||
_resources: &RenderResources,
|
||||
_world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
graph.run_sub_graph(draw_graph::NAME, vec![])?;
|
||||
|
||||
|
||||
@ -18,101 +18,109 @@
|
||||
//! We appreciate the design and implementation work which as gone into it.
|
||||
//!
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{ops::Deref, rc::Rc, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
environment::Environment,
|
||||
kernel::Kernel,
|
||||
plugin::Plugin,
|
||||
render::{
|
||||
error::RenderError,
|
||||
eventually::Eventually,
|
||||
render_phase::RenderPhase,
|
||||
resource::{BufferPool, Globals, Head, IndexEntry, Surface, Texture, TextureView},
|
||||
graph::{EmptyNode, RenderGraph},
|
||||
main_pass::{MainPassDriverNode, MainPassNode},
|
||||
resource::{Head, Surface, Texture, TextureView},
|
||||
settings::{RendererSettings, WgpuSettings},
|
||||
shaders::{ShaderFeatureStyle, ShaderLayerMetadata},
|
||||
tile_view_pattern::{TileShape, TileViewPattern},
|
||||
systems::{
|
||||
cleanup_system::cleanup_system, resource_system::ResourceSystem,
|
||||
sort_phase_system::sort_phase_system,
|
||||
tile_view_pattern_system::tile_view_pattern_system,
|
||||
},
|
||||
},
|
||||
tessellation::IndexDataType,
|
||||
schedule::{Schedule, StageLabel},
|
||||
tcs::{
|
||||
system::{stage::SystemStage, SystemContainer},
|
||||
world::World,
|
||||
},
|
||||
window::{HeadedMapWindow, MapWindow},
|
||||
};
|
||||
|
||||
pub mod graph;
|
||||
pub mod resource;
|
||||
pub mod stages;
|
||||
mod systems;
|
||||
|
||||
// Rendering internals
|
||||
mod debug_pass;
|
||||
mod graph_runner;
|
||||
mod main_pass;
|
||||
mod render_commands;
|
||||
mod render_phase;
|
||||
mod shaders;
|
||||
mod tile_pipeline;
|
||||
mod tile_view_pattern;
|
||||
pub mod shaders; // TODO: Make private
|
||||
|
||||
// Public API
|
||||
pub mod builder;
|
||||
pub mod camera;
|
||||
pub mod error;
|
||||
pub mod eventually;
|
||||
pub mod render_commands;
|
||||
pub mod render_phase;
|
||||
pub mod settings;
|
||||
pub mod tile_view_pattern;
|
||||
|
||||
pub use shaders::ShaderVertex;
|
||||
pub use stages::register_default_render_stages;
|
||||
|
||||
use crate::{
|
||||
render::{
|
||||
debug_pass::DebugPassNode,
|
||||
error::RenderError,
|
||||
graph::{EmptyNode, RenderGraph, RenderGraphError},
|
||||
main_pass::{MainPassDriverNode, MainPassNode},
|
||||
},
|
||||
window::{HeadedMapWindow, MapWindow},
|
||||
use crate::render::{
|
||||
render_phase::{LayerItem, RenderPhase, TileMaskItem},
|
||||
systems::{graph_runner_system::GraphRunnerSystem, upload_system::upload_system},
|
||||
tile_view_pattern::{ViewTileSources, WgpuTileViewPattern},
|
||||
};
|
||||
|
||||
const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
|
||||
pub(crate) const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
|
||||
|
||||
pub struct RenderState {
|
||||
render_target: Eventually<TextureView>,
|
||||
/// The labels of the default App rendering stages.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub enum RenderStageLabel {
|
||||
/// Extract data from the world.
|
||||
Extract,
|
||||
|
||||
buffer_pool: Eventually<
|
||||
BufferPool<
|
||||
wgpu::Queue,
|
||||
wgpu::Buffer,
|
||||
ShaderVertex,
|
||||
IndexDataType,
|
||||
ShaderLayerMetadata,
|
||||
ShaderFeatureStyle,
|
||||
>,
|
||||
>,
|
||||
tile_view_pattern: Eventually<TileViewPattern<wgpu::Queue, wgpu::Buffer>>,
|
||||
/// Prepare render resources from the extracted data for the GPU.
|
||||
/// For example during this phase textures are created, buffers are allocated and written.
|
||||
Prepare,
|
||||
|
||||
tile_pipeline: Eventually<wgpu::RenderPipeline>,
|
||||
mask_pipeline: Eventually<wgpu::RenderPipeline>,
|
||||
debug_pipeline: Eventually<wgpu::RenderPipeline>,
|
||||
/// Queues [PhaseItems](crate::render::render_phase::PhaseItem) that depend on
|
||||
/// [`Prepare`](RenderStageLabel::Prepare) data and queue up draw calls to run during the
|
||||
/// [`Render`](RenderStageLabel::Render) stage.
|
||||
/// For example data is uploaded to the GPU in this stage.
|
||||
Queue,
|
||||
|
||||
globals_bind_group: Eventually<Globals>,
|
||||
/// Sort the [`RenderPhases`](crate::render_phase::RenderPhase) here.
|
||||
PhaseSort,
|
||||
|
||||
depth_texture: Eventually<Texture>,
|
||||
multisampling_texture: Eventually<Option<Texture>>,
|
||||
/// Actual rendering happens here.
|
||||
/// In most cases, only the render backend should insert resources here.
|
||||
Render,
|
||||
|
||||
surface: Surface,
|
||||
|
||||
mask_phase: RenderPhase<TileShape>,
|
||||
tile_phase: RenderPhase<(IndexEntry, TileShape)>,
|
||||
/// Cleanup render resources here.
|
||||
Cleanup,
|
||||
}
|
||||
|
||||
impl RenderState {
|
||||
impl StageLabel for RenderStageLabel {
|
||||
fn dyn_clone(&self) -> Box<dyn StageLabel> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RenderResources {
|
||||
pub surface: Surface,
|
||||
pub render_target: Eventually<TextureView>,
|
||||
pub depth_texture: Eventually<Texture>,
|
||||
pub multisampling_texture: Eventually<Option<Texture>>,
|
||||
}
|
||||
|
||||
impl RenderResources {
|
||||
pub fn new(surface: Surface) -> Self {
|
||||
Self {
|
||||
render_target: Default::default(),
|
||||
buffer_pool: Default::default(),
|
||||
tile_view_pattern: Default::default(),
|
||||
tile_pipeline: Default::default(),
|
||||
mask_pipeline: Default::default(),
|
||||
debug_pipeline: Default::default(),
|
||||
globals_bind_group: Default::default(),
|
||||
depth_texture: Default::default(),
|
||||
multisampling_texture: Default::default(),
|
||||
surface,
|
||||
mask_phase: Default::default(),
|
||||
tile_phase: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,21 +134,6 @@ impl RenderState {
|
||||
pub fn surface(&self) -> &Surface {
|
||||
&self.surface
|
||||
}
|
||||
|
||||
pub fn buffer_pool_mut(
|
||||
&mut self,
|
||||
) -> &mut Eventually<
|
||||
BufferPool<
|
||||
wgpu::Queue,
|
||||
wgpu::Buffer,
|
||||
ShaderVertex,
|
||||
IndexDataType,
|
||||
ShaderLayerMetadata,
|
||||
ShaderFeatureStyle,
|
||||
>,
|
||||
> {
|
||||
&mut self.buffer_pool
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Renderer {
|
||||
@ -152,7 +145,8 @@ pub struct Renderer {
|
||||
pub wgpu_settings: WgpuSettings,
|
||||
pub settings: RendererSettings,
|
||||
|
||||
pub state: RenderState,
|
||||
pub resources: RenderResources,
|
||||
pub render_graph: RenderGraph,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
@ -195,7 +189,8 @@ impl Renderer {
|
||||
adapter,
|
||||
wgpu_settings,
|
||||
settings,
|
||||
state: RenderState::new(surface),
|
||||
resources: RenderResources::new(surface),
|
||||
render_graph: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -229,12 +224,13 @@ impl Renderer {
|
||||
adapter,
|
||||
wgpu_settings,
|
||||
settings,
|
||||
state: RenderState::new(surface),
|
||||
resources: RenderResources::new(surface),
|
||||
render_graph: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.state.surface.resize(width, height)
|
||||
pub fn resize_surface(&mut self, width: u32, height: u32) {
|
||||
self.resources.surface.resize(width, height)
|
||||
}
|
||||
|
||||
/// Requests a device
|
||||
@ -398,17 +394,21 @@ impl Renderer {
|
||||
pub fn queue(&self) -> &wgpu::Queue {
|
||||
&self.queue
|
||||
}
|
||||
pub fn state(&self) -> &RenderState {
|
||||
&self.state
|
||||
pub fn state(&self) -> &RenderResources {
|
||||
&self.resources
|
||||
}
|
||||
pub fn surface(&self) -> &Surface {
|
||||
&self.state.surface
|
||||
&self.resources.surface
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod tests {
|
||||
use crate::window::{MapWindow, MapWindowConfig, WindowSize};
|
||||
use crate::{
|
||||
tcs::world::World,
|
||||
window::{MapWindow, MapWindowConfig, WindowSize},
|
||||
};
|
||||
|
||||
pub struct HeadlessMapWindowConfig {
|
||||
size: WindowSize,
|
||||
@ -432,14 +432,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[tokio::test]
|
||||
async fn test_render() {
|
||||
use log::LevelFilter;
|
||||
|
||||
use crate::render::{
|
||||
graph::RenderGraph, graph_runner::RenderGraphRunner, resource::Surface, RenderState,
|
||||
RendererSettings,
|
||||
graph::RenderGraph, graph_runner::RenderGraphRunner, resource::Surface,
|
||||
RenderResources, RendererSettings,
|
||||
};
|
||||
|
||||
let _ = env_logger::builder()
|
||||
@ -464,10 +463,9 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.expect("Unable to request device");
|
||||
|
||||
let render_state = RenderState::new(Surface::from_image(
|
||||
let render_state = RenderResources::new(Surface::from_image(
|
||||
&device,
|
||||
&HeadlessMapWindow {
|
||||
size: WindowSize::new(100, 100).unwrap(),
|
||||
@ -475,7 +473,8 @@ mod tests {
|
||||
&RendererSettings::default(),
|
||||
));
|
||||
|
||||
RenderGraphRunner::run(&graph, &device, &queue, &render_state).unwrap();
|
||||
let world = World::default();
|
||||
RenderGraphRunner::run(&graph, &device, &queue, &render_state, &world).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -494,40 +493,90 @@ pub mod main_graph {
|
||||
}
|
||||
|
||||
/// Labels for the "draw" graph
|
||||
pub mod draw_graph {
|
||||
mod draw_graph {
|
||||
pub const NAME: &str = "draw";
|
||||
// Labels for input nodes
|
||||
pub mod input {}
|
||||
// Labels for non-input nodes
|
||||
pub mod node {
|
||||
pub const MAIN_PASS: &str = "main_pass";
|
||||
pub const DEBUG_PASS: &str = "debug_pass";
|
||||
#[cfg(feature = "headless")]
|
||||
pub const COPY: &str = "copy";
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_default_render_graph() -> Result<RenderGraph, RenderGraphError> {
|
||||
let mut graph = RenderGraph::default();
|
||||
pub struct MaskPipeline(pub wgpu::RenderPipeline);
|
||||
impl Deref for MaskPipeline {
|
||||
type Target = wgpu::RenderPipeline;
|
||||
|
||||
let mut draw_graph = RenderGraph::default();
|
||||
// Draw nodes
|
||||
draw_graph.add_node(draw_graph::node::MAIN_PASS, MainPassNode::new());
|
||||
draw_graph.add_node(draw_graph::node::DEBUG_PASS, DebugPassNode::new());
|
||||
// Input node
|
||||
let input_node_id = draw_graph.set_input(vec![]);
|
||||
// Edges
|
||||
draw_graph.add_node_edge(input_node_id, draw_graph::node::MAIN_PASS)?;
|
||||
// TODO: Enable debug pass via runtime flag
|
||||
draw_graph.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::DEBUG_PASS)?;
|
||||
|
||||
graph.add_sub_graph(draw_graph::NAME, draw_graph);
|
||||
graph.add_node(main_graph::node::MAIN_PASS_DEPENDENCIES, EmptyNode);
|
||||
graph.add_node(main_graph::node::MAIN_PASS_DRIVER, MainPassDriverNode);
|
||||
graph.add_node_edge(
|
||||
main_graph::node::MAIN_PASS_DEPENDENCIES,
|
||||
main_graph::node::MAIN_PASS_DRIVER,
|
||||
)?;
|
||||
|
||||
Ok(graph)
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Do we really want a render plugin or do we want to statically do this setup?
|
||||
#[derive(Default)]
|
||||
pub struct RenderPlugin;
|
||||
|
||||
impl<E: Environment> Plugin<E> for RenderPlugin {
|
||||
fn build(
|
||||
&self,
|
||||
schedule: &mut Schedule,
|
||||
_kernel: Rc<Kernel<E>>,
|
||||
world: &mut World,
|
||||
graph: &mut RenderGraph,
|
||||
) {
|
||||
let resources = &mut world.resources;
|
||||
|
||||
let mut draw_graph = RenderGraph::default();
|
||||
// Draw nodes
|
||||
draw_graph.add_node(draw_graph::node::MAIN_PASS, MainPassNode::new());
|
||||
// Input node
|
||||
let input_node_id = draw_graph.set_input(vec![]);
|
||||
// Edges
|
||||
draw_graph
|
||||
.add_node_edge(input_node_id, draw_graph::node::MAIN_PASS)
|
||||
.unwrap();
|
||||
|
||||
graph.add_sub_graph(draw_graph::NAME, draw_graph);
|
||||
graph.add_node(main_graph::node::MAIN_PASS_DEPENDENCIES, EmptyNode);
|
||||
graph.add_node(main_graph::node::MAIN_PASS_DRIVER, MainPassDriverNode);
|
||||
graph
|
||||
.add_node_edge(
|
||||
main_graph::node::MAIN_PASS_DEPENDENCIES,
|
||||
main_graph::node::MAIN_PASS_DRIVER,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// render graph dependency
|
||||
resources.init::<RenderPhase<LayerItem>>();
|
||||
resources.init::<RenderPhase<TileMaskItem>>();
|
||||
// tile_view_pattern:
|
||||
resources.insert(Eventually::<WgpuTileViewPattern>::Uninitialized);
|
||||
resources.init::<ViewTileSources>();
|
||||
// masks
|
||||
resources.insert(Eventually::<MaskPipeline>::Uninitialized);
|
||||
|
||||
schedule.add_stage(RenderStageLabel::Extract, SystemStage::default());
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Prepare,
|
||||
SystemStage::default().with_system(SystemContainer::new(ResourceSystem)),
|
||||
);
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Queue,
|
||||
SystemStage::default()
|
||||
.with_system(tile_view_pattern_system)
|
||||
.with_system(upload_system),
|
||||
);
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::PhaseSort,
|
||||
SystemStage::default().with_system(sort_phase_system),
|
||||
);
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Render,
|
||||
SystemStage::default().with_system(SystemContainer::new(GraphRunnerSystem)),
|
||||
);
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Cleanup,
|
||||
SystemStage::default().with_system(cleanup_system),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,100 +1,54 @@
|
||||
//! Specifies the instructions which are going to be sent to the GPU. Render commands can be concatenated
|
||||
//! into a new render command which executes multiple instruction sets.
|
||||
|
||||
use crate::render::{
|
||||
eventually::Eventually::Initialized,
|
||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
|
||||
resource::{Globals, IndexEntry, TrackedRenderPass},
|
||||
tile_view_pattern::TileShape,
|
||||
RenderState, INDEX_FORMAT,
|
||||
use crate::{
|
||||
render::{
|
||||
eventually::{Eventually, Eventually::Initialized},
|
||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TileMaskItem},
|
||||
resource::TrackedRenderPass,
|
||||
tile_view_pattern::WgpuTileViewPattern,
|
||||
MaskPipeline,
|
||||
},
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
impl PhaseItem for TileShape {
|
||||
type SortKey = ();
|
||||
|
||||
fn sort_key(&self) -> Self::SortKey {}
|
||||
}
|
||||
|
||||
impl PhaseItem for (IndexEntry, TileShape) {
|
||||
type SortKey = u32;
|
||||
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
self.0.style_layer.index
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetViewBindGroup<const I: usize>;
|
||||
impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetViewBindGroup<I> {
|
||||
fn render<'w>(
|
||||
state: &'w RenderState,
|
||||
_item: &P,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Initialized(Globals { bind_group, .. }) = &state.globals_bind_group else { return RenderCommandResult::Failure; };
|
||||
pass.set_bind_group(0, bind_group, &[]);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetMaskPipeline;
|
||||
impl<P: PhaseItem> RenderCommand<P> for SetMaskPipeline {
|
||||
fn render<'w>(
|
||||
state: &'w RenderState,
|
||||
world: &'w World,
|
||||
_item: &P,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Initialized(pipeline) = &state.mask_pipeline else { return RenderCommandResult::Failure; };
|
||||
pass.set_render_pipeline(pipeline);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetDebugPipeline;
|
||||
impl<P: PhaseItem> RenderCommand<P> for SetDebugPipeline {
|
||||
fn render<'w>(
|
||||
state: &'w RenderState,
|
||||
_item: &P,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Initialized(pipeline) = &state.debug_pipeline else { return RenderCommandResult::Failure; };
|
||||
pass.set_render_pipeline(pipeline);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetTilePipeline;
|
||||
impl<P: PhaseItem> RenderCommand<P> for SetTilePipeline {
|
||||
fn render<'w>(
|
||||
state: &'w RenderState,
|
||||
_item: &P,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Initialized(pipeline) = &state.tile_pipeline else { return RenderCommandResult::Failure; };
|
||||
let Some(Initialized(pipeline)) = world.resources.get::<Eventually<MaskPipeline>>() else { return RenderCommandResult::Failure; };
|
||||
pass.set_render_pipeline(pipeline);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DrawMask;
|
||||
impl RenderCommand<TileShape> for DrawMask {
|
||||
impl RenderCommand<TileMaskItem> for DrawMask {
|
||||
fn render<'w>(
|
||||
state: &'w RenderState,
|
||||
source_shape: &TileShape,
|
||||
world: &'w World,
|
||||
item: &TileMaskItem,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Initialized(tile_view_pattern) = &state.tile_view_pattern else { return RenderCommandResult::Failure; };
|
||||
tracing::trace!("Drawing mask {}", &source_shape.coords());
|
||||
let Some(Initialized(tile_view_pattern)) = world
|
||||
.resources
|
||||
.get::<Eventually<WgpuTileViewPattern>>() else { return RenderCommandResult::Failure; };
|
||||
|
||||
let tile_mask = &item.source_shape;
|
||||
|
||||
// Draw mask with stencil value of e.g. parent
|
||||
let reference = source_shape.coords().stencil_reference_value_3d() as u32;
|
||||
let reference = tile_mask.coords().stencil_reference_value_3d() as u32;
|
||||
|
||||
pass.set_stencil_reference(reference);
|
||||
|
||||
let tile_view_pattern_buffer = tile_mask
|
||||
.buffer_range()
|
||||
.expect("tile_view_pattern needs to be uploaded first"); // FIXME tcs
|
||||
pass.set_vertex_buffer(
|
||||
0,
|
||||
// Mask is of the requested shape
|
||||
tile_view_pattern
|
||||
.buffer()
|
||||
.slice(source_shape.buffer_range()),
|
||||
tile_view_pattern.buffer().slice(tile_view_pattern_buffer),
|
||||
);
|
||||
const TILE_MASK_SHADER_VERTICES: u32 = 6;
|
||||
pass.draw(0..TILE_MASK_SHADER_VERTICES, 0..1);
|
||||
@ -103,80 +57,4 @@ impl RenderCommand<TileShape> for DrawMask {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DrawDebugOutline;
|
||||
impl RenderCommand<TileShape> for DrawDebugOutline {
|
||||
fn render<'w>(
|
||||
state: &'w RenderState,
|
||||
source_shape: &TileShape,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let Initialized(tile_view_pattern) = &state.tile_view_pattern else { return RenderCommandResult::Failure; };
|
||||
pass.set_vertex_buffer(
|
||||
0,
|
||||
tile_view_pattern
|
||||
.buffer()
|
||||
.slice(source_shape.buffer_range()),
|
||||
);
|
||||
const TILE_MASK_SHADER_VERTICES: u32 = 24;
|
||||
pass.draw(0..TILE_MASK_SHADER_VERTICES, 0..1);
|
||||
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DrawTile;
|
||||
impl RenderCommand<(IndexEntry, TileShape)> for DrawTile {
|
||||
fn render<'w>(
|
||||
state: &'w RenderState,
|
||||
(entry, shape): &(IndexEntry, TileShape),
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult {
|
||||
let (Initialized(buffer_pool), Initialized(tile_view_pattern)) =
|
||||
(&state.buffer_pool, &state.tile_view_pattern) else { return RenderCommandResult::Failure; };
|
||||
|
||||
// Uses stencil value of requested tile and the shape of the requested tile
|
||||
let reference = shape.coords().stencil_reference_value_3d() as u32;
|
||||
|
||||
tracing::trace!(
|
||||
"Drawing layer {:?} at {}",
|
||||
entry.style_layer.source_layer,
|
||||
&entry.coords
|
||||
);
|
||||
|
||||
let index_range = entry.indices_buffer_range();
|
||||
|
||||
if index_range.is_empty() {
|
||||
tracing::error!("Tried to draw a vector tile without any vertices");
|
||||
return RenderCommandResult::Failure;
|
||||
}
|
||||
|
||||
pass.set_stencil_reference(reference);
|
||||
|
||||
pass.set_index_buffer(buffer_pool.indices().slice(index_range), INDEX_FORMAT);
|
||||
pass.set_vertex_buffer(
|
||||
0,
|
||||
buffer_pool.vertices().slice(entry.vertices_buffer_range()),
|
||||
);
|
||||
pass.set_vertex_buffer(1, tile_view_pattern.buffer().slice(shape.buffer_range()));
|
||||
pass.set_vertex_buffer(
|
||||
2,
|
||||
buffer_pool
|
||||
.metadata()
|
||||
.slice(entry.layer_metadata_buffer_range()),
|
||||
);
|
||||
pass.set_vertex_buffer(
|
||||
3,
|
||||
buffer_pool
|
||||
.feature_metadata()
|
||||
.slice(entry.feature_metadata_buffer_range()),
|
||||
);
|
||||
pass.draw_indexed(entry.indices_range(), 0, 0..1);
|
||||
RenderCommandResult::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub type DrawTiles = (SetTilePipeline, SetViewBindGroup<0>, DrawTile);
|
||||
|
||||
pub type DrawMasks = (SetMaskPipeline, DrawMask);
|
||||
|
||||
pub type DrawDebugOutlines = (SetDebugPipeline, DrawDebugOutline);
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
use crate::render::{resource::TrackedRenderPass, RenderState};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{render::resource::TrackedRenderPass, tcs::world::World};
|
||||
|
||||
/// A draw function which is used to draw a specific [`PhaseItem`].
|
||||
///
|
||||
@ -6,7 +8,7 @@ use crate::render::{resource::TrackedRenderPass, RenderState};
|
||||
/// are more modular.
|
||||
pub trait Draw<P: PhaseItem>: 'static {
|
||||
/// Draws the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`].
|
||||
fn draw<'w>(&mut self, pass: &mut TrackedRenderPass<'w>, state: &'w RenderState, item: &P);
|
||||
fn draw<'w>(&self, pass: &mut TrackedRenderPass<'w>, wold: &'w World, item: &P);
|
||||
}
|
||||
|
||||
/// An item which will be drawn to the screen. A phase item should be queued up for rendering
|
||||
@ -19,6 +21,8 @@ pub trait PhaseItem {
|
||||
type SortKey: Ord;
|
||||
/// Determines the order in which the items are drawn during the corresponding [`RenderPhase`](super::RenderPhase).
|
||||
fn sort_key(&self) -> Self::SortKey;
|
||||
|
||||
fn draw_function(&self) -> &dyn Draw<Self>;
|
||||
}
|
||||
|
||||
/// [`RenderCommand`] is a trait that runs an ECS query and produces one or more
|
||||
@ -42,8 +46,9 @@ pub trait PhaseItem {
|
||||
/// ```
|
||||
pub trait RenderCommand<P: PhaseItem> {
|
||||
/// Renders the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`].
|
||||
// TODO: reorder the arguments to match Node and Draw
|
||||
fn render<'w>(
|
||||
state: &'w RenderState,
|
||||
world: &'w World,
|
||||
item: &P,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult;
|
||||
@ -59,11 +64,11 @@ macro_rules! render_command_tuple_impl {
|
||||
impl<P: PhaseItem, $($name: RenderCommand<P>),*> RenderCommand<P> for ($($name,)*) {
|
||||
#[allow(non_snake_case)]
|
||||
fn render<'w>(
|
||||
_state: &'w RenderState,
|
||||
_item: &P,
|
||||
_pass: &mut TrackedRenderPass<'w>,
|
||||
world: &'w World,
|
||||
item: &P,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
) -> RenderCommandResult{
|
||||
$(if let RenderCommandResult::Failure = $name::render(_state, _item, _pass) {
|
||||
$(if let RenderCommandResult::Failure = $name::render(world, item, pass) {
|
||||
return RenderCommandResult::Failure;
|
||||
})*
|
||||
RenderCommandResult::Success
|
||||
@ -78,13 +83,27 @@ render_command_tuple_impl!(C0, C1, C2);
|
||||
render_command_tuple_impl!(C0, C1, C2, C3);
|
||||
render_command_tuple_impl!(C0, C1, C2, C3, C4);
|
||||
|
||||
impl<P, C: 'static> Draw<P> for C
|
||||
pub struct DrawState<C, P> {
|
||||
phantom_p: PhantomData<P>,
|
||||
phantom_c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C, P> DrawState<C, P> {
|
||||
pub(crate) fn new() -> Self {
|
||||
DrawState {
|
||||
phantom_p: Default::default(),
|
||||
phantom_c: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: 'static, C: 'static> Draw<P> for DrawState<P, C>
|
||||
where
|
||||
P: PhaseItem,
|
||||
C: RenderCommand<P>,
|
||||
{
|
||||
/// Prepares data for the wrapped [`RenderCommand`] and then renders it.
|
||||
fn draw<'w>(&mut self, pass: &mut TrackedRenderPass<'w>, state: &'w RenderState, item: &P) {
|
||||
C::render(state, item, pass);
|
||||
fn draw<'w>(&self, pass: &mut TrackedRenderPass<'w>, world: &'w World, item: &P) {
|
||||
C::render(world, item, pass);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,23 @@
|
||||
//! Describes the concept of a [`RenderPhase`] and [`PhaseItem`]
|
||||
|
||||
mod draw;
|
||||
|
||||
pub use draw::*;
|
||||
|
||||
use crate::{render::tile_view_pattern::TileShape, tcs::tiles::Tile};
|
||||
|
||||
mod draw;
|
||||
|
||||
/// A resource to collect and sort draw requests for specific [`PhaseItems`](PhaseItem).
|
||||
pub struct RenderPhase<I: PhaseItem> {
|
||||
pub items: Vec<I>,
|
||||
items: Vec<I>,
|
||||
}
|
||||
|
||||
impl<'a, I: PhaseItem> IntoIterator for &'a RenderPhase<I> {
|
||||
type Item = <&'a Vec<I> as IntoIterator>::Item;
|
||||
type IntoIter = <&'a Vec<I> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.items.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: PhaseItem> Default for RenderPhase<I> {
|
||||
@ -17,7 +28,6 @@ impl<I: PhaseItem> Default for RenderPhase<I> {
|
||||
|
||||
impl<I: PhaseItem> RenderPhase<I> {
|
||||
/// Adds a [`PhaseItem`] to this render phase.
|
||||
#[inline]
|
||||
pub fn add(&mut self, item: I) {
|
||||
self.items.push(item);
|
||||
}
|
||||
@ -26,4 +36,51 @@ impl<I: PhaseItem> RenderPhase<I> {
|
||||
pub fn sort(&mut self) {
|
||||
self.items.sort_by_key(|d| d.sort_key());
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.items.clear();
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LayerItem {
|
||||
pub draw_function: Box<dyn Draw<LayerItem>>,
|
||||
pub index: u32,
|
||||
|
||||
pub style_layer: String,
|
||||
|
||||
pub tile: Tile,
|
||||
pub source_shape: TileShape, // FIXME tcs: TileShape contains buffer ranges. This is bad, move them to a component?
|
||||
}
|
||||
|
||||
impl PhaseItem for LayerItem {
|
||||
type SortKey = u32;
|
||||
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
self.index
|
||||
}
|
||||
|
||||
fn draw_function(&self) -> &dyn Draw<LayerItem> {
|
||||
self.draw_function.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TileMaskItem {
|
||||
pub draw_function: Box<dyn Draw<TileMaskItem>>,
|
||||
pub source_shape: TileShape,
|
||||
}
|
||||
|
||||
impl PhaseItem for TileMaskItem {
|
||||
type SortKey = u32;
|
||||
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
0
|
||||
}
|
||||
|
||||
fn draw_function(&self) -> &dyn Draw<TileMaskItem> {
|
||||
self.draw_function.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
12
maplibre/src/render/resource/buffer.rs
Normal file
12
maplibre/src/render/resource/buffer.rs
Normal file
@ -0,0 +1,12 @@
|
||||
pub struct BackingBufferDescriptor<B> {
|
||||
/// The buffer which is used
|
||||
pub(crate) buffer: B,
|
||||
/// The size of buffer
|
||||
pub(crate) inner_size: wgpu::BufferAddress,
|
||||
}
|
||||
|
||||
impl<B> BackingBufferDescriptor<B> {
|
||||
pub fn new(buffer: B, inner_size: wgpu::BufferAddress) -> Self {
|
||||
Self { buffer, inner_size }
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
//! A bind group which binds a buffer with global data like the current camera transformations.
|
||||
|
||||
use std::{cmp, mem::size_of};
|
||||
|
||||
use crate::{platform::MIN_WEBGL_BUFFER_SIZE, render::shaders::ShaderGlobals};
|
||||
|
||||
pub struct Globals {
|
||||
pub uniform_buffer: wgpu::Buffer,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
impl Globals {
|
||||
pub fn from_device(device: &wgpu::Device, group: &wgpu::BindGroupLayout) -> Self {
|
||||
let globals_buffer_byte_size =
|
||||
cmp::max(MIN_WEBGL_BUFFER_SIZE, size_of::<ShaderGlobals>() as u64);
|
||||
|
||||
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Globals ubo"),
|
||||
size: globals_buffer_byte_size,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Bind group"),
|
||||
layout: group,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(uniform_buffer.as_entire_buffer_binding()),
|
||||
}],
|
||||
});
|
||||
Self {
|
||||
uniform_buffer,
|
||||
bind_group,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,22 @@
|
||||
//! Utilities which holds references to GPU-owned. Usually a resource is a wrapper which makes using
|
||||
//! buffers or textures simpler.
|
||||
|
||||
mod buffer_pool;
|
||||
mod globals;
|
||||
mod pipeline;
|
||||
mod shader;
|
||||
mod surface;
|
||||
mod texture;
|
||||
mod tracked_render_pass;
|
||||
|
||||
pub use buffer_pool::*;
|
||||
pub use globals::*;
|
||||
pub use buffer::*;
|
||||
pub use pipeline::*;
|
||||
pub use shader::*;
|
||||
pub use surface::*;
|
||||
pub use texture::*;
|
||||
pub use tile_pipeline::*;
|
||||
pub use tracked_render_pass::*;
|
||||
|
||||
mod buffer;
|
||||
mod pipeline;
|
||||
mod shader;
|
||||
mod surface;
|
||||
mod texture;
|
||||
mod tile_pipeline;
|
||||
mod tracked_render_pass;
|
||||
|
||||
pub trait Queue<B> {
|
||||
fn write_buffer(&self, buffer: &B, offset: wgpu::BufferAddress, data: &[u8]);
|
||||
}
|
||||
|
||||
@ -203,7 +203,7 @@ impl Surface {
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
// FIXME: Is this a sane default?
|
||||
// TODO: Is this a sane default?
|
||||
let format = settings
|
||||
.texture_format
|
||||
.unwrap_or(wgpu::TextureFormat::Rgba8Unorm);
|
||||
|
||||
@ -65,7 +65,7 @@ impl Deref for TextureView {
|
||||
}
|
||||
|
||||
pub struct Texture {
|
||||
pub size: (u32, u32),
|
||||
pub size: wgpu::Extent3d,
|
||||
pub texture: wgpu::Texture,
|
||||
pub view: TextureView,
|
||||
}
|
||||
@ -78,23 +78,26 @@ impl Texture {
|
||||
width: u32,
|
||||
height: u32,
|
||||
msaa: Msaa,
|
||||
usage: wgpu::TextureUsages,
|
||||
) -> Texture {
|
||||
let size = wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label,
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: msaa.samples,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
usage,
|
||||
});
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
Self {
|
||||
size: (width, height),
|
||||
size,
|
||||
texture,
|
||||
view: TextureView::TextureView(view),
|
||||
}
|
||||
@ -105,6 +108,7 @@ impl HasChanged for Texture {
|
||||
type Criteria = (u32, u32);
|
||||
|
||||
fn has_changed(&self, criteria: &Self::Criteria) -> bool {
|
||||
!self.size.eq(criteria)
|
||||
let size = (self.size.width, self.size.height);
|
||||
!size.eq(criteria)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
//! Utility for declaring pipelines.
|
||||
|
||||
use std::cmp;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::{
|
||||
platform::MIN_WEBGL_BUFFER_SIZE,
|
||||
render::{
|
||||
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
|
||||
settings::RendererSettings,
|
||||
shaders::ShaderGlobals,
|
||||
},
|
||||
use crate::render::{
|
||||
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
|
||||
settings::RendererSettings,
|
||||
};
|
||||
|
||||
pub struct TilePipeline {
|
||||
bind_globals: bool,
|
||||
name: Cow<'static, str>,
|
||||
/// Is the depth stencil used?
|
||||
depth_stencil_enabled: bool,
|
||||
/// This pipeline updates the stenctil
|
||||
@ -21,6 +17,7 @@ pub struct TilePipeline {
|
||||
debug_stencil: bool,
|
||||
wireframe: bool,
|
||||
multisampling: bool,
|
||||
raster: bool,
|
||||
settings: RendererSettings,
|
||||
|
||||
vertex_state: VertexState,
|
||||
@ -28,24 +25,26 @@ pub struct TilePipeline {
|
||||
}
|
||||
|
||||
impl TilePipeline {
|
||||
pub(crate) fn new(
|
||||
pub fn new(
|
||||
name: Cow<'static, str>,
|
||||
settings: RendererSettings,
|
||||
vertex_state: VertexState,
|
||||
fragment_state: FragmentState,
|
||||
bind_globals: bool,
|
||||
depth_stencil_enabled: bool,
|
||||
update_stencil: bool,
|
||||
debug_stencil: bool,
|
||||
wireframe: bool,
|
||||
multisampling: bool,
|
||||
raster: bool,
|
||||
) -> Self {
|
||||
TilePipeline {
|
||||
bind_globals,
|
||||
name,
|
||||
depth_stencil_enabled,
|
||||
update_stencil,
|
||||
debug_stencil,
|
||||
wireframe,
|
||||
multisampling,
|
||||
raster,
|
||||
settings,
|
||||
vertex_state,
|
||||
fragment_state,
|
||||
@ -75,24 +74,27 @@ impl RenderPipeline for TilePipeline {
|
||||
}
|
||||
};
|
||||
|
||||
let globals_buffer_byte_size = cmp::max(
|
||||
MIN_WEBGL_BUFFER_SIZE,
|
||||
std::mem::size_of::<ShaderGlobals>() as u64,
|
||||
);
|
||||
|
||||
RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: if self.bind_globals {
|
||||
Some(vec![vec![wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(globals_buffer_byte_size),
|
||||
label: Some(self.name),
|
||||
layout: if self.raster {
|
||||
Some(vec![vec![
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
count: None,
|
||||
}]])
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
]])
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@ -86,11 +86,11 @@ impl Shader for TileMaskShader {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TileShader {
|
||||
pub struct VectorTileShader {
|
||||
pub format: wgpu::TextureFormat,
|
||||
}
|
||||
|
||||
impl Shader for TileShader {
|
||||
impl Shader for VectorTileShader {
|
||||
fn describe_vertex(&self) -> VertexState {
|
||||
VertexState {
|
||||
source: include_str!("tile.vertex.wgsl"),
|
||||
@ -283,3 +283,102 @@ impl ShaderTileMetadata {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Pod, Zeroable)]
|
||||
pub struct ShaderTextureVertex {
|
||||
pub position: Vec2f32,
|
||||
pub tex_coords: Vec2f32,
|
||||
}
|
||||
|
||||
impl ShaderTextureVertex {
|
||||
pub fn new(position: Vec2f32, tex_coords: Vec2f32) -> Self {
|
||||
Self {
|
||||
position,
|
||||
tex_coords,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ShaderTextureVertex {
|
||||
fn default() -> Self {
|
||||
ShaderTextureVertex::new([0.0, 0.0], [0.0, 0.0])
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RasterTileShader {
|
||||
pub format: wgpu::TextureFormat,
|
||||
}
|
||||
|
||||
impl Shader for RasterTileShader {
|
||||
fn describe_vertex(&self) -> VertexState {
|
||||
VertexState {
|
||||
source: include_str!("tile_raster.vertex.wgsl"),
|
||||
entry_point: "main",
|
||||
buffers: vec![
|
||||
// tile metadata
|
||||
VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<ShaderTileMetadata>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: vec![
|
||||
// translate
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
shader_location: 4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: 1 * wgpu::VertexFormat::Float32x4.size(),
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
shader_location: 5,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: 2 * wgpu::VertexFormat::Float32x4.size(),
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
shader_location: 6,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: 3 * wgpu::VertexFormat::Float32x4.size(),
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
shader_location: 7,
|
||||
},
|
||||
// zoom_factor
|
||||
wgpu::VertexAttribute {
|
||||
offset: 4 * wgpu::VertexFormat::Float32x4.size(),
|
||||
format: wgpu::VertexFormat::Float32,
|
||||
shader_location: 9,
|
||||
},
|
||||
],
|
||||
},
|
||||
// layer metadata
|
||||
VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<ShaderLayerMetadata>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: vec![
|
||||
// z_index
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
format: wgpu::VertexFormat::Float32,
|
||||
shader_location: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn describe_fragment(&self) -> FragmentState {
|
||||
FragmentState {
|
||||
source: include_str!("tile_raster.fragment.wgsl"),
|
||||
entry_point: "main",
|
||||
targets: vec![Some(wgpu::ColorTargetState {
|
||||
format: self.format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent::REPLACE,
|
||||
alpha: wgpu::BlendComponent::REPLACE,
|
||||
}),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ fn main(
|
||||
vec3<f32>(EXTENT, EXTENT - WIDTH, z)
|
||||
);
|
||||
|
||||
let a_position = VERTICES[vertex_idx];
|
||||
let vertex = VERTICES[vertex_idx];
|
||||
|
||||
let scaling: mat3x3<f32> = mat3x3<f32>(
|
||||
vec3<f32>(target_width, 0.0, 0.0),
|
||||
@ -69,7 +69,7 @@ fn main(
|
||||
vec3<f32>(0.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
var position = mat4x4<f32>(translate1, translate2, translate3, translate4) * vec4<f32>((scaling * a_position), 1.0);
|
||||
var position = mat4x4<f32>(translate1, translate2, translate3, translate4) * vec4<f32>((scaling * vertex), 1.0);
|
||||
position.z = 1.0;
|
||||
return VertexOutput(DEBUG_COLOR, position);
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ fn main(
|
||||
vec3<f32>(0.0, EXTENT, z),
|
||||
vec3<f32>(EXTENT, EXTENT, z),
|
||||
);
|
||||
let a_position = VERTICES[vertex_idx];
|
||||
let vertex = VERTICES[vertex_idx];
|
||||
|
||||
let scaling: mat3x3<f32> = mat3x3<f32>(
|
||||
vec3<f32>(target_width, 0.0, 0.0),
|
||||
@ -37,7 +37,7 @@ fn main(
|
||||
vec3<f32>(0.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
var position = mat4x4<f32>(translate1, translate2, translate3, translate4) * vec4<f32>((scaling * a_position), 1.0);
|
||||
var position = mat4x4<f32>(translate1, translate2, translate3, translate4) * vec4<f32>((scaling * vertex), 1.0);
|
||||
// FIXME: how to fix z-fighting?
|
||||
position.z = 1.0;
|
||||
|
||||
|
||||
14
maplibre/src/render/shaders/tile_raster.fragment.wgsl
Normal file
14
maplibre/src/render/shaders/tile_raster.fragment.wgsl
Normal file
@ -0,0 +1,14 @@
|
||||
struct VertexOutput {
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
@builtin(position) position: vec4<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
@group(0) @binding(1)
|
||||
var s_diffuse: sampler;
|
||||
|
||||
@fragment
|
||||
fn main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
}
|
||||
48
maplibre/src/render/shaders/tile_raster.vertex.wgsl
Normal file
48
maplibre/src/render/shaders/tile_raster.vertex.wgsl
Normal file
@ -0,0 +1,48 @@
|
||||
struct VertexOutput {
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
};
|
||||
|
||||
var<private> EXTENT: f32 = 4096.0;
|
||||
|
||||
@vertex
|
||||
fn main(
|
||||
@location(4) translate1: vec4<f32>,
|
||||
@location(5) translate2: vec4<f32>,
|
||||
@location(6) translate3: vec4<f32>,
|
||||
@location(7) translate4: vec4<f32>,
|
||||
@location(9) zoom_factor: f32,
|
||||
|
||||
@location(10) z_index: f32,
|
||||
|
||||
@builtin(vertex_index) vertex_idx: u32,
|
||||
) -> VertexOutput {
|
||||
let z = 0.0;
|
||||
|
||||
var VERTICES: array<vec3<f32>, 6> = array<vec3<f32>, 6>(
|
||||
// Tile vertices
|
||||
vec3<f32>(0.0, 0.0, z),
|
||||
vec3<f32>(0.0, EXTENT, z),
|
||||
vec3<f32>(EXTENT, 0.0, z),
|
||||
vec3<f32>(EXTENT, 0.0, z),
|
||||
vec3<f32>(0.0, EXTENT, z),
|
||||
vec3<f32>(EXTENT, EXTENT, z),
|
||||
);
|
||||
let vertex = VERTICES[vertex_idx];
|
||||
|
||||
|
||||
var TEX_COORDS: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
||||
vec2<f32>(0.0, 0.0),
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(1.0, 0.0),
|
||||
vec2<f32>(1.0, 0.0),
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
);
|
||||
let tex_coords = TEX_COORDS[vertex_idx];
|
||||
|
||||
var position = mat4x4<f32>(translate1, translate2, translate3, translate4) * vec4<f32>(vertex, 1.0);
|
||||
position.z = z_index;
|
||||
|
||||
return VertexOutput(tex_coords, position);
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
//! Extracts data from the current state.
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{eventually::Eventually::Initialized, RenderState, Renderer},
|
||||
schedule::Stage,
|
||||
world::World,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExtractStage;
|
||||
|
||||
impl Stage for ExtractStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
world: World { view_state, .. },
|
||||
renderer:
|
||||
Renderer {
|
||||
state:
|
||||
RenderState {
|
||||
mask_phase: _,
|
||||
tile_phase: _,
|
||||
tile_view_pattern,
|
||||
buffer_pool,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let (Initialized(tile_view_pattern), Initialized(buffer_pool)) =
|
||||
(tile_view_pattern, &buffer_pool) else { return; };
|
||||
|
||||
let view_region = view_state.create_view_region();
|
||||
|
||||
if let Some(view_region) = &view_region {
|
||||
let zoom = view_state.zoom();
|
||||
tile_view_pattern.update_pattern(view_region, buffer_pool, zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
//! Rendering specific [Stages](Stage)
|
||||
|
||||
use graph_runner_stage::GraphRunnerStage;
|
||||
use resource_stage::ResourceStage;
|
||||
use upload_stage::UploadStage;
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
multi_stage,
|
||||
render::{
|
||||
graph::RenderGraph,
|
||||
stages::{
|
||||
extract_stage::ExtractStage, phase_sort_stage::PhaseSortStage, queue_stage::QueueStage,
|
||||
},
|
||||
},
|
||||
schedule::{Schedule, Stage, StageLabel},
|
||||
};
|
||||
|
||||
mod extract_stage;
|
||||
mod graph_runner_stage;
|
||||
mod phase_sort_stage;
|
||||
mod queue_stage;
|
||||
mod resource_stage;
|
||||
mod upload_stage;
|
||||
|
||||
/// The labels of the default App rendering stages.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub enum RenderStageLabel {
|
||||
/// Prepare render resources from the extracted data for the GPU.
|
||||
/// For example during this phase textures are created, buffers are allocated and written.
|
||||
Prepare,
|
||||
|
||||
/// Queues [PhaseItems](crate::render::render_phase::draw::PhaseItem) that depend on
|
||||
/// [`Prepare`](RenderStageLabel::Prepare) data and queue up draw calls to run during the
|
||||
/// [`Render`](RenderStageLabel::Render) stage.
|
||||
Queue,
|
||||
|
||||
/// Sort the [`RenderPhases`](crate::render_phase::RenderPhase) here.
|
||||
PhaseSort,
|
||||
|
||||
/// Actual rendering happens here.
|
||||
/// In most cases, only the render backend should insert resources here.
|
||||
Render,
|
||||
|
||||
/// Cleanup render resources here.
|
||||
Cleanup,
|
||||
}
|
||||
|
||||
impl StageLabel for RenderStageLabel {
|
||||
fn dyn_clone(&self) -> Box<dyn StageLabel> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
multi_stage!(
|
||||
PrepareStage,
|
||||
resource: ResourceStage,
|
||||
extract: ExtractStage,
|
||||
upload: UploadStage
|
||||
);
|
||||
|
||||
pub fn register_default_render_stages(graph: RenderGraph, schedule: &mut Schedule) {
|
||||
schedule.add_stage(RenderStageLabel::Prepare, PrepareStage::default());
|
||||
schedule.add_stage(RenderStageLabel::Queue, QueueStage::default());
|
||||
schedule.add_stage(RenderStageLabel::PhaseSort, PhaseSortStage::default());
|
||||
schedule.add_stage(RenderStageLabel::Render, GraphRunnerStage::new(graph));
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
//! Sorts items of the [RenderPhases](RenderPhase).
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{render_phase::RenderPhase, Renderer},
|
||||
schedule::Stage,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PhaseSortStage;
|
||||
|
||||
impl Stage for PhaseSortStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
renderer: Renderer { state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let mask_phase: &mut RenderPhase<_> = &mut state.mask_phase;
|
||||
mask_phase.sort();
|
||||
let file_phase = &mut state.tile_phase;
|
||||
file_phase.sort();
|
||||
}
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
//! Queues [PhaseItems](crate::render::render_phase::PhaseItem) for rendering.
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{eventually::Eventually::Initialized, resource::IndexEntry, RenderState, Renderer},
|
||||
schedule::Stage,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct QueueStage;
|
||||
|
||||
impl Stage for QueueStage {
|
||||
#[tracing::instrument(name = "QueueStage", skip_all)]
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
renderer:
|
||||
Renderer {
|
||||
state:
|
||||
RenderState {
|
||||
mask_phase,
|
||||
tile_phase,
|
||||
tile_view_pattern,
|
||||
buffer_pool,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
mask_phase.items.clear();
|
||||
tile_phase.items.clear();
|
||||
|
||||
let (Initialized(tile_view_pattern), Initialized(buffer_pool)) =
|
||||
(tile_view_pattern, &buffer_pool) else { return; };
|
||||
|
||||
let index = buffer_pool.index();
|
||||
|
||||
for view_tile in tile_view_pattern.iter() {
|
||||
let coords = &view_tile.coords();
|
||||
tracing::trace!("Drawing tile at {coords}");
|
||||
|
||||
// draw tile normal or the source e.g. parent or children
|
||||
view_tile.render(|source_shape| {
|
||||
// Draw masks for all source_shapes
|
||||
mask_phase.add(source_shape.clone());
|
||||
|
||||
let Some(entries) = index.get_layers(&source_shape.coords()) else {
|
||||
tracing::trace!("No layers found at {}", &source_shape.coords());
|
||||
return;
|
||||
};
|
||||
|
||||
let mut layers_to_render: Vec<&IndexEntry> = Vec::from_iter(entries);
|
||||
layers_to_render.sort_by_key(|entry| entry.style_layer.index);
|
||||
|
||||
for entry in layers_to_render {
|
||||
// Draw tile
|
||||
tile_phase.add((entry.clone(), source_shape.clone()))
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,222 +0,0 @@
|
||||
//! Uploads data to the GPU which is needed for rendering.
|
||||
|
||||
use std::iter;
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::ViewRegion,
|
||||
io::tile_repository::{StoredLayer, TileRepository},
|
||||
render::{
|
||||
camera::ViewProjection,
|
||||
eventually::Eventually::Initialized,
|
||||
shaders::{ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32},
|
||||
RenderState, Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
style::Style,
|
||||
world::World,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UploadStage;
|
||||
|
||||
impl Stage for UploadStage {
|
||||
#[tracing::instrument(name = "UploadStage", skip_all)]
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
world:
|
||||
World {
|
||||
tile_repository,
|
||||
view_state,
|
||||
..
|
||||
},
|
||||
style,
|
||||
renderer: Renderer { queue, state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let view_proj = view_state.view_projection();
|
||||
|
||||
if let Initialized(globals_bind_group) = &state.globals_bind_group {
|
||||
// Update globals
|
||||
queue.write_buffer(
|
||||
&globals_bind_group.uniform_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[ShaderGlobals::new(ShaderCamera::new(
|
||||
view_proj.downcast().into(),
|
||||
view_state
|
||||
.camera()
|
||||
.position()
|
||||
.to_homogeneous()
|
||||
.cast::<f32>()
|
||||
.unwrap() // TODO: Remove unwrap
|
||||
.into(),
|
||||
))]),
|
||||
);
|
||||
}
|
||||
|
||||
let view_region = view_state.create_view_region();
|
||||
|
||||
if let Some(view_region) = &view_region {
|
||||
self.upload_tile_geometry(state, queue, tile_repository, style, view_region);
|
||||
self.upload_tile_view_pattern(state, queue, &view_proj);
|
||||
//self.update_metadata(state, tile_repository, queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UploadStage {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) fn update_metadata(
|
||||
&self,
|
||||
RenderState { buffer_pool, .. }: &mut RenderState,
|
||||
tile_repository: &TileRepository,
|
||||
queue: &wgpu::Queue,
|
||||
) {
|
||||
let Initialized(buffer_pool) = buffer_pool else { return; };
|
||||
|
||||
let animated_one = 0.5
|
||||
* (1.0
|
||||
+ ((std::time::SystemTime::now()
|
||||
.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs_f64())
|
||||
.sin()));
|
||||
|
||||
for entries in buffer_pool.index().iter() {
|
||||
for entry in entries {
|
||||
let world_coords = entry.coords;
|
||||
|
||||
let source_layer = entry.style_layer.source_layer.as_ref().unwrap();
|
||||
|
||||
let Some(stored_layer) =
|
||||
tile_repository
|
||||
.iter_layers_at(&world_coords)
|
||||
.and_then(|mut layers| {
|
||||
layers.find(|layer| source_layer.as_str() == layer.layer_name())
|
||||
}) else { continue; };
|
||||
|
||||
let color: Option<Vec4f32> = entry
|
||||
.style_layer
|
||||
.paint
|
||||
.as_ref()
|
||||
.and_then(|paint| paint.get_color())
|
||||
.map(|mut color| {
|
||||
color.color.b = animated_one as f32;
|
||||
color.into()
|
||||
});
|
||||
|
||||
match stored_layer {
|
||||
StoredLayer::UnavailableLayer { .. } => {}
|
||||
StoredLayer::TessellatedLayer {
|
||||
feature_indices, ..
|
||||
} => {
|
||||
/* let feature_metadata = layer_data
|
||||
.features()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(i, _feature)| {
|
||||
iter::repeat(ShaderFeatureStyle {
|
||||
color: color.unwrap(),
|
||||
})
|
||||
.take(feature_indices[i] as usize)
|
||||
})
|
||||
.collect::<Vec<_>>();*/
|
||||
|
||||
let feature_metadata = (0..feature_indices.len())
|
||||
.flat_map(|i| {
|
||||
iter::repeat(ShaderFeatureStyle {
|
||||
color: color.unwrap(),
|
||||
})
|
||||
.take(feature_indices[i] as usize)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
buffer_pool.update_feature_metadata(queue, entry, &feature_metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn upload_tile_view_pattern(
|
||||
&self,
|
||||
RenderState {
|
||||
tile_view_pattern, ..
|
||||
}: &mut RenderState,
|
||||
queue: &wgpu::Queue,
|
||||
view_proj: &ViewProjection,
|
||||
) {
|
||||
let Initialized(tile_view_pattern) = tile_view_pattern else { return; };
|
||||
tile_view_pattern.upload_pattern(queue, view_proj);
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn upload_tile_geometry(
|
||||
&self,
|
||||
RenderState { buffer_pool, .. }: &mut RenderState,
|
||||
queue: &wgpu::Queue,
|
||||
tile_repository: &TileRepository,
|
||||
style: &Style,
|
||||
view_region: &ViewRegion,
|
||||
) {
|
||||
let Initialized(buffer_pool) = buffer_pool else { return; };
|
||||
|
||||
// Upload all tessellated layers which are in view
|
||||
for coords in view_region.iter() {
|
||||
let Some(available_layers) =
|
||||
tile_repository.iter_loaded_layers_at(buffer_pool, &coords) else { continue; };
|
||||
|
||||
for style_layer in &style.layers {
|
||||
let source_layer = style_layer.source_layer.as_ref().unwrap(); // TODO: Remove unwrap
|
||||
|
||||
let Some(stored_layer) = available_layers
|
||||
.iter()
|
||||
.find(|layer| source_layer.as_str() == layer.layer_name()) else { continue; };
|
||||
|
||||
let color: Option<Vec4f32> = style_layer
|
||||
.paint
|
||||
.as_ref()
|
||||
.and_then(|paint| paint.get_color())
|
||||
.map(|color| color.into());
|
||||
|
||||
match stored_layer {
|
||||
StoredLayer::UnavailableLayer { .. } => {}
|
||||
StoredLayer::TessellatedLayer {
|
||||
coords,
|
||||
feature_indices,
|
||||
buffer,
|
||||
..
|
||||
} => {
|
||||
let allocate_feature_metadata =
|
||||
tracing::span!(tracing::Level::TRACE, "allocate_feature_metadata");
|
||||
|
||||
let guard = allocate_feature_metadata.enter();
|
||||
let feature_metadata = (0..feature_indices.len()) // FIXME: Iterate over actual featrues
|
||||
.enumerate()
|
||||
.flat_map(|(i, _feature)| {
|
||||
iter::repeat(ShaderFeatureStyle {
|
||||
color: color.unwrap(),
|
||||
})
|
||||
.take(feature_indices[i] as usize)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
drop(guard);
|
||||
|
||||
tracing::trace!("Allocating geometry at {}", &coords);
|
||||
buffer_pool.allocate_layer_geometry(
|
||||
queue,
|
||||
*coords,
|
||||
style_layer.clone(),
|
||||
buffer,
|
||||
ShaderLayerMetadata::new(style_layer.index as f32),
|
||||
&feature_metadata,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
maplibre/src/render/systems/cleanup_system.rs
Normal file
16
maplibre/src/render/systems/cleanup_system.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::render_phase::{LayerItem, RenderPhase, TileMaskItem},
|
||||
};
|
||||
|
||||
pub fn cleanup_system(MapContext { world, .. }: &mut MapContext) {
|
||||
let Some((layer_item_phase, tile_mask_phase)) = world
|
||||
.resources
|
||||
.query_mut::<(
|
||||
&mut RenderPhase<LayerItem>,
|
||||
&mut RenderPhase<TileMaskItem>,
|
||||
)>() else { return; };
|
||||
|
||||
layer_item_phase.clear();
|
||||
tile_mask_phase.clear();
|
||||
}
|
||||
@ -1,44 +1,42 @@
|
||||
//! Executes the [`RenderGraph`] current render graph.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use log::error;
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
eventually::Eventually::Initialized, graph::RenderGraph, graph_runner::RenderGraphRunner,
|
||||
Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
render::{eventually::Eventually::Initialized, graph_runner::RenderGraphRunner, Renderer},
|
||||
tcs::system::System,
|
||||
};
|
||||
|
||||
/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame.
|
||||
pub struct GraphRunnerStage {
|
||||
graph: RenderGraph,
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct GraphRunnerSystem;
|
||||
|
||||
impl GraphRunnerStage {
|
||||
pub fn new(graph: RenderGraph) -> Self {
|
||||
Self { graph }
|
||||
impl System for GraphRunnerSystem {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
"graph_runner".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Stage for GraphRunnerStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
world,
|
||||
renderer:
|
||||
Renderer {
|
||||
device,
|
||||
queue,
|
||||
state,
|
||||
resources: state,
|
||||
render_graph,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
self.graph.update(state);
|
||||
render_graph.update(state);
|
||||
|
||||
if let Err(e) = RenderGraphRunner::run(&self.graph, device, queue, state) {
|
||||
if let Err(e) = RenderGraphRunner::run(render_graph, device, queue, state, world) {
|
||||
error!("Error running render graph:");
|
||||
{
|
||||
let mut src: &dyn std::error::Error = &e;
|
||||
8
maplibre/src/render/systems/mod.rs
Normal file
8
maplibre/src/render/systems/mod.rs
Normal file
@ -0,0 +1,8 @@
|
||||
//! Rendering specific systems
|
||||
|
||||
pub mod cleanup_system;
|
||||
pub mod graph_runner_system;
|
||||
pub mod resource_system;
|
||||
pub mod sort_phase_system;
|
||||
pub mod tile_view_pattern_system;
|
||||
pub mod upload_system;
|
||||
@ -1,25 +1,28 @@
|
||||
//! Prepares GPU-owned resources by initializing them if they are uninitialized or out-of-date.
|
||||
|
||||
use std::mem::size_of;
|
||||
use std::{borrow::Cow, mem};
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
resource::{BackingBufferDescriptor, BufferPool, Globals, RenderPipeline, Texture},
|
||||
eventually::Eventually,
|
||||
resource::{BackingBufferDescriptor, RenderPipeline, Texture, TilePipeline},
|
||||
shaders,
|
||||
shaders::{Shader, ShaderTileMetadata},
|
||||
tile_pipeline::TilePipeline,
|
||||
tile_view_pattern::{TileViewPattern, DEFAULT_TILE_VIEW_PATTERN_SIZE},
|
||||
Renderer,
|
||||
tile_view_pattern::{TileViewPattern, WgpuTileViewPattern, DEFAULT_TILE_VIEW_PATTERN_SIZE},
|
||||
MaskPipeline, Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
tcs::system::System,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ResourceStage;
|
||||
pub struct ResourceSystem;
|
||||
|
||||
impl System for ResourceSystem {
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
"resource_system".into()
|
||||
}
|
||||
|
||||
impl Stage for ResourceStage {
|
||||
#[tracing::instrument(name = "ResourceStage", skip_all)]
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
@ -27,12 +30,21 @@ impl Stage for ResourceStage {
|
||||
Renderer {
|
||||
settings,
|
||||
device,
|
||||
state,
|
||||
resources: state,
|
||||
..
|
||||
},
|
||||
world,
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let Some((
|
||||
tile_view_pattern,
|
||||
mask_pipeline
|
||||
)) = world.resources.query_mut::<(
|
||||
&mut Eventually<WgpuTileViewPattern>,
|
||||
&mut Eventually<MaskPipeline>,
|
||||
)>() else { return; };
|
||||
|
||||
let surface = &mut state.surface;
|
||||
|
||||
let size = surface.size();
|
||||
@ -52,6 +64,7 @@ impl Stage for ResourceStage {
|
||||
size.width(),
|
||||
size.height(),
|
||||
settings.msaa,
|
||||
wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
)
|
||||
},
|
||||
&(size.width(), size.height()),
|
||||
@ -67,6 +80,7 @@ impl Stage for ResourceStage {
|
||||
size.width(),
|
||||
size.height(),
|
||||
settings.msaa,
|
||||
wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
@ -75,14 +89,10 @@ impl Stage for ResourceStage {
|
||||
&(size.width(), size.height()),
|
||||
);
|
||||
|
||||
state
|
||||
.buffer_pool
|
||||
.initialize(|| BufferPool::from_device(device));
|
||||
|
||||
state.tile_view_pattern.initialize(|| {
|
||||
tile_view_pattern.initialize(|| {
|
||||
let tile_view_buffer_desc = wgpu::BufferDescriptor {
|
||||
label: Some("tile view buffer"),
|
||||
size: size_of::<ShaderTileMetadata>() as wgpu::BufferAddress
|
||||
size: mem::size_of::<ShaderTileMetadata>() as wgpu::BufferAddress
|
||||
* DEFAULT_TILE_VIEW_PATTERN_SIZE,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
@ -94,74 +104,28 @@ impl Stage for ResourceStage {
|
||||
))
|
||||
});
|
||||
|
||||
state.tile_pipeline.initialize(|| {
|
||||
let tile_shader = shaders::TileShader {
|
||||
format: surface.surface_format(),
|
||||
};
|
||||
|
||||
let pipeline = TilePipeline::new(
|
||||
*settings,
|
||||
tile_shader.describe_vertex(),
|
||||
tile_shader.describe_fragment(),
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
.describe_render_pipeline()
|
||||
.initialize(device);
|
||||
|
||||
state
|
||||
.globals_bind_group
|
||||
.initialize(|| Globals::from_device(device, &pipeline.get_bind_group_layout(0)));
|
||||
|
||||
pipeline
|
||||
});
|
||||
|
||||
state.mask_pipeline.initialize(|| {
|
||||
mask_pipeline.initialize(|| {
|
||||
let mask_shader = shaders::TileMaskShader {
|
||||
format: surface.surface_format(),
|
||||
draw_colors: false,
|
||||
debug_lines: false,
|
||||
};
|
||||
|
||||
TilePipeline::new(
|
||||
let pipeline = TilePipeline::new(
|
||||
"mask_pipeline".into(),
|
||||
*settings,
|
||||
mask_shader.describe_vertex(),
|
||||
mask_shader.describe_fragment(),
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
.describe_render_pipeline()
|
||||
.initialize(device)
|
||||
});
|
||||
|
||||
state.debug_pipeline.initialize(|| {
|
||||
let mask_shader = shaders::TileMaskShader {
|
||||
format: surface.surface_format(),
|
||||
draw_colors: true,
|
||||
debug_lines: true,
|
||||
};
|
||||
|
||||
TilePipeline::new(
|
||||
*settings,
|
||||
mask_shader.describe_vertex(),
|
||||
mask_shader.describe_fragment(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.describe_render_pipeline()
|
||||
.initialize(device)
|
||||
.initialize(device);
|
||||
MaskPipeline(pipeline)
|
||||
});
|
||||
}
|
||||
}
|
||||
14
maplibre/src/render/systems/sort_phase_system.rs
Normal file
14
maplibre/src/render/systems/sort_phase_system.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::render_phase::{LayerItem, RenderPhase},
|
||||
};
|
||||
|
||||
/// This system sorts all [`RenderPhases`](RenderPhase) for the [`PhaseItem`] type.
|
||||
pub fn sort_phase_system(MapContext { world, .. }: &mut MapContext) {
|
||||
// We are only sorting layers and not masks
|
||||
world
|
||||
.resources
|
||||
.get_mut::<RenderPhase<LayerItem>>()
|
||||
.unwrap()
|
||||
.sort();
|
||||
}
|
||||
38
maplibre/src/render/systems/tile_view_pattern_system.rs
Normal file
38
maplibre/src/render/systems/tile_view_pattern_system.rs
Normal file
@ -0,0 +1,38 @@
|
||||
//! Extracts data from the current state.
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
eventually::{Eventually, Eventually::Initialized},
|
||||
tile_view_pattern::{ViewTileSources, WgpuTileViewPattern},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn tile_view_pattern_system(
|
||||
MapContext {
|
||||
view_state, world, ..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let Some((
|
||||
Initialized(tile_view_pattern),
|
||||
view_tile_sources,
|
||||
)) = world.resources.query::<(
|
||||
&Eventually<WgpuTileViewPattern>,
|
||||
&ViewTileSources
|
||||
)>() else { return; };
|
||||
let view_region = view_state.create_view_region();
|
||||
|
||||
if let Some(view_region) = &view_region {
|
||||
let zoom = view_state.zoom();
|
||||
|
||||
let view_tiles =
|
||||
tile_view_pattern.generate_pattern(view_region, view_tile_sources, zoom, world);
|
||||
|
||||
// TODO: Can we &mut borrow initially somehow instead of here?
|
||||
let Some(Initialized(tile_view_pattern)) = world
|
||||
.resources
|
||||
.query_mut::<&mut Eventually<WgpuTileViewPattern>>() else { return; };
|
||||
|
||||
tile_view_pattern.update_pattern(view_tiles);
|
||||
}
|
||||
}
|
||||
28
maplibre/src/render/systems/upload_system.rs
Normal file
28
maplibre/src/render/systems/upload_system.rs
Normal file
@ -0,0 +1,28 @@
|
||||
//! Uploads data to the GPU which is needed for rendering.
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
eventually::{Eventually, Eventually::Initialized},
|
||||
tile_view_pattern::WgpuTileViewPattern,
|
||||
Renderer,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn upload_system(
|
||||
MapContext {
|
||||
world,
|
||||
style,
|
||||
view_state,
|
||||
renderer: Renderer { device, queue, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let Some(
|
||||
Initialized(tile_view_pattern)
|
||||
) = world.resources.query_mut::<
|
||||
&mut Eventually<WgpuTileViewPattern>
|
||||
>() else { return; };
|
||||
|
||||
let view_proj = view_state.view_projection();
|
||||
tile_view_pattern.upload_pattern(queue, &view_proj);
|
||||
}
|
||||
@ -1,238 +0,0 @@
|
||||
//! Utility for generating a tile pattern which can be used for masking.
|
||||
|
||||
use std::{marker::PhantomData, mem::size_of, ops::Range};
|
||||
|
||||
use cgmath::Matrix4;
|
||||
|
||||
use crate::{
|
||||
coords::{ViewRegion, WorldTileCoords, Zoom},
|
||||
render::{
|
||||
camera::ViewProjection,
|
||||
resource::{BackingBufferDescriptor, BufferPool, Queue},
|
||||
shaders::{ShaderFeatureStyle, ShaderLayerMetadata, ShaderTileMetadata},
|
||||
ShaderVertex,
|
||||
},
|
||||
tessellation::IndexDataType,
|
||||
};
|
||||
|
||||
pub const DEFAULT_TILE_VIEW_PATTERN_SIZE: wgpu::BufferAddress = 32 * 4;
|
||||
pub const CHILDREN_SEARCH_DEPTH: usize = 4;
|
||||
|
||||
/// Defines the exact location where a specific tile on the map is rendered. It defines the shape
|
||||
/// of the tile with its location for the current zoom factor.
|
||||
#[derive(Clone)]
|
||||
pub struct TileShape {
|
||||
coords: WorldTileCoords,
|
||||
|
||||
// TODO: optimization, `zoom_factor` and `transform` are no longer required if `buffer_range` is Some()
|
||||
zoom_factor: f64,
|
||||
transform: Matrix4<f64>,
|
||||
|
||||
buffer_range: Option<Range<wgpu::BufferAddress>>,
|
||||
}
|
||||
|
||||
impl TileShape {
|
||||
fn new(coords: WorldTileCoords, zoom: Zoom) -> Self {
|
||||
Self {
|
||||
coords,
|
||||
zoom_factor: zoom.scale_to_tile(&coords),
|
||||
transform: coords.transform_for_zoom(zoom),
|
||||
buffer_range: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_buffer_range(&mut self, index: u64) {
|
||||
const STRIDE: u64 = size_of::<ShaderTileMetadata>() as u64;
|
||||
self.buffer_range = Some(index * STRIDE..(index + 1) * STRIDE);
|
||||
}
|
||||
|
||||
pub fn buffer_range(&self) -> Range<wgpu::BufferAddress> {
|
||||
self.buffer_range.as_ref().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn coords(&self) -> WorldTileCoords {
|
||||
self.coords
|
||||
}
|
||||
}
|
||||
|
||||
/// This defines the source tile shaped from which the content for the `target` is taken.
|
||||
/// For example if the target is `(0, 0, 1)` (of [`ViewTile`]) , we might use
|
||||
/// `SourceShapes::Parent((0, 0, 0))` as source.
|
||||
/// Similarly if we have the target `(0, 0, 0)` we might use
|
||||
/// `SourceShapes::Children((0, 0, 1), (0, 1, 1), (1, 0, 1), (1, 1, 1))` as sources.
|
||||
#[derive(Clone)]
|
||||
pub enum SourceShapes {
|
||||
/// Parent tile is the source. We construct the `target` from parts of a parent.
|
||||
Parent(TileShape),
|
||||
/// Children are the source. We construct the `target` from multiple children.
|
||||
Children(Vec<TileShape>),
|
||||
/// Source and target are equal, so no need to differentiate. We render the `source` shape
|
||||
/// exactly at the `target`.
|
||||
SourceEqTarget(TileShape),
|
||||
/// No data available so nothing to render
|
||||
None,
|
||||
}
|
||||
|
||||
/// Defines the `target` tile and its `source` from which data tile data comes.
|
||||
#[derive(Clone)]
|
||||
pub struct ViewTile {
|
||||
target: WorldTileCoords,
|
||||
source: SourceShapes,
|
||||
}
|
||||
|
||||
impl ViewTile {
|
||||
pub fn coords(&self) -> WorldTileCoords {
|
||||
self.target
|
||||
}
|
||||
|
||||
pub fn render<F>(&self, mut callback: F)
|
||||
where
|
||||
F: FnMut(&TileShape),
|
||||
{
|
||||
match &self.source {
|
||||
SourceShapes::Parent(source_shape) => callback(source_shape),
|
||||
SourceShapes::Children(source_shapes) => {
|
||||
for shape in source_shapes {
|
||||
callback(shape)
|
||||
}
|
||||
}
|
||||
SourceShapes::SourceEqTarget(source_shape) => callback(source_shape),
|
||||
SourceShapes::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BackingBuffer<B> {
|
||||
/// The internal structure which is used for storage
|
||||
inner: B,
|
||||
/// The size of the `inner` buffer
|
||||
inner_size: wgpu::BufferAddress,
|
||||
}
|
||||
|
||||
impl<B> BackingBuffer<B> {
|
||||
fn new(inner: B, inner_size: wgpu::BufferAddress) -> Self {
|
||||
Self { inner, inner_size }
|
||||
}
|
||||
}
|
||||
|
||||
/// The tile mask pattern assigns each tile a value which can be used for stencil testing.
|
||||
pub struct TileViewPattern<Q, B> {
|
||||
in_view: Vec<ViewTile>,
|
||||
buffer: BackingBuffer<B>,
|
||||
phantom_q: PhantomData<Q>,
|
||||
}
|
||||
|
||||
impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
|
||||
pub fn new(buffer: BackingBufferDescriptor<B>) -> Self {
|
||||
Self {
|
||||
in_view: Vec::with_capacity(64),
|
||||
buffer: BackingBuffer::new(buffer.buffer, buffer.inner_size),
|
||||
phantom_q: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn update_pattern(
|
||||
&mut self,
|
||||
view_region: &ViewRegion,
|
||||
buffer_pool: &BufferPool<
|
||||
wgpu::Queue,
|
||||
wgpu::Buffer,
|
||||
ShaderVertex,
|
||||
IndexDataType,
|
||||
ShaderLayerMetadata,
|
||||
ShaderFeatureStyle,
|
||||
>,
|
||||
zoom: Zoom,
|
||||
) {
|
||||
self.in_view.clear();
|
||||
|
||||
let pool_index = buffer_pool.index();
|
||||
|
||||
for coords in view_region.iter() {
|
||||
if coords.build_quad_key().is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let source_shapes = {
|
||||
if pool_index.has_tile(&coords) {
|
||||
SourceShapes::SourceEqTarget(TileShape::new(coords, zoom))
|
||||
} else if let Some(parent_coords) = pool_index.get_available_parent(&coords) {
|
||||
log::info!("Could not find data at {coords}. Falling back to {parent_coords}");
|
||||
|
||||
SourceShapes::Parent(TileShape::new(parent_coords, zoom))
|
||||
} else if let Some(children_coords) =
|
||||
pool_index.get_available_children(&coords, CHILDREN_SEARCH_DEPTH)
|
||||
{
|
||||
log::info!(
|
||||
"Could not find data at {coords}. Falling back children: {children_coords:?}"
|
||||
);
|
||||
|
||||
SourceShapes::Children(
|
||||
children_coords
|
||||
.iter()
|
||||
.map(|child_coord| TileShape::new(*child_coord, zoom))
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
SourceShapes::None
|
||||
}
|
||||
};
|
||||
|
||||
self.in_view.push(ViewTile {
|
||||
target: coords,
|
||||
source: source_shapes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &ViewTile> + '_ {
|
||||
self.in_view.iter()
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> &B {
|
||||
&self.buffer.inner
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn upload_pattern(&mut self, queue: &Q, view_proj: &ViewProjection) {
|
||||
let mut buffer = Vec::with_capacity(self.in_view.len());
|
||||
|
||||
let mut add_to_buffer = |shape: &mut TileShape| {
|
||||
shape.set_buffer_range(buffer.len() as u64);
|
||||
buffer.push(ShaderTileMetadata {
|
||||
// We are casting here from 64bit to 32bit, because 32bit is more performant and is
|
||||
// better supported.
|
||||
transform: view_proj
|
||||
.to_model_view_projection(shape.transform)
|
||||
.downcast()
|
||||
.into(),
|
||||
zoom_factor: shape.zoom_factor as f32,
|
||||
});
|
||||
};
|
||||
|
||||
for view_tile in &mut self.in_view {
|
||||
match &mut view_tile.source {
|
||||
SourceShapes::Parent(source_shape) => {
|
||||
add_to_buffer(source_shape);
|
||||
}
|
||||
SourceShapes::Children(source_shapes) => {
|
||||
for source_shape in source_shapes {
|
||||
add_to_buffer(source_shape);
|
||||
}
|
||||
}
|
||||
SourceShapes::SourceEqTarget(source_shape) => add_to_buffer(source_shape),
|
||||
SourceShapes::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
let raw_buffer = bytemuck::cast_slice(buffer.as_slice());
|
||||
if raw_buffer.len() as wgpu::BufferAddress > self.buffer.inner_size {
|
||||
/* TODO: We need to avoid this case by either choosing a proper size
|
||||
TODO: (DEFAULT_TILE_VIEW_SIZE), or resizing the buffer */
|
||||
panic!("Buffer is too small to store the tile pattern!");
|
||||
}
|
||||
queue.write_buffer(&self.buffer.inner, 0, raw_buffer);
|
||||
}
|
||||
}
|
||||
230
maplibre/src/render/tile_view_pattern/mod.rs
Normal file
230
maplibre/src/render/tile_view_pattern/mod.rs
Normal file
@ -0,0 +1,230 @@
|
||||
//! Utility for generating a tile pattern which can be used for masking.
|
||||
|
||||
mod pattern;
|
||||
|
||||
use std::{marker::PhantomData, mem::size_of, ops::Range};
|
||||
|
||||
use cgmath::Matrix4;
|
||||
pub use pattern::{TileViewPattern, DEFAULT_TILE_VIEW_PATTERN_SIZE};
|
||||
|
||||
use crate::{
|
||||
coords::{WorldTileCoords, Zoom},
|
||||
render::shaders::ShaderTileMetadata,
|
||||
tcs::{resources::ResourceQuery, world::World},
|
||||
};
|
||||
|
||||
pub type WgpuTileViewPattern = TileViewPattern<wgpu::Queue, wgpu::Buffer>;
|
||||
|
||||
/// This defines the source tile shaped from which the content for the `target` is taken.
|
||||
/// For example if the target is `(0, 0, 1)` (of [`ViewTile`]) , we might use
|
||||
/// `SourceShapes::Parent((0, 0, 0))` as source.
|
||||
/// Similarly if we have the target `(0, 0, 0)` we might use
|
||||
/// `SourceShapes::Children((0, 0, 1), (0, 1, 1), (1, 0, 1), (1, 1, 1))` as sources.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SourceShapes {
|
||||
/// Parent tile is the source. We construct the `target` from parts of a parent.
|
||||
Parent(TileShape),
|
||||
/// Children are the source. We construct the `target` from multiple children.
|
||||
Children(Vec<TileShape>),
|
||||
/// Source and target are equal, so no need to differentiate. We render the `source` shape
|
||||
/// exactly at the `target`.
|
||||
SourceEqTarget(TileShape),
|
||||
/// No data available so nothing to render
|
||||
None,
|
||||
}
|
||||
|
||||
/// Defines the `target` tile and its `source` from which data tile data comes.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ViewTile {
|
||||
target: WorldTileCoords,
|
||||
source: SourceShapes,
|
||||
}
|
||||
|
||||
impl ViewTile {
|
||||
pub fn coords(&self) -> WorldTileCoords {
|
||||
self.target
|
||||
}
|
||||
|
||||
pub fn render<F>(&self, mut callback: F)
|
||||
where
|
||||
F: FnMut(&TileShape),
|
||||
{
|
||||
match &self.source {
|
||||
SourceShapes::Parent(source_shape) => callback(source_shape),
|
||||
SourceShapes::Children(source_shapes) => {
|
||||
for shape in source_shapes {
|
||||
callback(shape)
|
||||
}
|
||||
}
|
||||
SourceShapes::SourceEqTarget(source_shape) => callback(source_shape),
|
||||
SourceShapes::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the exact location where a specific tile on the map is rendered. It defines the shape
|
||||
/// of the tile with its location for the current zoom factor.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TileShape {
|
||||
coords: WorldTileCoords,
|
||||
|
||||
// TODO: optimization, `zoom_factor` and `transform` are no longer required if `buffer_range` is Some()
|
||||
zoom_factor: f64,
|
||||
transform: Matrix4<f64>,
|
||||
|
||||
buffer_range: Option<Range<wgpu::BufferAddress>>,
|
||||
}
|
||||
|
||||
impl TileShape {
|
||||
fn new(coords: WorldTileCoords, zoom: Zoom) -> Self {
|
||||
Self {
|
||||
coords,
|
||||
zoom_factor: zoom.scale_to_tile(&coords),
|
||||
transform: coords.transform_for_zoom(zoom),
|
||||
buffer_range: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_buffer_range(&mut self, index: u64) {
|
||||
const STRIDE: u64 = size_of::<ShaderTileMetadata>() as u64;
|
||||
self.buffer_range = Some(index * STRIDE..(index + 1) * STRIDE);
|
||||
}
|
||||
|
||||
pub fn buffer_range(&self) -> Option<Range<wgpu::BufferAddress>> {
|
||||
self.buffer_range.clone()
|
||||
}
|
||||
|
||||
pub fn coords(&self) -> WorldTileCoords {
|
||||
self.coords
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasTile {
|
||||
fn has_tile(&self, coords: WorldTileCoords, world: &World) -> bool;
|
||||
|
||||
fn get_available_parent(
|
||||
&self,
|
||||
coords: WorldTileCoords,
|
||||
world: &World,
|
||||
) -> Option<WorldTileCoords> {
|
||||
let mut current = coords;
|
||||
loop {
|
||||
if self.has_tile(current, world) {
|
||||
return Some(current);
|
||||
} else if let Some(parent) = current.get_parent() {
|
||||
current = parent
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_available_children(
|
||||
&self,
|
||||
coords: WorldTileCoords,
|
||||
world: &World,
|
||||
search_depth: usize,
|
||||
) -> Option<Vec<WorldTileCoords>> {
|
||||
let mut children = coords.get_children().to_vec();
|
||||
|
||||
let mut output = Vec::new();
|
||||
|
||||
for _ in 0..search_depth {
|
||||
let mut new_children = Vec::with_capacity(children.len() * 4);
|
||||
|
||||
for child in children {
|
||||
if self.has_tile(child, world) {
|
||||
output.push(child);
|
||||
} else {
|
||||
new_children.extend(child.get_children())
|
||||
}
|
||||
}
|
||||
|
||||
children = new_children;
|
||||
}
|
||||
|
||||
Some(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: HasTile> HasTile for &A {
|
||||
fn has_tile(&self, coords: WorldTileCoords, world: &World) -> bool {
|
||||
A::has_tile(*self, coords, world)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: HasTile> HasTile for (A,) {
|
||||
fn has_tile(&self, coords: WorldTileCoords, world: &World) -> bool {
|
||||
self.0.has_tile(coords, world)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: HasTile, B: HasTile> HasTile for (A, B) {
|
||||
fn has_tile(&self, coords: WorldTileCoords, world: &World) -> bool {
|
||||
self.0.has_tile(coords, world) && self.1.has_tile(coords, world)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: HasTile, B: HasTile, C: HasTile> HasTile for (A, B, C) {
|
||||
fn has_tile(&self, coords: WorldTileCoords, world: &World) -> bool {
|
||||
self.0.has_tile(coords, world)
|
||||
&& self.1.has_tile(coords, world)
|
||||
&& self.2.has_tile(coords, world)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueryHasTile<Q> {
|
||||
phantom_q: PhantomData<Q>,
|
||||
}
|
||||
|
||||
impl<Q: ResourceQuery> Default for QueryHasTile<Q> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
phantom_q: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: ResourceQuery> HasTile for QueryHasTile<Q>
|
||||
where
|
||||
for<'a> Q::Item<'a>: HasTile,
|
||||
{
|
||||
fn has_tile(&self, coords: WorldTileCoords, world: &World) -> bool {
|
||||
let resources = world
|
||||
.resources
|
||||
.query::<Q>()
|
||||
.expect("resource not found for has_tile check");
|
||||
|
||||
resources.has_tile(coords, world)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ViewTileSources {
|
||||
items: Vec<Box<dyn HasTile>>,
|
||||
}
|
||||
|
||||
impl ViewTileSources {
|
||||
pub fn add<H: HasTile + 'static + Default>(&mut self) -> &mut Self {
|
||||
self.items.push(Box::<H>::default());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_resource_query<Q: ResourceQuery + 'static>(&mut self) -> &mut Self
|
||||
where
|
||||
for<'a> Q::Item<'a>: HasTile,
|
||||
{
|
||||
self.items.push(Box::new(QueryHasTile::<Q>::default()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.items.clear()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasTile for ViewTileSources {
|
||||
fn has_tile(&self, coords: WorldTileCoords, world: &World) -> bool {
|
||||
self.items.iter().all(|item| item.has_tile(coords, world))
|
||||
}
|
||||
}
|
||||
154
maplibre/src/render/tile_view_pattern/pattern.rs
Normal file
154
maplibre/src/render/tile_view_pattern/pattern.rs
Normal file
@ -0,0 +1,154 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{
|
||||
coords::{ViewRegion, Zoom},
|
||||
render::{
|
||||
camera::ViewProjection,
|
||||
resource::{BackingBufferDescriptor, Queue},
|
||||
shaders::ShaderTileMetadata,
|
||||
tile_view_pattern::{HasTile, SourceShapes, TileShape, ViewTile},
|
||||
},
|
||||
tcs::world::World,
|
||||
};
|
||||
|
||||
pub const DEFAULT_TILE_VIEW_PATTERN_SIZE: wgpu::BufferAddress = 32 * 4;
|
||||
pub const CHILDREN_SEARCH_DEPTH: usize = 4;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BackingBuffer<B> {
|
||||
/// The internal structure which is used for storage
|
||||
inner: B,
|
||||
/// The size of the `inner` buffer
|
||||
inner_size: wgpu::BufferAddress,
|
||||
}
|
||||
|
||||
impl<B> BackingBuffer<B> {
|
||||
fn new(inner: B, inner_size: wgpu::BufferAddress) -> Self {
|
||||
Self { inner, inner_size }
|
||||
}
|
||||
}
|
||||
|
||||
/// The tile mask pattern assigns each tile a value which can be used for stencil testing.
|
||||
pub struct TileViewPattern<Q, B> {
|
||||
view_tiles: Vec<ViewTile>,
|
||||
view_tiles_buffer: BackingBuffer<B>,
|
||||
phantom_q: PhantomData<Q>,
|
||||
}
|
||||
|
||||
impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
|
||||
pub fn new(view_tiles_buffer: BackingBufferDescriptor<B>) -> Self {
|
||||
Self {
|
||||
view_tiles: Vec::with_capacity(64),
|
||||
view_tiles_buffer: BackingBuffer::new(
|
||||
view_tiles_buffer.buffer,
|
||||
view_tiles_buffer.inner_size,
|
||||
),
|
||||
phantom_q: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[must_use]
|
||||
pub fn generate_pattern<T: HasTile>(
|
||||
&self,
|
||||
view_region: &ViewRegion,
|
||||
container: &T,
|
||||
zoom: Zoom,
|
||||
world: &World,
|
||||
) -> Vec<ViewTile> {
|
||||
let mut view_tiles = Vec::with_capacity(self.view_tiles.len());
|
||||
|
||||
for coords in view_region.iter() {
|
||||
if coords.build_quad_key().is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let source_shapes = {
|
||||
if container.has_tile(coords, world) {
|
||||
SourceShapes::SourceEqTarget(TileShape::new(coords, zoom))
|
||||
} else if let Some(parent_coords) = container.get_available_parent(coords, world) {
|
||||
log::debug!("Could not find data at {coords}. Falling back to {parent_coords}");
|
||||
|
||||
SourceShapes::Parent(TileShape::new(parent_coords, zoom))
|
||||
} else if let Some(children_coords) =
|
||||
container.get_available_children(coords, world, CHILDREN_SEARCH_DEPTH)
|
||||
{
|
||||
log::debug!(
|
||||
"Could not find data at {coords}. Falling back children: {children_coords:?}"
|
||||
);
|
||||
|
||||
SourceShapes::Children(
|
||||
children_coords
|
||||
.iter()
|
||||
.map(|child_coord| TileShape::new(*child_coord, zoom))
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
SourceShapes::None
|
||||
}
|
||||
};
|
||||
|
||||
view_tiles.push(ViewTile {
|
||||
target: coords,
|
||||
source: source_shapes,
|
||||
});
|
||||
}
|
||||
|
||||
view_tiles
|
||||
}
|
||||
|
||||
pub fn update_pattern(&mut self, mut view_tiles: Vec<ViewTile>) {
|
||||
self.view_tiles.clear();
|
||||
self.view_tiles.append(&mut view_tiles)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &ViewTile> + '_ {
|
||||
self.view_tiles.iter()
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> &B {
|
||||
&self.view_tiles_buffer.inner
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn upload_pattern(&mut self, queue: &Q, view_proj: &ViewProjection) {
|
||||
let mut buffer = Vec::with_capacity(self.view_tiles.len());
|
||||
|
||||
let mut add_to_buffer = |shape: &mut TileShape| {
|
||||
shape.set_buffer_range(buffer.len() as u64);
|
||||
// TODO: Name `ShaderTileMetadata` is unfortunate here, because for raster rendering it actually is a layer
|
||||
buffer.push(ShaderTileMetadata {
|
||||
// We are casting here from 64bit to 32bit, because 32bit is more performant and is
|
||||
// better supported.
|
||||
transform: view_proj
|
||||
.to_model_view_projection(shape.transform)
|
||||
.downcast()
|
||||
.into(), // TODO: move this calculation to update() fn above
|
||||
zoom_factor: shape.zoom_factor as f32,
|
||||
});
|
||||
};
|
||||
|
||||
for view_tile in &mut self.view_tiles {
|
||||
match &mut view_tile.source {
|
||||
SourceShapes::Parent(source_shape) => {
|
||||
add_to_buffer(source_shape);
|
||||
}
|
||||
SourceShapes::Children(source_shapes) => {
|
||||
for source_shape in source_shapes {
|
||||
add_to_buffer(source_shape);
|
||||
}
|
||||
}
|
||||
SourceShapes::SourceEqTarget(source_shape) => add_to_buffer(source_shape),
|
||||
SourceShapes::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
let raw_buffer = bytemuck::cast_slice(buffer.as_slice());
|
||||
if raw_buffer.len() as wgpu::BufferAddress > self.view_tiles_buffer.inner_size {
|
||||
/* TODO: We need to avoid this case by either choosing a proper size
|
||||
TODO: (DEFAULT_TILE_VIEW_SIZE), or resizing the buffer */
|
||||
panic!("Buffer is too small to store the tile pattern!");
|
||||
}
|
||||
queue.write_buffer(&self.view_tiles_buffer.inner, 0, raw_buffer);
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,11 @@ use std::collections::HashMap;
|
||||
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
|
||||
use crate::{context::MapContext, define_label};
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
define_label,
|
||||
tcs::system::{stage::SystemStage, IntoSystemContainer},
|
||||
};
|
||||
|
||||
pub struct NopStage;
|
||||
|
||||
@ -18,7 +22,7 @@ macro_rules! multi_stage {
|
||||
}
|
||||
|
||||
impl Stage for $multi_stage {
|
||||
fn run(&mut self, context: &mut MapContext) {
|
||||
fn run(&mut self, context: &mut $crate::context::MapContext) {
|
||||
$(self.$stage.run(context);)*
|
||||
}
|
||||
}
|
||||
@ -102,6 +106,13 @@ impl Schedule {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn remove_stage(&mut self, label: impl StageLabel) -> &mut Self {
|
||||
let remove: Box<dyn StageLabel> = Box::new(label);
|
||||
self.stages.remove(&remove).expect("stage not found");
|
||||
self.stage_order.retain(|label| label != &remove);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds the given `stage` immediately after the `target` stage.
|
||||
///
|
||||
/// # Example
|
||||
@ -261,6 +272,38 @@ impl Schedule {
|
||||
.iter()
|
||||
.map(move |label| (&**label, &*self.stages[label]))
|
||||
}
|
||||
|
||||
/// Adds a system to the [`Stage`] identified by `stage_label`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use maplibre::context::MapContext;
|
||||
/// # use maplibre::tcs::system::stage::SystemStage;
|
||||
/// # use maplibre::schedule::{Schedule, NopStage};
|
||||
/// #
|
||||
/// # let mut schedule = Schedule::default();
|
||||
/// # schedule.add_stage("my_stage", SystemStage::default());
|
||||
/// # fn my_system(context: &mut MapContext) {}
|
||||
/// #
|
||||
/// schedule.add_system_to_stage("my_stage", my_system);
|
||||
/// ```
|
||||
pub fn add_system_to_stage(
|
||||
&mut self,
|
||||
stage_label: impl StageLabel,
|
||||
system: impl IntoSystemContainer,
|
||||
) -> &mut Self {
|
||||
let stage = self
|
||||
.get_stage_mut::<SystemStage>(&stage_label)
|
||||
.unwrap_or_else(move || {
|
||||
panic!(
|
||||
"Stage '{:?}' does not exist or is not a SystemStage",
|
||||
stage_label
|
||||
)
|
||||
});
|
||||
stage.add_system(system);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Stage for Schedule {
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
//! [Stages](Stage) for requesting and preparing data
|
||||
|
||||
use std::{marker::PhantomData, rc::Rc};
|
||||
|
||||
use geozero::mvt::tile;
|
||||
use request_stage::RequestStage;
|
||||
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
environment::Environment,
|
||||
io::{
|
||||
apc::{Context, Message},
|
||||
geometry_index::{IndexedGeometry, TileIndex},
|
||||
pipeline::{PipelineError, PipelineProcessor},
|
||||
source_client::HttpClient,
|
||||
transferables::{
|
||||
LayerIndexed, LayerTessellated, LayerUnavailable, TileTessellated, Transferables,
|
||||
},
|
||||
},
|
||||
kernel::Kernel,
|
||||
render::ShaderVertex,
|
||||
schedule::Schedule,
|
||||
stages::populate_tile_store_stage::PopulateTileStore,
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
};
|
||||
|
||||
mod populate_tile_store_stage;
|
||||
mod request_stage;
|
||||
|
||||
/// Register stages required for requesting and preparing new tiles.
|
||||
pub fn register_stages<E: Environment>(schedule: &mut Schedule, kernel: Rc<Kernel<E>>) {
|
||||
schedule.add_stage("request", RequestStage::<E>::new(kernel.clone()));
|
||||
schedule.add_stage("populate_tile_store", PopulateTileStore::<E>::new(kernel));
|
||||
}
|
||||
|
||||
pub struct HeadedPipelineProcessor<T: Transferables, HC: HttpClient, C: Context<T, HC>> {
|
||||
context: C,
|
||||
phantom_t: PhantomData<T>,
|
||||
phantom_hc: PhantomData<HC>,
|
||||
}
|
||||
|
||||
impl<T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
|
||||
for HeadedPipelineProcessor<T, HC, C>
|
||||
{
|
||||
fn tile_finished(&mut self, coords: &WorldTileCoords) -> Result<(), PipelineError> {
|
||||
self.context
|
||||
.send(Message::TileTessellated(T::TileTessellated::build_from(
|
||||
*coords,
|
||||
)))
|
||||
.map_err(|e| PipelineError::Processing(Box::new(e)))
|
||||
}
|
||||
|
||||
fn layer_unavailable(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
layer_name: &str,
|
||||
) -> Result<(), PipelineError> {
|
||||
self.context
|
||||
.send(Message::LayerUnavailable(T::LayerUnavailable::build_from(
|
||||
*coords,
|
||||
layer_name.to_owned(),
|
||||
)))
|
||||
.map_err(|e| PipelineError::Processing(Box::new(e)))
|
||||
}
|
||||
|
||||
fn layer_tesselation_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: tile::Layer,
|
||||
) -> Result<(), PipelineError> {
|
||||
self.context
|
||||
.send(Message::LayerTessellated(T::LayerTessellated::build_from(
|
||||
*coords,
|
||||
buffer,
|
||||
feature_indices,
|
||||
layer_data,
|
||||
)))
|
||||
.map_err(|e| PipelineError::Processing(Box::new(e)))
|
||||
}
|
||||
|
||||
fn layer_indexing_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
geometries: Vec<IndexedGeometry<f64>>,
|
||||
) -> Result<(), PipelineError> {
|
||||
self.context
|
||||
.send(Message::LayerIndexed(T::LayerIndexed::build_from(
|
||||
*coords,
|
||||
TileIndex::Linear { list: geometries },
|
||||
)))
|
||||
.map_err(|e| PipelineError::Processing(Box::new(e)))
|
||||
}
|
||||
}
|
||||
@ -1,97 +0,0 @@
|
||||
//! Receives data from async threads and populates the [`crate::io::tile_repository::TileRepository`].
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
environment::Environment,
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, Message},
|
||||
tile_repository::StoredLayer,
|
||||
transferables::{LayerIndexed, LayerTessellated, LayerUnavailable, TileTessellated},
|
||||
},
|
||||
kernel::Kernel,
|
||||
schedule::Stage,
|
||||
world::World,
|
||||
};
|
||||
|
||||
pub struct PopulateTileStore<E: Environment> {
|
||||
kernel: Rc<Kernel<E>>,
|
||||
}
|
||||
|
||||
impl<E: Environment> PopulateTileStore<E> {
|
||||
pub fn new(kernel: Rc<Kernel<E>>) -> Self {
|
||||
Self { kernel }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Environment> Stage for PopulateTileStore<E> {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
world:
|
||||
World {
|
||||
tile_repository,
|
||||
geometry_index,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
// TODO: (optimize) Using while instead of if means that we are processing all that is
|
||||
// available this might cause frame drops.
|
||||
while let Some(result) = self.kernel.apc().receive() {
|
||||
match result {
|
||||
// TODO: deduplicate
|
||||
Message::TileTessellated(message) => {
|
||||
let coords = message.coords();
|
||||
tracing::event!(tracing::Level::ERROR, %coords, "tile request done: {}", &coords);
|
||||
|
||||
tracing::trace!("Tile at {} finished loading", coords);
|
||||
log::warn!("Tile at {} finished loading", coords);
|
||||
|
||||
tile_repository.mark_tile_succeeded(&coords).unwrap(); // TODO: unwrap
|
||||
}
|
||||
Message::LayerUnavailable(message) => {
|
||||
let layer: StoredLayer = message.to_stored_layer();
|
||||
|
||||
tracing::debug!(
|
||||
"Layer {} at {} reached main thread",
|
||||
layer.layer_name(),
|
||||
layer.get_coords()
|
||||
);
|
||||
|
||||
tile_repository.put_layer(layer);
|
||||
}
|
||||
Message::LayerTessellated(message) => {
|
||||
// TODO: Is it fine to ignore layers without any vertices?
|
||||
if message.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let layer: StoredLayer = message.to_stored_layer();
|
||||
|
||||
tracing::debug!(
|
||||
"Layer {} at {} reached main thread",
|
||||
layer.layer_name(),
|
||||
layer.get_coords()
|
||||
);
|
||||
log::warn!(
|
||||
"Layer {} at {} reached main thread",
|
||||
layer.layer_name(),
|
||||
layer.get_coords()
|
||||
);
|
||||
|
||||
tile_repository.put_layer(layer);
|
||||
}
|
||||
Message::LayerIndexed(message) => {
|
||||
let coords = message.coords();
|
||||
|
||||
log::warn!("Layer index at {} reached main thread", coords);
|
||||
|
||||
geometry_index.index_tile(&coords, message.to_tile_index());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user