Improve headless rendering (#143)

* Remove util module in renderer

* Implement default for ZoomLevel

* Add function to clear the bufferpool

* Calculate camera height dynamically

* Deduplicate code and add sample tile-grid

* Remove unused function
This commit is contained in:
Max Ammann 2022-07-17 16:08:38 +02:00 committed by GitHub
parent 6f409d7aa2
commit d1b6360360
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 194 additions and 144 deletions

View File

@ -33,43 +33,10 @@ fn headless_render(c: &mut Criterion) {
.initialize_headless()
.await;
let http_source_client: HttpSourceClient<ReqwestHttpClient> =
HttpSourceClient::new(ReqwestHttpClient::new(None));
let coords = WorldTileCoords::from((0, 0, ZoomLevel::default()));
let request_id = 0;
let data = http_source_client
.fetch(&coords)
map.map_schedule
.fetch_process(&WorldTileCoords::from((0, 0, ZoomLevel::default())))
.await
.unwrap()
.into_boxed_slice();
let processor = HeadlessPipelineProcessor::default();
let mut pipeline_context = PipelineContext::new(processor);
let pipeline = build_vector_tile_pipeline();
pipeline.process(
(
TileRequest {
coords,
layers: HashSet::from(["boundary".to_owned(), "water".to_owned()]),
},
request_id,
data,
),
&mut pipeline_context,
);
let mut processor = pipeline_context
.take_processor::<HeadlessPipelineProcessor>()
.unwrap();
while let Some(v) = processor.layers.pop() {
map.map_schedule_mut()
.map_context
.tile_repository
.put_tessellated_layer(v);
}
.expect("Failed to fetch and process!");
map
});

View File

@ -17,6 +17,8 @@ env_logger = "0.9.0"
maplibre = { path = "../maplibre", version = "0.0.2", features = ["headless"] }
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
tile-grid = "0.3"
tracing = "0.1.35"
tracing-subscriber = { version = "0.3.14", optional = true }
tracing-tracy = { version = "0.8", optional = true }

View File

