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 crate::{render::Renderer, style::Style, world::World};
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores the context of the map.
|
/// Stores the context of the map.
|
||||||
pub struct MapContext {
|
pub struct MapContext {
|
||||||
pub view_state: ViewState,
|
|
||||||
pub style: Style,
|
pub style: Style,
|
||||||
|
pub world: World,
|
||||||
pub tile_repository: TileRepository,
|
|
||||||
pub renderer: Renderer,
|
pub renderer: Renderer,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
use crate::io::scheduler::Scheduler;
|
use crate::{
|
||||||
use crate::io::source_client::HttpClient;
|
io::{
|
||||||
use crate::io::{
|
apc::AsyncProcedureCall,
|
||||||
apc::AsyncProcedureCall,
|
scheduler::Scheduler,
|
||||||
transferables::{
|
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||||
DefaultTessellatedLayer, DefaultTileTessellated, DefaultUnavailableLayer, Transferables,
|
transferables::{
|
||||||
|
DefaultTessellatedLayer, DefaultTileTessellated, DefaultUnavailableLayer, Transferables,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
window::MapWindowConfig,
|
||||||
};
|
};
|
||||||
use crate::window::MapWindowConfig;
|
|
||||||
|
|
||||||
pub trait Environment: 'static {
|
pub trait Environment: 'static {
|
||||||
type MapWindowConfig: MapWindowConfig;
|
type MapWindowConfig: MapWindowConfig;
|
||||||
@ -19,10 +21,10 @@ pub trait Environment: 'static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Kernel<E: Environment> {
|
pub struct Kernel<E: Environment> {
|
||||||
map_window_config: E::MapWindowConfig,
|
pub map_window_config: E::MapWindowConfig,
|
||||||
apc: E::AsyncProcedureCall,
|
pub apc: E::AsyncProcedureCall,
|
||||||
scheduler: E::Scheduler,
|
pub scheduler: E::Scheduler,
|
||||||
http_client: E::HttpClient,
|
pub source_client: SourceClient<E::HttpClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KernelBuilder<E: Environment> {
|
pub struct KernelBuilder<E: Environment> {
|
||||||
@ -64,9 +66,9 @@ impl<E: Environment> KernelBuilder<E> {
|
|||||||
|
|
||||||
pub fn build(self) -> Kernel<E> {
|
pub fn build(self) -> Kernel<E> {
|
||||||
Kernel {
|
Kernel {
|
||||||
scheduler: self.scheduler.unwrap(), // TODO: Remove unwrap
|
scheduler: self.scheduler.unwrap(), // TODO: Remove unwrap
|
||||||
apc: self.apc.unwrap(), // TODO: Remove unwrap
|
apc: self.apc.unwrap(), // TODO: Remove unwrap
|
||||||
http_client: self.http_client.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
|
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;
|
use lyon::tessellation::TessellationError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
use crate::render::{error::RenderError, graph::RenderGraphError};
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enumeration of errors which can happen during the operation of the library.
|
/// Enumeration of errors which can happen during the operation of the library.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Schedule,
|
Scheduler,
|
||||||
Network(String),
|
Network(String),
|
||||||
Tesselation(TessellationError),
|
Tesselation(TessellationError),
|
||||||
Render(RenderError),
|
Render(RenderError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<wgpu::SurfaceError> for Error {
|
|
||||||
fn from(e: wgpu::SurfaceError) -> Self {
|
|
||||||
Error::Render(RenderError::Surface(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TessellationError> for Error {
|
impl From<TessellationError> for Error {
|
||||||
fn from(e: TessellationError) -> Self {
|
fn from(e: TessellationError) -> Self {
|
||||||
Error::Tesselation(e)
|
Error::Tesselation(e)
|
||||||
@ -51,6 +23,6 @@ impl From<TessellationError> for Error {
|
|||||||
|
|
||||||
impl<T> From<SendError<T>> for Error {
|
impl<T> From<SendError<T>> for Error {
|
||||||
fn from(_e: SendError<T>) -> Self {
|
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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::io::scheduler::Scheduler;
|
|
||||||
use crate::io::source_client::HttpClient;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
coords::WorldTileCoords,
|
coords::WorldTileCoords,
|
||||||
io::{
|
io::{
|
||||||
source_client::{HttpSourceClient, SourceClient},
|
scheduler::Scheduler,
|
||||||
|
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||||
transferables::{DefaultTransferables, Transferables},
|
transferables::{DefaultTransferables, Transferables},
|
||||||
TileRequest,
|
TileRequest,
|
||||||
},
|
},
|
||||||
@ -112,7 +111,7 @@ impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<DefaultTransferables, HC>
|
|||||||
input,
|
input,
|
||||||
SchedulerContext {
|
SchedulerContext {
|
||||||
sender,
|
sender,
|
||||||
source_client: SourceClient::Http(HttpSourceClient::new(client)),
|
source_client: SourceClient::new(HttpSourceClient::new(client)),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@ -31,6 +31,6 @@ impl Scheduler for NopScheduler {
|
|||||||
where
|
where
|
||||||
T: Future<Output = ()> + 'static,
|
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.
|
/// 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.
|
/// More types might be coming such as S3 and other cloud http clients.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum SourceClient<HC>
|
pub struct SourceClient<HC>
|
||||||
where
|
where
|
||||||
HC: HttpClient,
|
HC: HttpClient,
|
||||||
{
|
{
|
||||||
Http(HttpSourceClient<HC>),
|
http: HttpSourceClient<HC>, // TODO: mbtiles: Mbtiles
|
||||||
Mbtiles {
|
|
||||||
// TODO
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<HC> SourceClient<HC>
|
impl<HC> SourceClient<HC>
|
||||||
where
|
where
|
||||||
HC: HttpClient,
|
HC: HttpClient,
|
||||||
{
|
{
|
||||||
|
pub fn new(http: HttpSourceClient<HC>) -> Self {
|
||||||
|
Self { http }
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, Error> {
|
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, Error> {
|
||||||
match self {
|
self.http.fetch(coords).await
|
||||||
SourceClient::Http(client) => client.fetch(coords).await,
|
|
||||||
SourceClient::Mbtiles { .. } => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,17 +50,27 @@ pub enum TileStatus {
|
|||||||
|
|
||||||
/// Stores multiple [StoredLayers](StoredLayer).
|
/// Stores multiple [StoredLayers](StoredLayer).
|
||||||
pub struct StoredTile {
|
pub struct StoredTile {
|
||||||
|
coords: WorldTileCoords,
|
||||||
layers: Vec<StoredLayer>,
|
layers: Vec<StoredLayer>,
|
||||||
status: TileStatus,
|
status: TileStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoredTile {
|
impl StoredTile {
|
||||||
pub fn new() -> Self {
|
pub fn new(coords: WorldTileCoords) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
coords,
|
||||||
layers: vec![],
|
layers: vec![],
|
||||||
status: TileStatus::Pending,
|
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.
|
/// 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
|
/// Returns the list of tessellated layers at the given world tile coords. None if tile is
|
||||||
/// missing from the cache.
|
/// missing from the cache.
|
||||||
pub fn iter_tessellated_layers_at(
|
pub fn iter_tessellated_layers_at(
|
||||||
@ -115,11 +131,11 @@ impl TileRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new tile.
|
/// 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)) {
|
if let Some(entry) = coords.build_quad_key().map(|key| self.tree.entry(key)) {
|
||||||
match entry {
|
match entry {
|
||||||
btree_map::Entry::Vacant(entry) => {
|
btree_map::Entry::Vacant(entry) => {
|
||||||
entry.insert(StoredTile::new());
|
entry.insert(StoredTile::new(coords));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,7 @@ pub mod benchmarking;
|
|||||||
|
|
||||||
// Internal modules
|
// Internal modules
|
||||||
pub(crate) mod tessellation;
|
pub(crate) mod tessellation;
|
||||||
|
mod world;
|
||||||
|
|
||||||
// Export tile format
|
// Export tile format
|
||||||
pub use geozero::mvt::tile;
|
pub use geozero::mvt::tile;
|
||||||
|
|||||||
@ -1,22 +1,25 @@
|
|||||||
use std::{cell::RefCell, marker::PhantomData, mem, rc::Rc};
|
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::{
|
use crate::{
|
||||||
context::{MapContext, ViewState},
|
context::MapContext,
|
||||||
coords::{LatLon, WorldCoords, Zoom, TILE_SIZE},
|
coords::{LatLon, WorldCoords, Zoom, TILE_SIZE},
|
||||||
|
environment::Environment,
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{
|
io::{
|
||||||
scheduler::Scheduler,
|
scheduler::Scheduler,
|
||||||
source_client::{HttpClient, HttpSourceClient},
|
source_client::{HttpClient, HttpSourceClient},
|
||||||
tile_repository::TileRepository,
|
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},
|
schedule::{Schedule, Stage},
|
||||||
stages::register_stages,
|
stages::register_stages,
|
||||||
style::Style,
|
style::Style,
|
||||||
|
window::{HeadedMapWindow, MapWindowConfig, WindowSize},
|
||||||
|
world::{ViewState, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
|
/// 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> {
|
impl<E: Environment> InteractiveMapSchedule<E> {
|
||||||
pub fn new(
|
pub fn new(window_size: WindowSize, renderer: Option<Renderer>, style: Style) -> Self {
|
||||||
map_window_config: E::MapWindowConfig,
|
let center = style.center.unwrap_or_default();
|
||||||
window_size: WindowSize,
|
let world = World::new_at(
|
||||||
renderer: Option<Renderer>,
|
window_size,
|
||||||
scheduler: E::Scheduler, // TODO: unused
|
LatLon::new(center[0], center[1]),
|
||||||
apc: E::AsyncProcedureCall,
|
style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default(),
|
||||||
http_client: E::HttpClient,
|
cgmath::Deg::<f64>(style.pitch.unwrap_or_default()),
|
||||||
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));
|
|
||||||
|
|
||||||
let tile_repository = TileRepository::new();
|
|
||||||
let mut schedule = Schedule::default();
|
let mut schedule = Schedule::default();
|
||||||
|
|
||||||
let apc = Rc::new(RefCell::new(apc));
|
let apc = Rc::new(RefCell::new(apc));
|
||||||
@ -70,16 +62,14 @@ impl<E: Environment> InteractiveMapSchedule<E> {
|
|||||||
map_window_config,
|
map_window_config,
|
||||||
map_context: match renderer {
|
map_context: match renderer {
|
||||||
None => EventuallyMapContext::Premature(PrematureMapContext {
|
None => EventuallyMapContext::Premature(PrematureMapContext {
|
||||||
view_state,
|
world,
|
||||||
style,
|
style,
|
||||||
tile_repository,
|
|
||||||
wgpu_settings,
|
wgpu_settings,
|
||||||
renderer_settings,
|
renderer_settings,
|
||||||
}),
|
}),
|
||||||
Some(renderer) => EventuallyMapContext::Full(MapContext {
|
Some(renderer) => EventuallyMapContext::Full(MapContext {
|
||||||
view_state,
|
world,
|
||||||
style,
|
style,
|
||||||
tile_repository,
|
|
||||||
renderer,
|
renderer,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -169,10 +159,9 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrematureMapContext {
|
pub struct PrematureMapContext {
|
||||||
view_state: ViewState,
|
world: World,
|
||||||
style: Style,
|
|
||||||
|
|
||||||
tile_repository: TileRepository,
|
style: Style,
|
||||||
|
|
||||||
wgpu_settings: WgpuSettings,
|
wgpu_settings: WgpuSettings,
|
||||||
renderer_settings: RendererSettings,
|
renderer_settings: RendererSettings,
|
||||||
|
|||||||
@ -3,8 +3,7 @@ use reqwest::{Client, StatusCode};
|
|||||||
use reqwest_middleware::ClientWithMiddleware;
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
use reqwest_middleware_cache::{managers::CACacheManager, Cache, CacheMode};
|
use reqwest_middleware_cache::{managers::CACacheManager, Cache, CacheMode};
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{error::Error, io::source_client::HttpClient};
|
||||||
use crate::io::source_client::HttpClient;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ReqwestHttpClient {
|
pub struct ReqwestHttpClient {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{error::Error, io::scheduler::Scheduler};
|
||||||
use crate::io::scheduler::Scheduler;
|
|
||||||
|
|
||||||
/// Multi-threading with Tokio.
|
/// Multi-threading with Tokio.
|
||||||
pub struct TokioScheduler;
|
pub struct TokioScheduler;
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
use crate::environment::Environment;
|
use crate::{
|
||||||
use crate::render::settings::{RendererSettings, WgpuSettings};
|
environment::Environment,
|
||||||
use crate::render::Renderer;
|
render::{
|
||||||
use crate::style::Style;
|
settings::{RendererSettings, WgpuSettings},
|
||||||
use crate::window::{HeadedMapWindow, MapWindow, MapWindowConfig};
|
Renderer,
|
||||||
|
},
|
||||||
|
style::Style,
|
||||||
|
window::{HeadedMapWindow, MapWindow, MapWindowConfig},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct RenderBuilder {
|
pub struct RenderBuilder {
|
||||||
wgpu_settings: Option<WgpuSettings>,
|
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
|
// Public API
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
|
pub mod error;
|
||||||
pub mod eventually;
|
pub mod eventually;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
||||||
pub use shaders::ShaderVertex;
|
pub use shaders::ShaderVertex;
|
||||||
pub use stages::register_default_render_stages;
|
pub use stages::register_default_render_stages;
|
||||||
|
|
||||||
use crate::render::{
|
use crate::{
|
||||||
graph::{EmptyNode, RenderGraph, RenderGraphError},
|
render::{
|
||||||
main_pass::{MainPassDriverNode, MainPassNode},
|
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
|
const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
|
||||||
|
|
||||||
@ -409,10 +412,11 @@ impl Renderer {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::render::settings::RendererSettings;
|
use crate::{
|
||||||
use crate::render::RenderState;
|
render::{settings::RendererSettings, RenderState},
|
||||||
use crate::window::{MapWindow, MapWindowConfig, WindowSize};
|
window::{MapWindow, MapWindowConfig, WindowSize},
|
||||||
use crate::{MapWindow, MapWindowConfig, WindowSize};
|
MapWindow, MapWindowConfig, WindowSize,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct HeadlessMapWindowConfig {
|
pub struct HeadlessMapWindowConfig {
|
||||||
size: WindowSize,
|
size: WindowSize,
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
//! Specifies the instructions which are going to be sent to the GPU. Render commands can be concatenated
|
//! 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.
|
//! into a new render command which executes multiple instruction sets.
|
||||||
|
|
||||||
use crate::render::RenderState;
|
|
||||||
use crate::render::{
|
use crate::render::{
|
||||||
eventually::Eventually::Initialized,
|
eventually::Eventually::Initialized,
|
||||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
|
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
|
||||||
resource::{Globals, IndexEntry, TrackedRenderPass},
|
resource::{Globals, IndexEntry, TrackedRenderPass},
|
||||||
tile_view_pattern::{TileInView, TileShape},
|
tile_view_pattern::{TileInView, TileShape},
|
||||||
INDEX_FORMAT,
|
RenderState, INDEX_FORMAT,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl PhaseItem for TileInView {
|
impl PhaseItem for TileInView {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use crate::render::resource::TrackedRenderPass;
|
use crate::render::{resource::TrackedRenderPass, RenderState};
|
||||||
use crate::render::RenderState;
|
|
||||||
|
|
||||||
/// A draw function which is used to draw a specific [`PhaseItem`].
|
/// A draw function which is used to draw a specific [`PhaseItem`].
|
||||||
///
|
///
|
||||||
|
|||||||
@ -3,10 +3,9 @@
|
|||||||
|
|
||||||
use std::{mem::size_of, sync::Arc};
|
use std::{mem::size_of, sync::Arc};
|
||||||
|
|
||||||
use crate::window::{MapWindow, WindowSize};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
render::{eventually::HasChanged, resource::texture::TextureView, settings::RendererSettings},
|
render::{eventually::HasChanged, resource::texture::TextureView, settings::RendererSettings},
|
||||||
window::HeadedMapWindow,
|
window::{HeadedMapWindow, MapWindow, WindowSize},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct BufferDimensions {
|
pub struct BufferDimensions {
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
//! Extracts data from the current state.
|
//! Extracts data from the current state.
|
||||||
|
|
||||||
use crate::render::{RenderState, Renderer};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::MapContext, coords::ViewRegion, render::eventually::Eventually::Initialized,
|
context::MapContext,
|
||||||
|
coords::ViewRegion,
|
||||||
|
render::{eventually::Eventually::Initialized, RenderState, Renderer},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -13,7 +15,7 @@ impl Stage for ExtractStage {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
MapContext {
|
MapContext {
|
||||||
view_state,
|
world: World { view_state, .. },
|
||||||
renderer:
|
renderer:
|
||||||
Renderer {
|
Renderer {
|
||||||
state:
|
state:
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
use log::error;
|
use log::error;
|
||||||
|
|
||||||
use crate::render::Renderer;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::MapContext,
|
context::MapContext,
|
||||||
render::{
|
render::{
|
||||||
eventually::Eventually::Initialized, graph::RenderGraph, graph_runner::RenderGraphRunner,
|
eventually::Eventually::Initialized, graph::RenderGraph, graph_runner::RenderGraphRunner,
|
||||||
|
Renderer,
|
||||||
},
|
},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
//! Sorts items of the [RenderPhases](RenderPhase).
|
//! Sorts items of the [RenderPhases](RenderPhase).
|
||||||
|
|
||||||
use crate::render::Renderer;
|
use crate::{
|
||||||
use crate::{context::MapContext, render::render_phase::RenderPhase, schedule::Stage};
|
context::MapContext,
|
||||||
|
render::{render_phase::RenderPhase, Renderer},
|
||||||
|
schedule::Stage,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PhaseSortStage;
|
pub struct PhaseSortStage;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
//! Queues [PhaseItems](crate::render::render_phase::PhaseItem) for rendering.
|
//! Queues [PhaseItems](crate::render::render_phase::PhaseItem) for rendering.
|
||||||
|
|
||||||
use crate::render::{RenderState, Renderer};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::MapContext,
|
context::MapContext,
|
||||||
render::{
|
render::{
|
||||||
eventually::Eventually::Initialized, resource::IndexEntry, tile_view_pattern::TileInView,
|
eventually::Eventually::Initialized, resource::IndexEntry, tile_view_pattern::TileInView,
|
||||||
|
RenderState, Renderer,
|
||||||
},
|
},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
};
|
};
|
||||||
@ -17,7 +17,6 @@ impl Stage for QueueStage {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
MapContext {
|
MapContext {
|
||||||
view_state: _,
|
|
||||||
renderer:
|
renderer:
|
||||||
Renderer {
|
Renderer {
|
||||||
state:
|
state:
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
use crate::render::Renderer;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::MapContext,
|
context::MapContext,
|
||||||
render::{
|
render::{
|
||||||
@ -11,6 +10,7 @@ use crate::{
|
|||||||
shaders::{Shader, ShaderTileMetadata},
|
shaders::{Shader, ShaderTileMetadata},
|
||||||
tile_pipeline::TilePipeline,
|
tile_pipeline::TilePipeline,
|
||||||
tile_view_pattern::{TileViewPattern, DEFAULT_TILE_VIEW_SIZE},
|
tile_view_pattern::{TileViewPattern, DEFAULT_TILE_VIEW_SIZE},
|
||||||
|
Renderer,
|
||||||
},
|
},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use crate::render::{RenderState, Renderer};
|
|
||||||
use crate::style::Style;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::MapContext,
|
context::MapContext,
|
||||||
coords::ViewRegion,
|
coords::ViewRegion,
|
||||||
@ -12,8 +10,11 @@ use crate::{
|
|||||||
camera::ViewProjection,
|
camera::ViewProjection,
|
||||||
eventually::Eventually::Initialized,
|
eventually::Eventually::Initialized,
|
||||||
shaders::{ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32},
|
shaders::{ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32},
|
||||||
|
RenderState, Renderer,
|
||||||
},
|
},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
|
style::Style,
|
||||||
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -24,9 +25,12 @@ impl Stage for UploadStage {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
MapContext {
|
MapContext {
|
||||||
view_state,
|
world:
|
||||||
|
World {
|
||||||
|
tile_repository,
|
||||||
|
view_state,
|
||||||
|
},
|
||||||
style,
|
style,
|
||||||
tile_repository,
|
|
||||||
renderer: Renderer { queue, state, .. },
|
renderer: Renderer { queue, state, .. },
|
||||||
..
|
..
|
||||||
}: &mut MapContext,
|
}: &mut MapContext,
|
||||||
@ -41,8 +45,7 @@ impl Stage for UploadStage {
|
|||||||
bytemuck::cast_slice(&[ShaderGlobals::new(ShaderCamera::new(
|
bytemuck::cast_slice(&[ShaderGlobals::new(ShaderCamera::new(
|
||||||
view_proj.downcast().into(),
|
view_proj.downcast().into(),
|
||||||
view_state
|
view_state
|
||||||
.camera
|
.camera_position()
|
||||||
.position
|
|
||||||
.to_homogeneous()
|
.to_homogeneous()
|
||||||
.cast::<f32>()
|
.cast::<f32>()
|
||||||
.unwrap() // TODO: Remove unwrap
|
.unwrap() // TODO: Remove unwrap
|
||||||
|
|||||||
@ -10,16 +10,15 @@ use std::{
|
|||||||
use geozero::{mvt::tile, GeozeroDatasource};
|
use geozero::{mvt::tile, GeozeroDatasource};
|
||||||
use request_stage::RequestStage;
|
use request_stage::RequestStage;
|
||||||
|
|
||||||
use crate::environment::Environment;
|
|
||||||
use crate::io::source_client::HttpClient;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel},
|
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel},
|
||||||
|
environment::Environment,
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{
|
io::{
|
||||||
apc::{AsyncProcedureCall, Context, Message},
|
apc::{AsyncProcedureCall, Context, Message},
|
||||||
geometry_index::{GeometryIndex, IndexedGeometry, TileIndex},
|
geometry_index::{GeometryIndex, IndexedGeometry, TileIndex},
|
||||||
pipeline::{PipelineContext, PipelineProcessor, Processable},
|
pipeline::{PipelineContext, PipelineProcessor, Processable},
|
||||||
source_client::HttpSourceClient,
|
source_client::{HttpClient, HttpSourceClient},
|
||||||
tile_pipelines::build_vector_tile_pipeline,
|
tile_pipelines::build_vector_tile_pipeline,
|
||||||
transferables::{
|
transferables::{
|
||||||
DefaultTessellatedLayer, DefaultTileTessellated, DefaultTransferables,
|
DefaultTessellatedLayer, DefaultTileTessellated, DefaultTransferables,
|
||||||
|
|||||||
@ -2,15 +2,16 @@
|
|||||||
|
|
||||||
use std::{borrow::BorrowMut, cell::RefCell, ops::Deref, rc::Rc};
|
use std::{borrow::BorrowMut, cell::RefCell, ops::Deref, rc::Rc};
|
||||||
|
|
||||||
use crate::environment::Environment;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::MapContext,
|
context::MapContext,
|
||||||
|
environment::Environment,
|
||||||
io::{
|
io::{
|
||||||
apc::{AsyncProcedureCall, Message},
|
apc::{AsyncProcedureCall, Message},
|
||||||
tile_repository::StoredLayer,
|
tile_repository::StoredLayer,
|
||||||
transferables::{TessellatedLayer, TileTessellated, UnavailableLayer},
|
transferables::{TessellatedLayer, TileTessellated, UnavailableLayer},
|
||||||
},
|
},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PopulateTileStore<E: Environment> {
|
pub struct PopulateTileStore<E: Environment> {
|
||||||
@ -27,7 +28,10 @@ impl<E: Environment> Stage for PopulateTileStore<E> {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
MapContext {
|
MapContext {
|
||||||
tile_repository, ..
|
world: World {
|
||||||
|
tile_repository, ..
|
||||||
|
},
|
||||||
|
..
|
||||||
}: &mut MapContext,
|
}: &mut MapContext,
|
||||||
) {
|
) {
|
||||||
if let Ok(mut apc) = self.apc.deref().try_borrow_mut() {
|
if let Ok(mut apc) = self.apc.deref().try_borrow_mut() {
|
||||||
|
|||||||
@ -12,11 +12,10 @@ use std::{
|
|||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::environment::Environment;
|
|
||||||
use crate::style::Style;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::MapContext,
|
context::MapContext,
|
||||||
coords::{ViewRegion, WorldTileCoords, ZoomLevel},
|
coords::{ViewRegion, WorldTileCoords, ZoomLevel},
|
||||||
|
environment::Environment,
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{
|
io::{
|
||||||
apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, Message},
|
apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, Message},
|
||||||
@ -29,6 +28,8 @@ use crate::{
|
|||||||
},
|
},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
stages::HeadedPipelineProcessor,
|
stages::HeadedPipelineProcessor,
|
||||||
|
style::Style,
|
||||||
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct RequestStage<E: Environment> {
|
pub struct RequestStage<E: Environment> {
|
||||||
@ -52,23 +53,25 @@ impl<E: Environment> Stage for RequestStage<E> {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
MapContext {
|
MapContext {
|
||||||
view_state,
|
world:
|
||||||
|
World {
|
||||||
|
tile_repository,
|
||||||
|
view_state,
|
||||||
|
},
|
||||||
style,
|
style,
|
||||||
tile_repository,
|
|
||||||
..
|
..
|
||||||
}: &mut MapContext,
|
}: &mut MapContext,
|
||||||
) {
|
) {
|
||||||
let view_region = view_state.create_view_region();
|
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 {
|
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
|
// 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);
|
self.request_tiles_in_view(tile_repository, style, view_region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
view_state.camera.update_reference();
|
view_state.update_references();
|
||||||
view_state.zoom.update_reference();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +136,7 @@ impl<E: Environment> RequestStage<E> {
|
|||||||
for coords in view_region.iter() {
|
for coords in view_region.iter() {
|
||||||
if coords.build_quad_key().is_some() {
|
if coords.build_quad_key().is_some() {
|
||||||
// TODO: Make tesselation depend on style?
|
// 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
|
.unwrap(); // TODO: Remove unwrap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,7 +145,7 @@ impl<E: Environment> RequestStage<E> {
|
|||||||
fn request_tile(
|
fn request_tile(
|
||||||
&self,
|
&self,
|
||||||
tile_repository: &mut TileRepository,
|
tile_repository: &mut TileRepository,
|
||||||
coords: &WorldTileCoords,
|
coords: WorldTileCoords,
|
||||||
layers: &HashSet<String>,
|
layers: &HashSet<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
/* if !tile_repository.is_layers_missing(coords, layers) {
|
/* if !tile_repository.is_layers_missing(coords, layers) {
|
||||||
@ -155,7 +158,7 @@ impl<E: Environment> RequestStage<E> {
|
|||||||
tracing::info!("new tile request: {}", &coords);
|
tracing::info!("new tile request: {}", &coords);
|
||||||
self.apc.deref().borrow().schedule(
|
self.apc.deref().borrow().schedule(
|
||||||
Input::TileRequest(TileRequest {
|
Input::TileRequest(TileRequest {
|
||||||
coords: *coords,
|
coords,
|
||||||
layers: layers.clone(),
|
layers: layers.clone(),
|
||||||
}),
|
}),
|
||||||
schedule::<
|
schedule::<
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use crate::environment::Environment;
|
|
||||||
use crate::map_schedule::InteractiveMapSchedule;
|
|
||||||
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
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.
|
/// Window of a certain [`WindowSize`]. This can either be a proper window or a headless one.
|
||||||
pub trait MapWindow {
|
pub trait MapWindow {
|
||||||
fn size(&self) -> WindowSize;
|
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