mirror of
https://github.com/maplibre/maplibre-rs.git
synced 2025-12-08 19:05:57 +00:00
Refactor headless module, introduce world
This commit is contained in:
parent
f8b38dc8f2
commit
4879d9c0b4
@ -1,95 +1,8 @@
|
||||
use std::ops::Div;
|
||||
|
||||
use cgmath::Angle;
|
||||
|
||||
use crate::render::Renderer;
|
||||
use crate::style::Style;
|
||||
use crate::window::WindowSize;
|
||||
use crate::{
|
||||
coords::{LatLon, ViewRegion, WorldCoords, Zoom, ZoomLevel, TILE_SIZE},
|
||||
io::tile_repository::TileRepository,
|
||||
render::camera::{Camera, Perspective, ViewProjection},
|
||||
util::ChangeObserver,
|
||||
};
|
||||
|
||||
/// Stores the camera configuration.
|
||||
pub struct ViewState {
|
||||
pub zoom: ChangeObserver<Zoom>,
|
||||
pub camera: ChangeObserver<Camera>,
|
||||
pub perspective: Perspective,
|
||||
}
|
||||
|
||||
impl ViewState {
|
||||
pub fn new<P: Into<cgmath::Rad<f64>>>(
|
||||
window_size: &WindowSize,
|
||||
position: WorldCoords,
|
||||
zoom: Zoom,
|
||||
pitch: f64,
|
||||
fovy: P,
|
||||
) -> Self {
|
||||
let tile_center = TILE_SIZE / 2.0;
|
||||
let fovy = fovy.into();
|
||||
let height = tile_center / (fovy / 2.0).tan();
|
||||
|
||||
let camera = Camera::new(
|
||||
(position.x, position.y, height),
|
||||
cgmath::Deg(-90.0),
|
||||
cgmath::Deg(pitch),
|
||||
window_size.width(),
|
||||
window_size.height(),
|
||||
);
|
||||
|
||||
let perspective = Perspective::new(
|
||||
window_size.width(),
|
||||
window_size.height(),
|
||||
cgmath::Deg(110.0),
|
||||
// in tile.vertex.wgsl we are setting each layer's final `z` in ndc space to `z_index`.
|
||||
// This means that regardless of the `znear` value all layers will be rendered as part
|
||||
// of the near plane.
|
||||
// These values have been selected experimentally:
|
||||
// https://www.sjbaker.org/steve/omniv/love_your_z_buffer.html
|
||||
1024.0,
|
||||
2048.0,
|
||||
);
|
||||
|
||||
Self {
|
||||
zoom: ChangeObserver::new(zoom),
|
||||
camera: ChangeObserver::new(camera),
|
||||
perspective,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_view_region(&self) -> Option<ViewRegion> {
|
||||
self.camera
|
||||
.view_region_bounding_box(&self.view_projection().invert())
|
||||
.map(|bounding_box| {
|
||||
ViewRegion::new(bounding_box, 0, 32, *self.zoom, self.visible_level())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn view_projection(&self) -> ViewProjection {
|
||||
self.camera.calc_view_proj(&self.perspective)
|
||||
}
|
||||
|
||||
pub fn visible_level(&self) -> ZoomLevel {
|
||||
self.zoom.level()
|
||||
}
|
||||
|
||||
pub fn zoom(&self) -> Zoom {
|
||||
*self.zoom
|
||||
}
|
||||
|
||||
pub fn update_zoom(&mut self, new_zoom: Zoom) {
|
||||
*self.zoom = new_zoom;
|
||||
log::info!("zoom: {}", new_zoom);
|
||||
}
|
||||
}
|
||||
use crate::{render::Renderer, style::Style, world::World};
|
||||
|
||||
/// Stores the context of the map.
|
||||
pub struct MapContext {
|
||||
pub view_state: ViewState,
|
||||
pub style: Style,
|
||||
|
||||
pub tile_repository: TileRepository,
|
||||
pub world: World,
|
||||
pub renderer: Renderer,
|
||||
}
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
use crate::io::scheduler::Scheduler;
|
||||
use crate::io::source_client::HttpClient;
|
||||
use crate::io::{
|
||||
apc::AsyncProcedureCall,
|
||||
transferables::{
|
||||
DefaultTessellatedLayer, DefaultTileTessellated, DefaultUnavailableLayer, Transferables,
|
||||
use crate::{
|
||||
io::{
|
||||
apc::AsyncProcedureCall,
|
||||
scheduler::Scheduler,
|
||||
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||
transferables::{
|
||||
DefaultTessellatedLayer, DefaultTileTessellated, DefaultUnavailableLayer, Transferables,
|
||||
},
|
||||
},
|
||||
window::MapWindowConfig,
|
||||
};
|
||||
use crate::window::MapWindowConfig;
|
||||
|
||||
pub trait Environment: 'static {
|
||||
type MapWindowConfig: MapWindowConfig;
|
||||
@ -19,10 +21,10 @@ pub trait Environment: 'static {
|
||||
}
|
||||
|
||||
pub struct Kernel<E: Environment> {
|
||||
map_window_config: E::MapWindowConfig,
|
||||
apc: E::AsyncProcedureCall,
|
||||
scheduler: E::Scheduler,
|
||||
http_client: E::HttpClient,
|
||||
pub map_window_config: E::MapWindowConfig,
|
||||
pub apc: E::AsyncProcedureCall,
|
||||
pub scheduler: E::Scheduler,
|
||||
pub source_client: SourceClient<E::HttpClient>,
|
||||
}
|
||||
|
||||
pub struct KernelBuilder<E: Environment> {
|
||||
@ -64,9 +66,9 @@ impl<E: Environment> KernelBuilder<E> {
|
||||
|
||||
pub fn build(self) -> Kernel<E> {
|
||||
Kernel {
|
||||
scheduler: self.scheduler.unwrap(), // TODO: Remove unwrap
|
||||
apc: self.apc.unwrap(), // TODO: Remove unwrap
|
||||
http_client: self.http_client.unwrap(), // TODO: Remove unwrap
|
||||
scheduler: self.scheduler.unwrap(), // TODO: Remove unwrap
|
||||
apc: self.apc.unwrap(), // TODO: Remove unwrap
|
||||
source_client: SourceClient::new(HttpSourceClient::new(self.http_client.unwrap())), // TODO: Remove unwrap
|
||||
map_window_config: self.map_window_config.unwrap(), // TODO: Remove unwrap
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,45 +4,17 @@ use std::{fmt, fmt::Formatter, sync::mpsc::SendError};
|
||||
|
||||
use lyon::tessellation::TessellationError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RenderError {
|
||||
Surface(wgpu::SurfaceError),
|
||||
}
|
||||
|
||||
impl fmt::Display for RenderError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RenderError::Surface(e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderError {
|
||||
pub fn should_exit(&self) -> bool {
|
||||
match self {
|
||||
RenderError::Surface(e) => match e {
|
||||
wgpu::SurfaceError::OutOfMemory => true,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::render::{error::RenderError, graph::RenderGraphError};
|
||||
|
||||
/// Enumeration of errors which can happen during the operation of the library.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Schedule,
|
||||
Scheduler,
|
||||
Network(String),
|
||||
Tesselation(TessellationError),
|
||||
Render(RenderError),
|
||||
}
|
||||
|
||||
impl From<wgpu::SurfaceError> for Error {
|
||||
fn from(e: wgpu::SurfaceError) -> Self {
|
||||
Error::Render(RenderError::Surface(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TessellationError> for Error {
|
||||
fn from(e: TessellationError) -> Self {
|
||||
Error::Tesselation(e)
|
||||
@ -51,6 +23,6 @@ impl From<TessellationError> for Error {
|
||||
|
||||
impl<T> From<SendError<T>> for Error {
|
||||
fn from(_e: SendError<T>) -> Self {
|
||||
Error::Schedule
|
||||
Error::Scheduler
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,355 +0,0 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::File,
|
||||
future::Future,
|
||||
io::Write,
|
||||
iter,
|
||||
marker::PhantomData,
|
||||
ops::{Deref, Range},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use tokio::{runtime::Handle, task};
|
||||
use wgpu::{BufferAsyncError, BufferSlice};
|
||||
|
||||
use crate::environment::Environment;
|
||||
use crate::io::scheduler::Scheduler;
|
||||
use crate::io::source_client::HttpClient;
|
||||
use crate::render::Renderer;
|
||||
use crate::style::Style;
|
||||
use crate::window::{MapWindow, MapWindowConfig, WindowSize};
|
||||
use crate::{
|
||||
context::{MapContext, ViewState},
|
||||
coords::{LatLon, ViewRegion, WorldCoords, WorldTileCoords, Zoom, TILE_SIZE},
|
||||
error::Error,
|
||||
headless::utils::HeadlessPipelineProcessor,
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, SchedulerAsyncProcedureCall},
|
||||
pipeline::{PipelineContext, Processable},
|
||||
source_client::HttpSourceClient,
|
||||
tile_pipelines::build_vector_tile_pipeline,
|
||||
tile_repository::{StoredLayer, TileRepository},
|
||||
transferables::{DefaultTransferables, Transferables},
|
||||
TileRequest,
|
||||
},
|
||||
render::{
|
||||
camera::ViewProjection,
|
||||
create_default_render_graph, draw_graph,
|
||||
eventually::Eventually,
|
||||
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
|
||||
register_default_render_stages,
|
||||
resource::{BufferDimensions, BufferedTextureHead, Head, IndexEntry, TrackedRenderPass},
|
||||
stages::RenderStageLabel,
|
||||
RenderState,
|
||||
},
|
||||
schedule::{Schedule, Stage},
|
||||
};
|
||||
|
||||
pub struct HeadlessMapWindowConfig {
|
||||
pub size: WindowSize,
|
||||
}
|
||||
|
||||
impl MapWindowConfig for HeadlessMapWindowConfig {
|
||||
type MapWindow = HeadlessMapWindow;
|
||||
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
Self::MapWindow { size: self.size }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HeadlessMapWindow {
|
||||
size: WindowSize,
|
||||
}
|
||||
|
||||
impl MapWindow for HeadlessMapWindow {
|
||||
fn size(&self) -> WindowSize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HeadlessEnvironment<
|
||||
S: Scheduler,
|
||||
HC: HttpClient,
|
||||
T: Transferables,
|
||||
APC: AsyncProcedureCall<T, HC>,
|
||||
> {
|
||||
phantom_s: PhantomData<S>,
|
||||
phantom_hc: PhantomData<HC>,
|
||||
phantom_t: PhantomData<T>,
|
||||
phantom_apc: PhantomData<APC>,
|
||||
}
|
||||
|
||||
impl<S: Scheduler, HC: HttpClient, T: Transferables, APC: AsyncProcedureCall<T, HC>> Environment
|
||||
for HeadlessEnvironment<S, HC, T, APC>
|
||||
{
|
||||
type MapWindowConfig = HeadlessMapWindowConfig;
|
||||
type AsyncProcedureCall = APC;
|
||||
type Scheduler = S;
|
||||
type HttpClient = HC;
|
||||
type Transferables = T;
|
||||
}
|
||||
|
||||
pub struct HeadlessMap<E: Environment> {
|
||||
pub map_schedule: HeadlessMapSchedule<E>,
|
||||
pub window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
|
||||
}
|
||||
|
||||
impl<E: Environment> HeadlessMap<E> {
|
||||
pub fn map_schedule_mut(&mut self) -> &mut HeadlessMapSchedule<E> {
|
||||
&mut self.map_schedule
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
|
||||
pub struct HeadlessMapSchedule<E: Environment> {
|
||||
map_window_config: E::MapWindowConfig,
|
||||
|
||||
pub map_context: MapContext,
|
||||
|
||||
schedule: Schedule,
|
||||
scheduler: E::Scheduler,
|
||||
http_client: E::HttpClient,
|
||||
}
|
||||
|
||||
impl<E: Environment> HeadlessMapSchedule<E> {
|
||||
pub fn new(
|
||||
map_window_config: E::MapWindowConfig,
|
||||
window_size: WindowSize,
|
||||
renderer: Renderer,
|
||||
scheduler: E::Scheduler,
|
||||
http_client: E::HttpClient,
|
||||
style: Style,
|
||||
) -> Self {
|
||||
let view_state = ViewState::new(
|
||||
&window_size,
|
||||
WorldCoords::from((TILE_SIZE / 2., TILE_SIZE / 2.)),
|
||||
Zoom::default(),
|
||||
0.0,
|
||||
cgmath::Deg(110.0),
|
||||
);
|
||||
let tile_repository = TileRepository::new();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
let mut graph = create_default_render_graph().unwrap(); // TODO: remove unwrap
|
||||
let draw_graph = graph.get_sub_graph_mut(draw_graph::NAME).unwrap(); // TODO: remove unwrap
|
||||
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
|
||||
|
||||
register_default_render_stages(graph, &mut schedule);
|
||||
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Cleanup,
|
||||
WriteSurfaceBufferStage::default(),
|
||||
);
|
||||
|
||||
Self {
|
||||
map_window_config,
|
||||
map_context: MapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_repository,
|
||||
renderer,
|
||||
},
|
||||
schedule,
|
||||
scheduler,
|
||||
http_client,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "update_and_redraw", skip_all)]
|
||||
pub fn update_and_redraw(&mut self) -> Result<(), Error> {
|
||||
self.schedule.run(&mut self.map_context);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn schedule(&self) -> &Schedule {
|
||||
&self.schedule
|
||||
}
|
||||
pub fn scheduler(&self) -> &E::Scheduler {
|
||||
&self.scheduler
|
||||
}
|
||||
pub fn http_client(&self) -> &E::HttpClient {
|
||||
&self.http_client
|
||||
}
|
||||
|
||||
pub async fn fetch_process(&mut self, coords: &WorldTileCoords) -> Option<()> {
|
||||
let source_layers: HashSet<String> = self
|
||||
.map_context
|
||||
.style
|
||||
.layers
|
||||
.iter()
|
||||
.filter_map(|layer| layer.source_layer.clone())
|
||||
.collect();
|
||||
|
||||
let http_source_client: HttpSourceClient<E::HttpClient> =
|
||||
HttpSourceClient::new(self.http_client.clone());
|
||||
|
||||
let data = http_source_client
|
||||
.fetch(&coords)
|
||||
.await
|
||||
.unwrap() // TODO: remove unwrap
|
||||
.into_boxed_slice();
|
||||
|
||||
let mut pipeline_context = PipelineContext::new(HeadlessPipelineProcessor::default());
|
||||
let pipeline = build_vector_tile_pipeline();
|
||||
|
||||
let request = TileRequest {
|
||||
coords: WorldTileCoords::default(),
|
||||
layers: source_layers,
|
||||
};
|
||||
|
||||
pipeline.process((request, data), &mut pipeline_context);
|
||||
|
||||
let mut processor = pipeline_context
|
||||
.take_processor::<HeadlessPipelineProcessor>()
|
||||
.unwrap(); // TODO: remove unwrap
|
||||
|
||||
if let Eventually::Initialized(pool) = self.map_context.renderer.state.buffer_pool_mut() {
|
||||
pool.clear();
|
||||
}
|
||||
|
||||
self.map_context.tile_repository.clear();
|
||||
|
||||
while let Some(layer) = processor.layers.pop() {
|
||||
self.map_context
|
||||
.tile_repository
|
||||
.create_tile(&layer.get_coords());
|
||||
self.map_context
|
||||
.tile_repository
|
||||
.put_tessellated_layer(layer);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for CopySurfaceBufferNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn update(&mut self, _state: &mut RenderState) {}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
RenderContext {
|
||||
command_encoder, ..
|
||||
}: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let surface = state.surface();
|
||||
match surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let size = surface.size();
|
||||
command_encoder.copy_texture_to_buffer(
|
||||
buffered_texture.texture.as_image_copy(),
|
||||
wgpu::ImageCopyBuffer {
|
||||
buffer: &buffered_texture.output_buffer,
|
||||
layout: wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(
|
||||
std::num::NonZeroU32::new(
|
||||
buffered_texture.buffer_dimensions.padded_bytes_per_row as u32,
|
||||
)
|
||||
.unwrap(), // TODO: remove unwrap
|
||||
),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: size.width() as u32,
|
||||
height: size.height() as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Stage which writes the current contents of the GPU/CPU buffer in [`BufferedTextureHead`]
|
||||
/// to disk as PNG.
|
||||
#[derive(Default)]
|
||||
pub struct WriteSurfaceBufferStage {
|
||||
frame: u64,
|
||||
}
|
||||
|
||||
impl Stage for WriteSurfaceBufferStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
renderer: Renderer { state, device, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let surface = state.surface();
|
||||
match surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let buffered_texture: Arc<BufferedTextureHead> = buffered_texture.clone();
|
||||
|
||||
let device = device.clone();
|
||||
let current_frame = self.frame;
|
||||
|
||||
task::block_in_place(|| {
|
||||
Handle::current().block_on(async {
|
||||
buffered_texture
|
||||
.create_png(&device, format!("frame_{}.png", current_frame).as_str())
|
||||
.await;
|
||||
})
|
||||
});
|
||||
|
||||
self.frame += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod utils {
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
io::{pipeline::PipelineProcessor, tile_repository::StoredLayer, RawLayer},
|
||||
render::ShaderVertex,
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HeadlessPipelineProcessor {
|
||||
pub layers: Vec<StoredLayer>,
|
||||
}
|
||||
|
||||
impl PipelineProcessor for HeadlessPipelineProcessor {
|
||||
fn layer_tesselation_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: RawLayer,
|
||||
) {
|
||||
self.layers.push(StoredLayer::TessellatedLayer {
|
||||
coords: *coords,
|
||||
layer_name: layer_data.name,
|
||||
buffer,
|
||||
feature_indices,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
16
maplibre/src/headless/environment.rs
Normal file
16
maplibre/src/headless/environment.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use crate::environment::Environment;
|
||||
use crate::headless::window::HeadlessMapWindowConfig;
|
||||
use crate::io::apc::SchedulerAsyncProcedureCall;
|
||||
use crate::io::transferables::DefaultTransferables;
|
||||
use crate::platform::http_client::ReqwestHttpClient;
|
||||
use crate::platform::scheduler::TokioScheduler;
|
||||
|
||||
pub struct HeadlessEnvironment;
|
||||
|
||||
impl Environment for HeadlessEnvironment {
|
||||
type MapWindowConfig = HeadlessMapWindowConfig;
|
||||
type AsyncProcedureCall = SchedulerAsyncProcedureCall<Self::HttpClient, Self::Scheduler>;
|
||||
type Scheduler = TokioScheduler;
|
||||
type HttpClient = ReqwestHttpClient;
|
||||
type Transferables = DefaultTransferables;
|
||||
}
|
||||
63
maplibre/src/headless/graph_node.rs
Normal file
63
maplibre/src/headless/graph_node.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use crate::render::graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo};
|
||||
use crate::render::resource::Head;
|
||||
use crate::render::RenderState;
|
||||
|
||||
/// 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 {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for CopySurfaceBufferNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn update(&mut self, _state: &mut RenderState) {}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
RenderContext {
|
||||
command_encoder, ..
|
||||
}: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let surface = state.surface();
|
||||
match surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let size = surface.size();
|
||||
command_encoder.copy_texture_to_buffer(
|
||||
buffered_texture.texture.as_image_copy(),
|
||||
wgpu::ImageCopyBuffer {
|
||||
buffer: &buffered_texture.output_buffer,
|
||||
layout: wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(
|
||||
std::num::NonZeroU32::new(
|
||||
buffered_texture.buffer_dimensions.padded_bytes_per_row as u32,
|
||||
)
|
||||
.unwrap(), // TODO: remove unwrap
|
||||
),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: size.width() as u32,
|
||||
height: size.height() as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
145
maplibre/src/headless/map.rs
Normal file
145
maplibre/src/headless/map.rs
Normal file
@ -0,0 +1,145 @@
|
||||
use crate::io::pipeline::Processable;
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::{WorldCoords, WorldTileCoords, Zoom, TILE_SIZE},
|
||||
environment::Kernel,
|
||||
error::Error,
|
||||
headless::{
|
||||
environment::HeadlessEnvironment, graph_node::CopySurfaceBufferNode,
|
||||
stage::WriteSurfaceBufferStage,
|
||||
},
|
||||
io::{
|
||||
pipeline::{PipelineContext, PipelineProcessor},
|
||||
tile_pipelines::build_vector_tile_pipeline,
|
||||
tile_repository::{StoredLayer, StoredTile, TileStatus},
|
||||
RawLayer, TileRequest,
|
||||
},
|
||||
render::{
|
||||
create_default_render_graph, draw_graph, error::RenderError, eventually::Eventually,
|
||||
register_default_render_stages, resource::Head, stages::RenderStageLabel, Renderer,
|
||||
ShaderVertex,
|
||||
},
|
||||
schedule::{Schedule, Stage},
|
||||
style::Style,
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
window::WindowSize,
|
||||
world::World,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub struct HeadlessMap {
|
||||
window_size: WindowSize,
|
||||
kernel: Kernel<HeadlessEnvironment>,
|
||||
map_context: MapContext,
|
||||
schedule: Schedule,
|
||||
}
|
||||
|
||||
impl HeadlessMap {
|
||||
pub fn new(
|
||||
style: Style,
|
||||
window_size: WindowSize,
|
||||
renderer: Renderer,
|
||||
kernel: Kernel<HeadlessEnvironment>,
|
||||
) -> Result<Self, Error> {
|
||||
let world = World::new(
|
||||
window_size,
|
||||
WorldCoords::from((TILE_SIZE / 2., TILE_SIZE / 2.)),
|
||||
Zoom::default(),
|
||||
cgmath::Deg(110.0),
|
||||
);
|
||||
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
let mut graph = create_default_render_graph()?;
|
||||
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
|
||||
|
||||
register_default_render_stages(graph, &mut schedule);
|
||||
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Cleanup,
|
||||
WriteSurfaceBufferStage::default(),
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
window_size,
|
||||
kernel,
|
||||
map_context: MapContext {
|
||||
style,
|
||||
world,
|
||||
renderer,
|
||||
},
|
||||
schedule,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn render_tile(&mut self, tile: StoredTile) -> Result<(), Error> {
|
||||
let context = &mut self.map_context;
|
||||
|
||||
if let Eventually::Initialized(pool) = context.renderer.state.buffer_pool_mut() {
|
||||
pool.clear();
|
||||
}
|
||||
|
||||
context.world.tile_repository.put_tile(tile);
|
||||
|
||||
self.schedule.run(&mut self.map_context);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fetch_tile(
|
||||
&self,
|
||||
coords: WorldTileCoords,
|
||||
source_layers: HashSet<String>,
|
||||
) -> Result<StoredTile, Error> {
|
||||
let source_client = &self.kernel.source_client;
|
||||
|
||||
let data = source_client.fetch(&coords).await?.into_boxed_slice();
|
||||
|
||||
let mut pipeline_context = PipelineContext::new(HeadlessPipelineProcessor::default());
|
||||
let pipeline = build_vector_tile_pipeline();
|
||||
|
||||
pipeline.process(
|
||||
(
|
||||
TileRequest {
|
||||
coords: WorldTileCoords::default(),
|
||||
layers: source_layers,
|
||||
},
|
||||
data,
|
||||
),
|
||||
&mut pipeline_context,
|
||||
);
|
||||
|
||||
let mut processor = pipeline_context
|
||||
.take_processor::<HeadlessPipelineProcessor>()
|
||||
.expect("Unable to get processor");
|
||||
|
||||
Ok(StoredTile::success(coords, processor.layers))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HeadlessPipelineProcessor {
|
||||
pub layers: Vec<StoredLayer>,
|
||||
}
|
||||
|
||||
impl PipelineProcessor for HeadlessPipelineProcessor {
|
||||
fn layer_tesselation_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: RawLayer,
|
||||
) {
|
||||
self.layers.push(StoredLayer::TessellatedLayer {
|
||||
coords: *coords,
|
||||
layer_name: layer_data.name,
|
||||
buffer,
|
||||
feature_indices,
|
||||
})
|
||||
}
|
||||
}
|
||||
6
maplibre/src/headless/mod.rs
Normal file
6
maplibre/src/headless/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
mod environment;
|
||||
mod graph_node;
|
||||
mod stage;
|
||||
mod window;
|
||||
|
||||
pub mod map;
|
||||
50
maplibre/src/headless/stage.rs
Normal file
50
maplibre/src/headless/stage.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::{runtime::Handle, task};
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
resource::{BufferedTextureHead, Head},
|
||||
Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
};
|
||||
|
||||
/// Stage which writes the current contents of the GPU/CPU buffer in [`BufferedTextureHead`]
|
||||
/// to disk as PNG.
|
||||
#[derive(Default)]
|
||||
pub struct WriteSurfaceBufferStage {
|
||||
frame: u64,
|
||||
}
|
||||
|
||||
impl Stage for WriteSurfaceBufferStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
renderer: Renderer { state, device, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let surface = state.surface();
|
||||
match surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let buffered_texture: Arc<BufferedTextureHead> = buffered_texture.clone();
|
||||
|
||||
let device = device.clone();
|
||||
let current_frame = self.frame;
|
||||
|
||||
task::block_in_place(|| {
|
||||
Handle::current().block_on(async {
|
||||
buffered_texture
|
||||
.create_png(&device, format!("frame_{}.png", current_frame).as_str())
|
||||
.await;
|
||||
})
|
||||
});
|
||||
|
||||
self.frame += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
maplibre/src/headless/window.rs
Normal file
23
maplibre/src/headless/window.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use crate::window::{MapWindow, MapWindowConfig, WindowSize};
|
||||
|
||||
pub struct HeadlessMapWindowConfig {
|
||||
pub size: WindowSize,
|
||||
}
|
||||
|
||||
impl MapWindowConfig for HeadlessMapWindowConfig {
|
||||
type MapWindow = HeadlessMapWindow;
|
||||
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
Self::MapWindow { size: self.size }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HeadlessMapWindow {
|
||||
size: WindowSize,
|
||||
}
|
||||
|
||||
impl MapWindow for HeadlessMapWindow {
|
||||
fn size(&self) -> WindowSize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
@ -10,12 +10,11 @@ use std::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::io::scheduler::Scheduler;
|
||||
use crate::io::source_client::HttpClient;
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
io::{
|
||||
source_client::{HttpSourceClient, SourceClient},
|
||||
scheduler::Scheduler,
|
||||
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||
transferables::{DefaultTransferables, Transferables},
|
||||
TileRequest,
|
||||
},
|
||||
@ -112,7 +111,7 @@ impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<DefaultTransferables, HC>
|
||||
input,
|
||||
SchedulerContext {
|
||||
sender,
|
||||
source_client: SourceClient::Http(HttpSourceClient::new(client)),
|
||||
source_client: SourceClient::new(HttpSourceClient::new(client)),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -31,6 +31,6 @@ impl Scheduler for NopScheduler {
|
||||
where
|
||||
T: Future<Output = ()> + 'static,
|
||||
{
|
||||
Err(Error::Schedule)
|
||||
Err(Error::Scheduler)
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,25 +32,23 @@ where
|
||||
/// Defines the different types of HTTP clients such as basic HTTP and Mbtiles.
|
||||
/// More types might be coming such as S3 and other cloud http clients.
|
||||
#[derive(Clone)]
|
||||
pub enum SourceClient<HC>
|
||||
pub struct SourceClient<HC>
|
||||
where
|
||||
HC: HttpClient,
|
||||
{
|
||||
Http(HttpSourceClient<HC>),
|
||||
Mbtiles {
|
||||
// TODO
|
||||
},
|
||||
http: HttpSourceClient<HC>, // TODO: mbtiles: Mbtiles
|
||||
}
|
||||
|
||||
impl<HC> SourceClient<HC>
|
||||
where
|
||||
HC: HttpClient,
|
||||
{
|
||||
pub fn new(http: HttpSourceClient<HC>) -> Self {
|
||||
Self { http }
|
||||
}
|
||||
|
||||
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, Error> {
|
||||
match self {
|
||||
SourceClient::Http(client) => client.fetch(coords).await,
|
||||
SourceClient::Mbtiles { .. } => unimplemented!(),
|
||||
}
|
||||
self.http.fetch(coords).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -50,17 +50,27 @@ pub enum TileStatus {
|
||||
|
||||
/// Stores multiple [StoredLayers](StoredLayer).
|
||||
pub struct StoredTile {
|
||||
coords: WorldTileCoords,
|
||||
layers: Vec<StoredLayer>,
|
||||
status: TileStatus,
|
||||
}
|
||||
|
||||
impl StoredTile {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores and provides access to a quad tree of cached tiles with world tile coords.
|
||||
@ -102,6 +112,12 @@ impl TileRepository {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put_tile(&mut self, tile: StoredTile) {
|
||||
if let Some(key) = tile.coords.build_quad_key() {
|
||||
self.tree.insert(key, tile);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of tessellated layers at the given world tile coords. None if tile is
|
||||
/// missing from the cache.
|
||||
pub fn iter_tessellated_layers_at(
|
||||
@ -115,11 +131,11 @@ impl TileRepository {
|
||||
}
|
||||
|
||||
/// Create a new tile.
|
||||
pub fn create_tile(&mut self, coords: &WorldTileCoords) -> bool {
|
||||
pub fn create_tile(&mut self, coords: WorldTileCoords) -> bool {
|
||||
if let Some(entry) = coords.build_quad_key().map(|key| self.tree.entry(key)) {
|
||||
match entry {
|
||||
btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(StoredTile::new());
|
||||
entry.insert(StoredTile::new(coords));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -43,6 +43,7 @@ pub mod benchmarking;
|
||||
|
||||
// Internal modules
|
||||
pub(crate) mod tessellation;
|
||||
mod world;
|
||||
|
||||
// Export tile format
|
||||
pub use geozero::mvt::tile;
|
||||
|
||||
@ -1,22 +1,25 @@
|
||||
use std::{cell::RefCell, marker::PhantomData, mem, rc::Rc};
|
||||
|
||||
use crate::environment::Environment;
|
||||
use crate::render::settings::{RendererSettings, WgpuSettings};
|
||||
use crate::render::Renderer;
|
||||
use crate::window::{HeadedMapWindow, MapWindowConfig, WindowSize};
|
||||
use crate::{
|
||||
context::{MapContext, ViewState},
|
||||
context::MapContext,
|
||||
coords::{LatLon, WorldCoords, Zoom, TILE_SIZE},
|
||||
environment::Environment,
|
||||
error::Error,
|
||||
io::{
|
||||
scheduler::Scheduler,
|
||||
source_client::{HttpClient, HttpSourceClient},
|
||||
tile_repository::TileRepository,
|
||||
},
|
||||
render::{create_default_render_graph, register_default_render_stages},
|
||||
render::{
|
||||
create_default_render_graph, register_default_render_stages,
|
||||
settings::{RendererSettings, WgpuSettings},
|
||||
Renderer,
|
||||
},
|
||||
schedule::{Schedule, Stage},
|
||||
stages::register_stages,
|
||||
style::Style,
|
||||
window::{HeadedMapWindow, MapWindowConfig, WindowSize},
|
||||
world::{ViewState, World},
|
||||
};
|
||||
|
||||
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
|
||||
@ -34,26 +37,15 @@ pub struct InteractiveMapSchedule<E: Environment> {
|
||||
}
|
||||
|
||||
impl<E: Environment> InteractiveMapSchedule<E> {
|
||||
pub fn new(
|
||||
map_window_config: E::MapWindowConfig,
|
||||
window_size: WindowSize,
|
||||
renderer: Option<Renderer>,
|
||||
scheduler: E::Scheduler, // TODO: unused
|
||||
apc: E::AsyncProcedureCall,
|
||||
http_client: E::HttpClient,
|
||||
style: Style,
|
||||
wgpu_settings: WgpuSettings,
|
||||
renderer_settings: RendererSettings,
|
||||
) -> Self {
|
||||
let zoom = style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default();
|
||||
let position = style
|
||||
.center
|
||||
.map(|center| WorldCoords::from_lat_lon(LatLon::new(center[0], center[1]), zoom))
|
||||
.unwrap_or_default();
|
||||
let pitch = style.pitch.unwrap_or_default();
|
||||
let view_state = ViewState::new(&window_size, position, zoom, pitch, cgmath::Deg(110.0));
|
||||
pub fn new(window_size: WindowSize, renderer: Option<Renderer>, style: Style) -> Self {
|
||||
let center = style.center.unwrap_or_default();
|
||||
let world = World::new_at(
|
||||
window_size,
|
||||
LatLon::new(center[0], center[1]),
|
||||
style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default(),
|
||||
cgmath::Deg::<f64>(style.pitch.unwrap_or_default()),
|
||||
);
|
||||
|
||||
let tile_repository = TileRepository::new();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
let apc = Rc::new(RefCell::new(apc));
|
||||
@ -70,16 +62,14 @@ impl<E: Environment> InteractiveMapSchedule<E> {
|
||||
map_window_config,
|
||||
map_context: match renderer {
|
||||
None => EventuallyMapContext::Premature(PrematureMapContext {
|
||||
view_state,
|
||||
world,
|
||||
style,
|
||||
tile_repository,
|
||||
wgpu_settings,
|
||||
renderer_settings,
|
||||
}),
|
||||
Some(renderer) => EventuallyMapContext::Full(MapContext {
|
||||
view_state,
|
||||
world,
|
||||
style,
|
||||
tile_repository,
|
||||
renderer,
|
||||
}),
|
||||
},
|
||||
@ -169,10 +159,9 @@ where
|
||||
}
|
||||
|
||||
pub struct PrematureMapContext {
|
||||
view_state: ViewState,
|
||||
style: Style,
|
||||
world: World,
|
||||
|
||||
tile_repository: TileRepository,
|
||||
style: Style,
|
||||
|
||||
wgpu_settings: WgpuSettings,
|
||||
renderer_settings: RendererSettings,
|
||||
|
||||
@ -3,8 +3,7 @@ use reqwest::{Client, StatusCode};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use reqwest_middleware_cache::{managers::CACacheManager, Cache, CacheMode};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::io::source_client::HttpClient;
|
||||
use crate::{error::Error, io::source_client::HttpClient};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ReqwestHttpClient {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
use std::future::Future;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::io::scheduler::Scheduler;
|
||||
use crate::{error::Error, io::scheduler::Scheduler};
|
||||
|
||||
/// Multi-threading with Tokio.
|
||||
pub struct TokioScheduler;
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
use crate::environment::Environment;
|
||||
use crate::render::settings::{RendererSettings, WgpuSettings};
|
||||
use crate::render::Renderer;
|
||||
use crate::style::Style;
|
||||
use crate::window::{HeadedMapWindow, MapWindow, MapWindowConfig};
|
||||
use crate::{
|
||||
environment::Environment,
|
||||
render::{
|
||||
settings::{RendererSettings, WgpuSettings},
|
||||
Renderer,
|
||||
},
|
||||
style::Style,
|
||||
window::{HeadedMapWindow, MapWindow, MapWindowConfig},
|
||||
};
|
||||
|
||||
pub struct RenderBuilder {
|
||||
wgpu_settings: Option<WgpuSettings>,
|
||||
|
||||
36
maplibre/src/render/error.rs
Normal file
36
maplibre/src/render/error.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::{error::Error, render::graph::RenderGraphError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RenderError {
|
||||
Surface(wgpu::SurfaceError),
|
||||
Graph(RenderGraphError),
|
||||
}
|
||||
|
||||
impl fmt::Display for RenderError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RenderError::Surface(e) => write!(f, "{}", e),
|
||||
RenderError::Graph(e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderError {
|
||||
pub fn should_exit(&self) -> bool {
|
||||
match self {
|
||||
RenderError::Surface(e) => match e {
|
||||
wgpu::SurfaceError::OutOfMemory => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu::SurfaceError> for Error {
|
||||
fn from(e: wgpu::SurfaceError) -> Self {
|
||||
Error::Render(RenderError::Surface(e))
|
||||
}
|
||||
}
|
||||
@ -50,17 +50,20 @@ mod tile_view_pattern;
|
||||
// Public API
|
||||
pub mod builder;
|
||||
pub mod camera;
|
||||
pub mod error;
|
||||
pub mod eventually;
|
||||
pub mod settings;
|
||||
|
||||
pub use shaders::ShaderVertex;
|
||||
pub use stages::register_default_render_stages;
|
||||
|
||||
use crate::render::{
|
||||
graph::{EmptyNode, RenderGraph, RenderGraphError},
|
||||
main_pass::{MainPassDriverNode, MainPassNode},
|
||||
use crate::{
|
||||
render::{
|
||||
graph::{EmptyNode, RenderGraph, RenderGraphError},
|
||||
main_pass::{MainPassDriverNode, MainPassNode},
|
||||
},
|
||||
window::{HeadedMapWindow, MapWindow},
|
||||
};
|
||||
use crate::window::{HeadedMapWindow, MapWindow};
|
||||
|
||||
const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
|
||||
|
||||
@ -409,10 +412,11 @@ impl Renderer {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::render::settings::RendererSettings;
|
||||
use crate::render::RenderState;
|
||||
use crate::window::{MapWindow, MapWindowConfig, WindowSize};
|
||||
use crate::{MapWindow, MapWindowConfig, WindowSize};
|
||||
use crate::{
|
||||
render::{settings::RendererSettings, RenderState},
|
||||
window::{MapWindow, MapWindowConfig, WindowSize},
|
||||
MapWindow, MapWindowConfig, WindowSize,
|
||||
};
|
||||
|
||||
pub struct HeadlessMapWindowConfig {
|
||||
size: WindowSize,
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
//! 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::RenderState;
|
||||
use crate::render::{
|
||||
eventually::Eventually::Initialized,
|
||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
|
||||
resource::{Globals, IndexEntry, TrackedRenderPass},
|
||||
tile_view_pattern::{TileInView, TileShape},
|
||||
INDEX_FORMAT,
|
||||
RenderState, INDEX_FORMAT,
|
||||
};
|
||||
|
||||
impl PhaseItem for TileInView {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
use crate::render::resource::TrackedRenderPass;
|
||||
use crate::render::RenderState;
|
||||
use crate::render::{resource::TrackedRenderPass, RenderState};
|
||||
|
||||
/// A draw function which is used to draw a specific [`PhaseItem`].
|
||||
///
|
||||
|
||||
@ -3,10 +3,9 @@
|
||||
|
||||
use std::{mem::size_of, sync::Arc};
|
||||
|
||||
use crate::window::{MapWindow, WindowSize};
|
||||
use crate::{
|
||||
render::{eventually::HasChanged, resource::texture::TextureView, settings::RendererSettings},
|
||||
window::HeadedMapWindow,
|
||||
window::{HeadedMapWindow, MapWindow, WindowSize},
|
||||
};
|
||||
|
||||
pub struct BufferDimensions {
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
//! Extracts data from the current state.
|
||||
|
||||
use crate::render::{RenderState, Renderer};
|
||||
use crate::{
|
||||
context::MapContext, coords::ViewRegion, render::eventually::Eventually::Initialized,
|
||||
context::MapContext,
|
||||
coords::ViewRegion,
|
||||
render::{eventually::Eventually::Initialized, RenderState, Renderer},
|
||||
schedule::Stage,
|
||||
world::World,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@ -13,7 +15,7 @@ impl Stage for ExtractStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
view_state,
|
||||
world: World { view_state, .. },
|
||||
renderer:
|
||||
Renderer {
|
||||
state:
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
|
||||
use log::error;
|
||||
|
||||
use crate::render::Renderer;
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
eventually::Eventually::Initialized, graph::RenderGraph, graph_runner::RenderGraphRunner,
|
||||
Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
};
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
//! Sorts items of the [RenderPhases](RenderPhase).
|
||||
|
||||
use crate::render::Renderer;
|
||||
use crate::{context::MapContext, render::render_phase::RenderPhase, schedule::Stage};
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{render_phase::RenderPhase, Renderer},
|
||||
schedule::Stage,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PhaseSortStage;
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
//! Queues [PhaseItems](crate::render::render_phase::PhaseItem) for rendering.
|
||||
|
||||
use crate::render::{RenderState, Renderer};
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
eventually::Eventually::Initialized, resource::IndexEntry, tile_view_pattern::TileInView,
|
||||
RenderState, Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
};
|
||||
@ -17,7 +17,6 @@ impl Stage for QueueStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
view_state: _,
|
||||
renderer:
|
||||
Renderer {
|
||||
state:
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::render::Renderer;
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
@ -11,6 +10,7 @@ use crate::{
|
||||
shaders::{Shader, ShaderTileMetadata},
|
||||
tile_pipeline::TilePipeline,
|
||||
tile_view_pattern::{TileViewPattern, DEFAULT_TILE_VIEW_SIZE},
|
||||
Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
};
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
|
||||
use std::iter;
|
||||
|
||||
use crate::render::{RenderState, Renderer};
|
||||
use crate::style::Style;
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::ViewRegion,
|
||||
@ -12,8 +10,11 @@ use crate::{
|
||||
camera::ViewProjection,
|
||||
eventually::Eventually::Initialized,
|
||||
shaders::{ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32},
|
||||
RenderState, Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
style::Style,
|
||||
world::World,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@ -24,9 +25,12 @@ impl Stage for UploadStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
view_state,
|
||||
world:
|
||||
World {
|
||||
tile_repository,
|
||||
view_state,
|
||||
},
|
||||
style,
|
||||
tile_repository,
|
||||
renderer: Renderer { queue, state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
@ -41,8 +45,7 @@ impl Stage for UploadStage {
|
||||
bytemuck::cast_slice(&[ShaderGlobals::new(ShaderCamera::new(
|
||||
view_proj.downcast().into(),
|
||||
view_state
|
||||
.camera
|
||||
.position
|
||||
.camera_position()
|
||||
.to_homogeneous()
|
||||
.cast::<f32>()
|
||||
.unwrap() // TODO: Remove unwrap
|
||||
|
||||
@ -10,16 +10,15 @@ use std::{
|
||||
use geozero::{mvt::tile, GeozeroDatasource};
|
||||
use request_stage::RequestStage;
|
||||
|
||||
use crate::environment::Environment;
|
||||
use crate::io::source_client::HttpClient;
|
||||
use crate::{
|
||||
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel},
|
||||
environment::Environment,
|
||||
error::Error,
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, Context, Message},
|
||||
geometry_index::{GeometryIndex, IndexedGeometry, TileIndex},
|
||||
pipeline::{PipelineContext, PipelineProcessor, Processable},
|
||||
source_client::HttpSourceClient,
|
||||
source_client::{HttpClient, HttpSourceClient},
|
||||
tile_pipelines::build_vector_tile_pipeline,
|
||||
transferables::{
|
||||
DefaultTessellatedLayer, DefaultTileTessellated, DefaultTransferables,
|
||||
|
||||
@ -2,15 +2,16 @@
|
||||
|
||||
use std::{borrow::BorrowMut, cell::RefCell, ops::Deref, rc::Rc};
|
||||
|
||||
use crate::environment::Environment;
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
environment::Environment,
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, Message},
|
||||
tile_repository::StoredLayer,
|
||||
transferables::{TessellatedLayer, TileTessellated, UnavailableLayer},
|
||||
},
|
||||
schedule::Stage,
|
||||
world::World,
|
||||
};
|
||||
|
||||
pub struct PopulateTileStore<E: Environment> {
|
||||
@ -27,7 +28,10 @@ impl<E: Environment> Stage for PopulateTileStore<E> {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
tile_repository, ..
|
||||
world: World {
|
||||
tile_repository, ..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
if let Ok(mut apc) = self.apc.deref().try_borrow_mut() {
|
||||
|
||||
@ -12,11 +12,10 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::environment::Environment;
|
||||
use crate::style::Style;
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::{ViewRegion, WorldTileCoords, ZoomLevel},
|
||||
environment::Environment,
|
||||
error::Error,
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, Message},
|
||||
@ -29,6 +28,8 @@ use crate::{
|
||||
},
|
||||
schedule::Stage,
|
||||
stages::HeadedPipelineProcessor,
|
||||
style::Style,
|
||||
world::World,
|
||||
};
|
||||
|
||||
pub struct RequestStage<E: Environment> {
|
||||
@ -52,23 +53,25 @@ impl<E: Environment> Stage for RequestStage<E> {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
view_state,
|
||||
world:
|
||||
World {
|
||||
tile_repository,
|
||||
view_state,
|
||||
},
|
||||
style,
|
||||
tile_repository,
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let view_region = view_state.create_view_region();
|
||||
|
||||
if view_state.camera.did_change(0.05) || view_state.zoom.did_change(0.05) {
|
||||
if view_state.did_camera_change() || view_state.did_zoom_change() {
|
||||
if let Some(view_region) = &view_region {
|
||||
// FIXME: We also need to request tiles from layers above if we are over the maximum zoom level
|
||||
self.request_tiles_in_view(tile_repository, style, view_region);
|
||||
}
|
||||
}
|
||||
|
||||
view_state.camera.update_reference();
|
||||
view_state.zoom.update_reference();
|
||||
view_state.update_references();
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,7 +136,7 @@ impl<E: Environment> RequestStage<E> {
|
||||
for coords in view_region.iter() {
|
||||
if coords.build_quad_key().is_some() {
|
||||
// TODO: Make tesselation depend on style?
|
||||
self.request_tile(tile_repository, &coords, &source_layers)
|
||||
self.request_tile(tile_repository, coords, &source_layers)
|
||||
.unwrap(); // TODO: Remove unwrap
|
||||
}
|
||||
}
|
||||
@ -142,7 +145,7 @@ impl<E: Environment> RequestStage<E> {
|
||||
fn request_tile(
|
||||
&self,
|
||||
tile_repository: &mut TileRepository,
|
||||
coords: &WorldTileCoords,
|
||||
coords: WorldTileCoords,
|
||||
layers: &HashSet<String>,
|
||||
) -> Result<(), Error> {
|
||||
/* if !tile_repository.is_layers_missing(coords, layers) {
|
||||
@ -155,7 +158,7 @@ impl<E: Environment> RequestStage<E> {
|
||||
tracing::info!("new tile request: {}", &coords);
|
||||
self.apc.deref().borrow().schedule(
|
||||
Input::TileRequest(TileRequest {
|
||||
coords: *coords,
|
||||
coords,
|
||||
layers: layers.clone(),
|
||||
}),
|
||||
schedule::<
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::environment::Environment;
|
||||
use crate::map_schedule::InteractiveMapSchedule;
|
||||
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
||||
|
||||
use crate::{environment::Environment, map_schedule::InteractiveMapSchedule};
|
||||
|
||||
/// Window of a certain [`WindowSize`]. This can either be a proper window or a headless one.
|
||||
pub trait MapWindow {
|
||||
fn size(&self) -> WindowSize;
|
||||
|
||||
143
maplibre/src/world.rs
Normal file
143
maplibre/src/world.rs
Normal file
@ -0,0 +1,143 @@
|
||||
use cgmath::{Angle, Point3};
|
||||
|
||||
use crate::{
|
||||
coords::{LatLon, ViewRegion, WorldCoords, Zoom, ZoomLevel, TILE_SIZE},
|
||||
io::tile_repository::TileRepository,
|
||||
render::camera::{Camera, Perspective, ViewProjection},
|
||||
util::ChangeObserver,
|
||||
window::WindowSize,
|
||||
};
|
||||
|
||||
pub struct World {
|
||||
pub view_state: ViewState,
|
||||
pub tile_repository: TileRepository,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new_at<P: Into<cgmath::Deg<f64>>>(
|
||||
window_size: WindowSize,
|
||||
initial_center: LatLon,
|
||||
initial_zoom: Zoom,
|
||||
pitch: P,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
window_size,
|
||||
WorldCoords::from_lat_lon(initial_center, initial_zoom),
|
||||
initial_zoom,
|
||||
pitch,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new<P: Into<cgmath::Deg<f64>>>(
|
||||
window_size: WindowSize,
|
||||
initial_center: WorldCoords,
|
||||
initial_zoom: Zoom,
|
||||
pitch: P,
|
||||
) -> Self {
|
||||
let position = initial_center;
|
||||
let view_state = ViewState::new(
|
||||
window_size,
|
||||
position,
|
||||
initial_zoom,
|
||||
pitch,
|
||||
cgmath::Deg(110.0),
|
||||
);
|
||||
|
||||
let tile_repository = TileRepository::new();
|
||||
|
||||
World {
|
||||
view_state,
|
||||
tile_repository,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the camera configuration.
|
||||
pub struct ViewState {
|
||||
zoom: ChangeObserver<Zoom>,
|
||||
camera: ChangeObserver<Camera>,
|
||||
perspective: Perspective,
|
||||
}
|
||||
|
||||
impl ViewState {
|
||||
pub fn new<F: Into<cgmath::Rad<f64>>, P: Into<cgmath::Deg<f64>>>(
|
||||
window_size: WindowSize,
|
||||
position: WorldCoords,
|
||||
zoom: Zoom,
|
||||
pitch: P,
|
||||
fovy: F,
|
||||
) -> Self {
|
||||
let tile_center = TILE_SIZE / 2.0;
|
||||
let fovy = fovy.into();
|
||||
let height = tile_center / (fovy / 2.0).tan();
|
||||
|
||||
let camera = Camera::new(
|
||||
(position.x, position.y, height),
|
||||
cgmath::Deg(-90.0),
|
||||
pitch.into(),
|
||||
window_size.width(),
|
||||
window_size.height(),
|
||||
);
|
||||
|
||||
let perspective = Perspective::new(
|
||||
window_size.width(),
|
||||
window_size.height(),
|
||||
cgmath::Deg(110.0),
|
||||
// in tile.vertex.wgsl we are setting each layer's final `z` in ndc space to `z_index`.
|
||||
// This means that regardless of the `znear` value all layers will be rendered as part
|
||||
// of the near plane.
|
||||
// These values have been selected experimentally:
|
||||
// https://www.sjbaker.org/steve/omniv/love_your_z_buffer.html
|
||||
1024.0,
|
||||
2048.0,
|
||||
);
|
||||
|
||||
Self {
|
||||
zoom: ChangeObserver::new(zoom),
|
||||
camera: ChangeObserver::new(camera),
|
||||
perspective,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_view_region(&self) -> Option<ViewRegion> {
|
||||
self.camera
|
||||
.view_region_bounding_box(&self.view_projection().invert())
|
||||
.map(|bounding_box| {
|
||||
ViewRegion::new(bounding_box, 0, 32, *self.zoom, self.visible_level())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn view_projection(&self) -> ViewProjection {
|
||||
self.camera.calc_view_proj(&self.perspective)
|
||||
}
|
||||
|
||||
pub fn visible_level(&self) -> ZoomLevel {
|
||||
self.zoom.level()
|
||||
}
|
||||
|
||||
pub fn zoom(&self) -> Zoom {
|
||||
*self.zoom
|
||||
}
|
||||
|
||||
pub fn camera_position(&self) -> Point3<f64> {
|
||||
self.camera.position
|
||||
}
|
||||
|
||||
pub fn did_zoom_change(&self) -> bool {
|
||||
self.zoom.did_change(0.05)
|
||||
}
|
||||
|
||||
pub fn did_camera_change(&self) -> bool {
|
||||
self.camera.did_change(0.05)
|
||||
}
|
||||
|
||||
pub fn update_references(&mut self) {
|
||||
self.camera.update_reference();
|
||||
self.zoom.update_reference();
|
||||
}
|
||||
|
||||
pub fn update_zoom(&mut self, new_zoom: Zoom) {
|
||||
*self.zoom = new_zoom;
|
||||
log::info!("zoom: {}", new_zoom);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user