@ -1,5 +1,5 @@
use maplibre::benchmarking::tessellation::{IndexDataType, OverAlignedVertexBuffer};
use maplibre::coords::{WorldTileCoords, ZoomLevel};
use maplibre::coords::{TileCoords, ViewRegion, WorldTileCoords, ZoomLevel};
use maplibre::error::Error;
use maplibre::headless::HeadlessMapWindowConfig;
use maplibre::io::pipeline::Processable;
@ -20,7 +20,11 @@ use maplibre::MapBuilder;
use maplibre_winit::winit::WinitMapWindowConfig;
use maplibre::headless::utils::HeadlessPipelineProcessor;
use maplibre::style::source::TileAddressingScheme;
use maplibre::util::grid::google_mercator;
use maplibre::util::math::Aabb2;
use std::collections::HashSet;
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
#[cfg(feature = "trace")]
fn enable_tracing() {
@ -61,52 +65,33 @@ fn run_headless() {
.initialize_headless()
.await;
let http_source_client: HttpSourceClient<ReqwestHttpClient> =
HttpSourceClient::new(ReqwestHttpClient::new(None));
let coords = WorldTileCoords::from((0, 0, ZoomLevel::default()));
let request_id = 0;
let data = http_source_client
.fetch(&coords)
.await
.unwrap()
.into_boxed_slice();
let processor = HeadlessPipelineProcessor::default();
let mut pipeline_context = PipelineContext::new(processor);
let pipeline = build_vector_tile_pipeline();
pipeline.process(
(
TileRequest {
coords,
layers: HashSet::from(["boundary".to_owned(), "water".to_owned()]),
},
request_id,
data,
),
&mut pipeline_context,
let tile_limits = google_mercator().tile_limits(
extent_wgs84_to_merc(&Extent {
minx: 11.3475219363,
miny: 48.0345697188,
maxx: 11.7917815798,
maxy: 48.255861,
}),
0,
);
let mut processor = pipeline_context
.take_processor::<HeadlessPipelineProcessor>()
.unwrap();
for (z, x, y) in GridIterator::new(10, 10, tile_limits) {
let coords = WorldTileCoords::from((x as i32, y as i32, z.into()));
println!("Rendering {}", &coords);
map.map_schedule
.fetch_process(&coords)
.await
.expect("Failed to fetch and process!");
while let Some(v) = processor.layers.pop() {
map.map_schedule_mut()
.map_context
.tile_repository
.put_tessellated_layer(v);
match map.map_schedule_mut().update_and_redraw() {
Ok(_) => {}
Err(Error::Render(e)) => {
eprintln!("{}", e);
if e.should_exit() {}
}
e => eprintln!("{:?}", e),
};
}
match map.map_schedule_mut().update_and_redraw() {
Ok(_) => {}
Err(Error::Render(e)) => {
eprintln!("{}", e);
if e.should_exit() {}
}
e => eprintln!("{:?}", e),
};
})
}
@ -116,6 +101,6 @@ fn main() {
#[cfg(feature = "trace")]
enable_tracing();
run_headless();
//run_headless();
run_in_window();
}

View File

@ -3,6 +3,8 @@ use crate::io::tile_repository::TileRepository;
use crate::render::camera::{Camera, Perspective, ViewProjection};
use crate::util::ChangeObserver;
use crate::{Renderer, Style, WindowSize};
use cgmath::Angle;
use std::ops::Div;
/// Stores the camera configuration.
pub struct ViewState {
@ -12,10 +14,20 @@ pub struct ViewState {
}
impl ViewState {
pub fn new(window_size: &WindowSize, zoom: Zoom, center: LatLon, pitch: f64) -> Self {
pub fn new<P: Into<cgmath::Rad<f64>>>(
window_size: &WindowSize,
zoom: Zoom,
center: LatLon,
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 position = WorldCoords::from_lat_lon(center, zoom);
let camera = Camera::new(
(position.x, position.y, 150.0),
(position.x, position.y, height),
cgmath::Deg(-90.0),
cgmath::Deg(pitch),
window_size.width(),

View File

@ -59,7 +59,7 @@ impl fmt::Debug for Quadkey {
}
}
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone, Debug)]
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone, Debug, Default)]
pub struct ZoomLevel(u8);
impl ZoomLevel {
@ -71,12 +71,6 @@ impl ZoomLevel {
}
}
impl Default for ZoomLevel {
fn default() -> Self {
ZoomLevel(0)
}
}
impl std::ops::Add<u8> for ZoomLevel {
type Output = ZoomLevel;
@ -206,7 +200,7 @@ impl SignificantlyDifferent for Zoom {
/// # Coordinate System Origin
///
/// The origin is in the upper-left corner.
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct InnerCoords {
pub x: f64,
pub y: f64,
@ -218,7 +212,7 @@ pub struct InnerCoords {
/// # Coordinate System Origin
///
/// For Web Mercator the origin of the coordinate system is in the upper-left corner.
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Default)]
pub struct TileCoords {
pub x: u32,
pub y: u32,
@ -272,7 +266,7 @@ impl From<(u32, u32, ZoomLevel)> for TileCoords {
/// # Coordinate System Origin
///
/// The origin of the coordinate system is in the upper-left corner.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct WorldTileCoords {
pub x: i32,
pub y: i32,
@ -465,7 +459,7 @@ impl AlignedWorldTileCoords {
/// # Coordinate System Origin
///
/// The origin of the coordinate system is in the upper-left corner.
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct WorldCoords {
pub x: f64,
pub y: f64,

View File

@ -1,8 +1,16 @@
use crate::context::{MapContext, ViewState};
use crate::coords::{LatLon, ViewRegion, Zoom};
use crate::coords::{LatLon, ViewRegion, WorldTileCoords, Zoom};
use crate::error::Error;
use crate::io::tile_repository::TileRepository;
use crate::headless::utils::HeadlessPipelineProcessor;
use crate::io::pipeline::PipelineContext;
use crate::io::pipeline::Processable;
use crate::io::source_client::HttpSourceClient;
use crate::io::tile_pipelines::build_vector_tile_pipeline;
use crate::io::tile_repository::{StoredLayer, TileRepository};
use crate::io::tile_request_state::TileRequestState;
use crate::io::TileRequest;
use crate::render::camera::ViewProjection;
use crate::render::eventually::Eventually;
use crate::render::graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo};
use crate::render::resource::{BufferDimensions, BufferedTextureHead, IndexEntry};
use crate::render::resource::{Head, TrackedRenderPass};
@ -14,6 +22,7 @@ use crate::schedule::{Schedule, Stage};
use crate::{
HttpClient, MapWindow, MapWindowConfig, Renderer, ScheduleMethod, Scheduler, Style, WindowSize,
};
use std::collections::HashSet;
use std::fs::File;
use std::future::Future;
use std::io::Write;
@ -81,6 +90,7 @@ where
schedule: Schedule,
scheduler: Scheduler<SM>,
http_client: HC,
tile_request_state: TileRequestState,
}
impl<MWC, SM, HC> HeadlessMapSchedule<MWC, SM, HC>
@ -105,6 +115,7 @@ where
.map(|center| LatLon::new(center[0], center[1]))
.unwrap_or_default(),
style.pitch.unwrap_or_default(),
cgmath::Deg(110.0),
);
let tile_repository = TileRepository::new();
let mut schedule = Schedule::default();
@ -134,13 +145,13 @@ where
schedule,
scheduler,
http_client,
tile_request_state: Default::default(),
}
}
#[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(())
}
@ -153,6 +164,55 @@ where
pub fn http_client(&self) -> &HC {
&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<HC> =
HttpSourceClient::new(self.http_client.clone());
let data = http_source_client
.fetch(&coords)
.await
.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,
};
let request_id = self
.tile_request_state
.start_tile_request(request.clone())?;
pipeline.process((request, request_id, data), &mut pipeline_context);
self.tile_request_state.finish_tile_request(request_id);
let mut processor = pipeline_context
.take_processor::<HeadlessPipelineProcessor>()
.unwrap();
if let Eventually::Initialized(pool) = self.map_context.renderer.state.buffer_pool_mut() {
pool.clear();
}
while let Some(layer) = processor.layers.pop() {
self.map_context
.tile_repository
.put_tessellated_layer(layer);
}
Some(())
}
}
/// Node which copies the contents of the GPU-side texture in [`BufferedTextureHead`] to an
@ -182,10 +242,11 @@ impl Node for CopySurfaceBufferNode {
}: &mut RenderContext,
state: &RenderState,
) -> Result<(), NodeRunError> {
match state.surface.head() {
let surface = state.surface();
match surface.head() {
Head::Headed(_) => {}
Head::Headless(buffered_texture) => {
let size = state.surface.size();
let size = surface.size();
command_encoder.copy_texture_to_buffer(
buffered_texture.texture.as_image_copy(),
wgpu::ImageCopyBuffer {
@ -229,7 +290,8 @@ impl Stage for WriteSurfaceBufferStage {
..
}: &mut MapContext,
) {
match state.surface.head() {
let surface = state.surface();
match surface.head() {
Head::Headed(_) => {}
Head::Headless(buffered_texture) => {
let buffered_texture: Arc<BufferedTextureHead> = buffered_texture.clone();

View File

@ -36,6 +36,7 @@ pub mod platform;
// Exposed because of camera
pub mod render;
pub mod style;
pub mod util;
pub mod window;
// Exposed because of doc-strings
@ -48,7 +49,6 @@ pub mod benchmarking;
// Internal modules
pub(crate) mod tessellation;
pub mod util;
/// The [`Map`] defines the public interface of the map renderer.
// DO NOT IMPLEMENT INTERNALS ON THIS STRUCT.

View File

@ -60,6 +60,7 @@ where
.map(|center| LatLon::new(center[0], center[1]))
.unwrap_or_default(),
style.pitch.unwrap_or_default(),
cgmath::Deg(110.0),
);
let tile_repository = TileRepository::new();
let mut schedule = Schedule::default();

View File

@ -1,39 +1,5 @@
use std::cmp::Ordering;
use std::mem;
/// A wrapper type that enables ordering floats. This is a work around for the famous "rust float
/// ordering" problem. By using it, you acknowledge that sorting NaN is undefined according to spec.
/// This implementation treats NaN as the "smallest" float.
#[derive(Debug, Copy, Clone, PartialOrd)]
pub struct FloatOrd(pub f32);
impl PartialEq for FloatOrd {
fn eq(&self, other: &Self) -> bool {
if self.0.is_nan() && other.0.is_nan() {
true
} else {
self.0 == other.0
}
}
}
impl Eq for FloatOrd {}
#[allow(clippy::derive_ord_xor_partial_ord)]
impl Ord for FloatOrd {
fn cmp(&self, other: &Self) -> Ordering {
self.0.partial_cmp(&other.0).unwrap_or_else(|| {
if self.0.is_nan() && !other.0.is_nan() {
Ordering::Less
} else if !self.0.is_nan() && other.0.is_nan() {
Ordering::Greater
} else {
Ordering::Equal
}
})
}
}
/// Wrapper around a resource which can be initialized or uninitialized.
/// Uninitialized resourced can be initialized by calling [`Eventually::initialize()`].
pub enum Eventually<T> {

View File

@ -18,6 +18,7 @@
//! We appreciate the design and implementation work which as gone into it.
//!
use crate::render::eventually::Eventually;
use crate::render::render_phase::RenderPhase;
use crate::render::resource::{BufferPool, Globals, IndexEntry};
use crate::render::resource::{Head, Surface};
@ -25,7 +26,6 @@ use crate::render::resource::{Texture, TextureView};
use crate::render::settings::{RendererSettings, WgpuSettings};
use crate::render::shaders::{ShaderFeatureStyle, ShaderLayerMetadata};
use crate::render::tile_view_pattern::{TileInView, TileShape, TileViewPattern};
use crate::render::util::Eventually;
use crate::tessellation::IndexDataType;
use crate::{HeadedMapWindow, MapWindow};
use log::info;
@ -43,10 +43,10 @@ mod render_phase;
mod shaders;
mod tile_pipeline;
mod tile_view_pattern;
mod util;
// Public API
pub mod camera;
pub mod eventually;
pub mod settings;
use crate::render::graph::{EmptyNode, RenderGraph, RenderGraphError};
@ -79,7 +79,7 @@ pub struct RenderState {
depth_texture: Eventually<Texture>,
multisampling_texture: Eventually<Option<Texture>>,
pub surface: Surface,
surface: Surface,
mask_phase: RenderPhase<TileInView>,
tile_phase: RenderPhase<(IndexEntry, TileShape)>,
@ -108,6 +108,25 @@ impl RenderState {
{
self.surface.recreate::<MW>(window, instance);
}
pub fn surface(&self) -> &Surface {
&self.surface
}
pub fn buffer_pool_mut(
&mut self,
) -> &mut Eventually<
BufferPool<
wgpu::Queue,
wgpu::Buffer,
ShaderVertex,
IndexDataType,
ShaderLayerMetadata,
ShaderFeatureStyle,
>,
> {
&mut self.buffer_pool
}
}
pub struct Renderer {

View File

@ -1,10 +1,10 @@
//! Specifies the instructions which are going to be sent to the GPU. Render commands can be concatenated
//! into a new render command which executes multiple instruction sets.
use crate::render::eventually::Eventually::Initialized;
use crate::render::render_phase::{PhaseItem, RenderCommand, RenderCommandResult};
use crate::render::resource::{Globals, IndexEntry, TrackedRenderPass};
use crate::render::tile_view_pattern::{TileInView, TileShape};
use crate::render::util::Eventually::Initialized;
use crate::render::INDEX_FORMAT;
use crate::RenderState;

View File

@ -129,6 +129,10 @@ impl<Q: Queue<B>, B, V: Pod, I: Pod, TM: Pod, FM: Pod> BufferPool<Q, B, V, I, TM
}
}
pub fn clear(&mut self) {
self.index.clear()
}
#[cfg(test)]
fn available_space(&self, typ: BackingBufferType) -> wgpu::BufferAddress {
let gap = match typ {
@ -480,6 +484,11 @@ impl RingIndex {
}
}
pub fn clear(&mut self) {
self.linear_index.clear();
self.tree_index.clear();
}
pub fn front(&self) -> Option<&IndexEntry> {
self.linear_index
.front()

View File

@ -1,9 +1,9 @@
//! Utilities for handling surfaces which can be either headless or headed. A headed surface has
//! a handle to a window. A headless surface renders to a texture.
use crate::render::eventually::HasChanged;
use crate::render::resource::texture::TextureView;
use crate::render::settings::RendererSettings;
use crate::render::util::HasChanged;
use crate::window::HeadedMapWindow;
use crate::{MapWindow, WindowSize};

View File

@ -1,8 +1,8 @@
//! Utility for a texture view which can either be created by a [`TextureView`](wgpu::TextureView)
//! or [`SurfaceTexture`](wgpu::SurfaceTexture)
use crate::render::eventually::HasChanged;
use crate::render::settings::Msaa;
use crate::render::util::HasChanged;
use std::ops::Deref;
/// Describes a [`TextureView`].

View File

@ -3,7 +3,7 @@
use crate::context::MapContext;
use crate::coords::ViewRegion;
use crate::render::util::Eventually::Initialized;
use crate::render::eventually::Eventually::Initialized;
use crate::schedule::Stage;
use crate::{RenderState, Renderer};

View File

@ -4,7 +4,7 @@ use crate::context::MapContext;
use crate::render::graph::RenderGraph;
use crate::render::graph_runner::RenderGraphRunner;
use crate::render::util::Eventually::Initialized;
use crate::render::eventually::Eventually::Initialized;
use crate::schedule::Stage;
use crate::Renderer;
use log::error;

View File

@ -4,8 +4,8 @@ use crate::context::MapContext;
use crate::render::resource::IndexEntry;
use crate::render::eventually::Eventually::Initialized;
use crate::render::tile_view_pattern::TileInView;
use crate::render::util::Eventually::Initialized;
use crate::schedule::Stage;
use crate::{RenderState, Renderer};

View File

@ -9,7 +9,7 @@ use crate::render::shaders::{
ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32,
};
use crate::render::util::Eventually::Initialized;
use crate::render::eventually::Eventually::Initialized;
use crate::schedule::Stage;
use crate::{RenderState, Renderer, Style};

View File

@ -345,6 +345,39 @@ where
}
}
/// A wrapper type that enables ordering floats. This is a work around for the famous "rust float
/// ordering" problem. By using it, you acknowledge that sorting NaN is undefined according to spec.
/// This implementation treats NaN as the "smallest" float.
#[derive(Debug, Copy, Clone, PartialOrd)]
pub struct FloatOrd(pub f32);
impl PartialEq for FloatOrd {
fn eq(&self, other: &Self) -> bool {
if self.0.is_nan() && other.0.is_nan() {
true
} else {
self.0 == other.0
}
}
}
impl Eq for FloatOrd {}
#[allow(clippy::derive_ord_xor_partial_ord)]
impl Ord for FloatOrd {
fn cmp(&self, other: &Self) -> Ordering {
self.0.partial_cmp(&other.0).unwrap_or_else(|| {
if self.0.is_nan() && !other.0.is_nan() {
Ordering::Less
} else if !self.0.is_nan() && other.0.is_nan() {
Ordering::Greater
} else {
Ordering::Equal
}
})
}
}
pub const fn div_away(lhs: i32, rhs: i32) -> i32 {
if rhs < 0 {
panic!("rhs must be positive")