Refactor render loop to use a single pattern for the current view

This commit is contained in:
Maximilian Ammann 2022-04-04 12:29:55 +02:00
parent a1b511db45
commit 575cd07924
8 changed files with 403 additions and 220 deletions

View File

@ -67,6 +67,39 @@ impl TileCache {
}
}
pub fn has_tile(&self, coords: &WorldTileCoords) -> bool {
coords
.build_quad_key()
.and_then(|key| {
self.cache_index.get(&key).and_then(|entries| {
if entries.is_empty() {
None
} else if entries.iter().all(|entry| match entry {
LayerTessellateResult::UnavailableLayer { .. } => true,
LayerTessellateResult::TessellatedLayer { .. } => false,
}) {
None
} else {
Some(entries)
}
})
})
.is_some()
}
pub fn get_tile_coords_fallback(&self, coords: &WorldTileCoords) -> Option<WorldTileCoords> {
let mut current = *coords;
loop {
if self.has_tile(&current) {
return Some(current);
} else if let Some(parent) = current.get_parent() {
current = parent
} else {
return None;
}
}
}
pub fn iter_tessellated_layers_at(
&self,
coords: &WorldTileCoords,

View File

@ -286,7 +286,7 @@ impl<Q: Queue<B>, B, V: bytemuck::Pod, I: bytemuck::Pod, TM: bytemuck::Pod, FM:
pub struct BackingBufferDescriptor<B> {
/// The buffer which is used
buffer: B,
pub buffer: B,
/// The size of buffer
inner_size: wgpu::BufferAddress,
}

View File

@ -6,7 +6,7 @@ mod options;
mod piplines;
mod shaders;
mod texture;
mod tile_mask_pattern;
mod tile_view_pattern;
pub mod camera;
pub mod render_state;

View File

@ -7,5 +7,6 @@ pub const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must m
pub const VERTEX_BUFFER_SIZE: BufferAddress = 1024 * 1024 * 32;
pub const FEATURE_METADATA_BUFFER_SIZE: BufferAddress = 1024 * 1024 * 32;
pub const INDICES_BUFFER_SIZE: BufferAddress = 1024 * 1024 * 16;
pub const TILE_VIEW_BUFFER_SIZE: BufferAddress = 4096;
pub const TILE_META_COUNT: BufferAddress = 1024 * 24;

View File

@ -1,4 +1,3 @@
use cgmath::{Matrix4, Vector4};
use std::collections::HashSet;
use std::default::Default;
use std::fmt::Formatter;
@ -6,27 +5,28 @@ use std::io::sink;
use std::time::{Instant, SystemTime};
use std::{cmp, fmt, iter};
use crate::coords::{ViewRegion, TILE_SIZE};
use crate::io::scheduler::IOScheduler;
use crate::io::LayerTessellateResult;
use style_spec::layer::{LayerPaint, StyleLayer};
use style_spec::{EncodedSrgb, Style};
use cgmath::{Matrix4, Vector4};
use tracing;
use wgpu::{Buffer, Limits, Queue};
use winit::dpi::PhysicalSize;
use winit::window::Window;
use style_spec::layer::{LayerPaint, StyleLayer};
use style_spec::{EncodedSrgb, Style};
use crate::coords::{ViewRegion, TILE_SIZE};
use crate::io::scheduler::IOScheduler;
use crate::io::LayerTessellateResult;
use crate::platform::{COLOR_TEXTURE_FORMAT, MIN_BUFFER_SIZE};
use crate::render::buffer_pool::{BackingBufferDescriptor, BufferPool, IndexEntry};
use crate::render::camera;
use crate::render::camera::ViewProjection;
use crate::render::options::{
DEBUG_WIREFRAME, FEATURE_METADATA_BUFFER_SIZE, INDEX_FORMAT, INDICES_BUFFER_SIZE,
TILE_META_COUNT, VERTEX_BUFFER_SIZE,
TILE_META_COUNT, TILE_VIEW_BUFFER_SIZE, VERTEX_BUFFER_SIZE,
};
use crate::render::tile_mask_pattern::TileMaskPattern;
use crate::render::tile_view_pattern::{TileInView, TileViewPattern};
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
use crate::util::FPSMeter;
use super::piplines::*;
@ -34,8 +34,6 @@ use super::shaders;
use super::shaders::*;
use super::texture::Texture;
use tracing;
pub struct RenderState {
instance: wgpu::Instance,
@ -70,7 +68,7 @@ pub struct RenderState {
ShaderFeatureStyle,
>,
tile_mask_pattern: TileMaskPattern,
tile_view_pattern: TileViewPattern<Queue, Buffer>,
pub camera: camera::Camera,
pub perspective: camera::Perspective,
@ -162,14 +160,21 @@ impl RenderState {
mapped_at_creation: false,
});
let tile_view_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: TILE_VIEW_BUFFER_SIZE,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let globals_buffer_byte_size =
cmp::max(MIN_BUFFER_SIZE, std::mem::size_of::<ShaderGlobals>() as u64);
let metadata_buffer_size =
let layer_metadata_buffer_size =
std::mem::size_of::<ShaderLayerMetadata>() as u64 * TILE_META_COUNT;
let metadata_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Tiles ubo"),
size: metadata_buffer_size,
let layer_metadata_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Layer Metadata ubo"),
size: layer_metadata_buffer_size,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
@ -297,10 +302,13 @@ impl RenderState {
buffer_pool: BufferPool::new(
BackingBufferDescriptor::new(vertex_buffer, VERTEX_BUFFER_SIZE),
BackingBufferDescriptor::new(indices_buffer, INDICES_BUFFER_SIZE),
BackingBufferDescriptor::new(metadata_buffer, metadata_buffer_size),
BackingBufferDescriptor::new(layer_metadata_buffer, layer_metadata_buffer_size),
BackingBufferDescriptor::new(feature_metadata_buffer, FEATURE_METADATA_BUFFER_SIZE),
),
tile_mask_pattern: TileMaskPattern::new(),
tile_view_pattern: TileViewPattern::new(BackingBufferDescriptor::new(
tile_view_buffer,
TILE_VIEW_BUFFER_SIZE,
)),
zoom: 0.0,
style,
}
@ -379,11 +387,16 @@ impl RenderState {
/// tile metadata in the the `buffer_pool` gets updated exactly once and not twice.
#[tracing::instrument(skip_all)]
fn update_metadata(
&self,
_scheduler: &mut IOScheduler,
&mut self,
scheduler: &mut IOScheduler,
view_region: &ViewRegion,
view_proj: &ViewProjection,
) {
self.tile_view_pattern
.update_pattern(view_region, scheduler.get_tile_cache(), self.zoom);
self.tile_view_pattern
.upload_pattern(&self.queue, &view_proj);
/*let animated_one = 0.5
* (1.0
+ ((SystemTime::now()
@ -396,30 +409,9 @@ impl RenderState {
// Factor which determines how much we need to adjust the width of lines for example.
// If zoom == z -> zoom_factor == 1
for entries in self.buffer_pool.index().iter() {
/* for entries in self.buffer_pool.index().iter() {
for entry in entries {
let world_coords = entry.coords;
// FIXME: Does not take into account rendering tiles with different z
/*if !view_region.is_in_view(&entry.coords) {
continue;
}*/
let zoom_factor = 2.0_f64.powf(world_coords.z as f64 - self.zoom) as f32;
let transform: Matrix4<f32> = (view_proj
.to_model_view_projection(world_coords.transform_for_zoom(self.zoom)))
.downcast();
self.buffer_pool.update_layer_metadata(
&self.queue,
entry,
ShaderLayerMetadata::new(
transform.into(),
zoom_factor,
entry.style_layer.index as f32,
),
);
let world_coords = entry.coords;*/
// TODO: Update features
/*let source_layer = entry.style_layer.source_layer.as_ref().unwrap();
@ -468,8 +460,8 @@ impl RenderState {
}
}
}*/
}
}
/* }
}*/
}
#[tracing::instrument(skip_all)]
@ -481,10 +473,6 @@ impl RenderState {
) {
let visible_z = self.visible_z();
// Factor which determines how much we need to adjust the width of lines for example.
// If zoom == z -> zoom_factor == 1
let zoom_factor = 2.0_f64.powf(visible_z as f64 - self.zoom) as f32; // TODO deduplicate
// Upload all tessellated layers which are in view
for world_coords in view_region.iter() {
let loaded_layers = self
@ -538,23 +526,12 @@ impl RenderState {
})
.collect::<Vec<_>>();
// We are casting here from 64bit to 32bit, because 32bit is more performant and is
// better supported.
let transform: Matrix4<f32> = (view_proj.to_model_view_projection(
world_coords.transform_for_zoom(self.zoom),
))
.downcast();
self.buffer_pool.allocate_layer_geometry(
&self.queue,
*coords,
style_layer.clone(),
buffer,
ShaderLayerMetadata::new(
transform.into(),
zoom_factor,
style_layer.index as f32,
),
ShaderLayerMetadata::new(style_layer.index as f32),
&feature_metadata,
);
}
@ -567,18 +544,23 @@ impl RenderState {
#[tracing::instrument(skip_all)]
pub fn prepare_render_data(&mut self, scheduler: &mut IOScheduler) {
let visible_z = self.visible_z();
let render_setup_span = tracing::span!(tracing::Level::TRACE, "setup view region");
let _guard = render_setup_span.enter();
let view_region = self
.camera
.view_region_bounding_box(&self.camera.calc_view_proj(&self.perspective).invert())
.map(|bounding_box| ViewRegion::new(bounding_box, 1, self.zoom, visible_z));
let visible_z = self.visible_z();
let view_proj = self.camera.calc_view_proj(&self.perspective);
let view_region = self
.camera
.view_region_bounding_box(&view_proj.invert())
.map(|bounding_box| ViewRegion::new(bounding_box, 1, self.zoom, visible_z));
drop(_guard);
if let Some(view_region) = &view_region {
self.update_metadata(scheduler, &view_region, &view_proj);
self.upload_tile_geometry(&view_proj, &view_region, scheduler);
self.update_metadata(scheduler, &view_region, &view_proj);
self.request_tiles_in_view(view_region, scheduler);
}
@ -597,6 +579,9 @@ impl RenderState {
#[tracing::instrument(skip_all)]
pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
let render_setup_span = tracing::span!(tracing::Level::TRACE, "render prepare");
let _guard = render_setup_span.enter();
let frame = self.surface.get_current_texture()?;
let frame_view = frame
.texture
@ -608,8 +593,13 @@ impl RenderState {
label: Some("Encoder"),
});
drop(_guard);
{
let color_attachment = if let Some(multisampling_target) = &self.multisampling_texture {
let _span_ = tracing::span!(tracing::Level::TRACE, "render pass").entered();
{
let color_attachment =
if let Some(multisampling_target) = &self.multisampling_texture {
wgpu::RenderPassColorAttachment {
view: &multisampling_target.view,
ops: wgpu::Operations {
@ -648,48 +638,39 @@ impl RenderState {
pass.set_bind_group(0, &self.bind_group, &[]);
{
let _span_ = tracing::span!(tracing::Level::TRACE, "render pass").entered();
let visible_z = self.visible_z();
let inverted_view_proj = self.camera.calc_view_proj(&self.perspective).invert();
let view_region = self
.camera
.view_region_bounding_box(&inverted_view_proj)
.map(|bounding_box| ViewRegion::new(bounding_box, 1, self.zoom, visible_z));
let index = self.buffer_pool.index();
if let Some(view_region) = &view_region {
for world_coords in view_region.iter() {
tracing::trace!("Drawing tile at {world_coords}");
for TileInView { shape, fallback } in self.tile_view_pattern.iter() {
let coords = shape.coords;
tracing::trace!("Drawing tile at {coords}");
if let Some(entries) = index.get_layers_fallback(&world_coords) {
let mut to_render: Vec<&IndexEntry> = Vec::from_iter(entries);
to_render.sort_by_key(|entry| entry.style_layer.index);
let shape_to_render = fallback.as_ref().unwrap_or(shape);
let reference = self
.tile_mask_pattern
.stencil_reference_value(&world_coords)
.tile_view_pattern
.stencil_reference_value(&shape_to_render.coords)
as u32;
// Draw mask
if let Some(mask_entry) = entries.front() {
{
tracing::trace!("Drawing mask {}", &mask_entry.coords);
tracing::trace!("Drawing mask {}", &coords);
pass.set_pipeline(&self.mask_pipeline);
pass.set_stencil_reference(reference);
pass.set_vertex_buffer(
0,
self.buffer_pool
.metadata()
.slice(mask_entry.layer_metadata_buffer_range()),
self.tile_view_pattern
.buffer()
.slice(shape.buffer_range.clone()),
);
pass.draw(0..6, 0..1);
}
}
for entry in to_render {
if let Some(entries) = index.get_layers(&shape_to_render.coords) {
let mut layers_to_render: Vec<&IndexEntry> = Vec::from_iter(entries);
layers_to_render.sort_by_key(|entry| entry.style_layer.index);
for entry in layers_to_render {
// Draw tile
{
tracing::trace!(
@ -714,12 +695,18 @@ impl RenderState {
);
pass.set_vertex_buffer(
1,
self.tile_view_pattern
.buffer()
.slice(shape_to_render.buffer_range.clone()),
);
pass.set_vertex_buffer(
2,
self.buffer_pool
.metadata()
.slice(entry.layer_metadata_buffer_range()),
);
pass.set_vertex_buffer(
2,
3,
self.buffer_pool
.feature_metadata()
.slice(entry.feature_metadata_buffer_range()),
@ -733,8 +720,16 @@ impl RenderState {
}
}
{
let _span = tracing::span!(tracing::Level::TRACE, "render finish").entered();
tracing::trace!("Finished drawing");
self.queue.submit(Some(encoder.finish()));
tracing::trace!("Submitted queue");
frame.present();
tracing::trace!("Presented frame");
}
self.fps_meter.update_and_print();
Ok(())

View File

@ -82,7 +82,7 @@ impl VertexShaderState {
pub mod tile {
use super::{ShaderLayerMetadata, ShaderVertex};
use crate::platform::COLOR_TEXTURE_FORMAT;
use crate::render::shaders::ShaderFeatureStyle;
use crate::render::shaders::{ShaderFeatureStyle, ShaderTileMetadata};
use super::{FragmentShaderState, VertexShaderState};
@ -110,7 +110,7 @@ pub mod tile {
},
// tile metadata
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<ShaderLayerMetadata>() as u64,
array_stride: std::mem::size_of::<ShaderTileMetadata>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
// translate
@ -134,20 +134,28 @@ pub mod tile {
format: wgpu::VertexFormat::Float32x4,
shader_location: 7,
},
// zoom_factor
wgpu::VertexAttribute {
offset: 4 * wgpu::VertexFormat::Float32x4.size(),
format: wgpu::VertexFormat::Float32,
shader_location: 9,
},
],
},
// layer metadata
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<ShaderLayerMetadata>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
// z_index
wgpu::VertexAttribute {
offset: 4 * wgpu::VertexFormat::Float32x4.size()
+ wgpu::VertexFormat::Float32.size(),
offset: 0,
format: wgpu::VertexFormat::Float32,
shader_location: 10,
},
],
},
// vertex style
// features
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<ShaderFeatureStyle>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
@ -188,7 +196,7 @@ pub mod tile {
pub mod tile_mask {
use crate::platform::COLOR_TEXTURE_FORMAT;
use crate::render::options::DEBUG_STENCIL_PATTERN;
use crate::render::shaders::ShaderLayerMetadata;
use crate::render::shaders::{ShaderLayerMetadata, ShaderTileMetadata};
use wgpu::ColorWrites;
use super::{FragmentShaderState, VertexShaderState};
@ -196,7 +204,7 @@ pub mod tile_mask {
pub const VERTEX: VertexShaderState = VertexShaderState::new(
include_str!("tile_mask.vertex.wgsl"),
&[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<ShaderLayerMetadata>() as u64,
array_stride: std::mem::size_of::<ShaderTileMetadata>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
// translate
@ -309,17 +317,27 @@ pub struct ShaderFeatureStyle {
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct ShaderLayerMetadata {
pub transform: Mat4x4f32,
pub zoom_factor: f32,
pub z_index: f32,
}
impl ShaderLayerMetadata {
pub fn new(transform: Mat4x4f32, zoom_factor: f32, z_index: f32) -> Self {
pub fn new(z_index: f32) -> Self {
Self { z_index }
}
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct ShaderTileMetadata {
pub transform: Mat4x4f32,
pub zoom_factor: f32,
}
impl ShaderTileMetadata {
pub fn new(transform: Mat4x4f32, zoom_factor: f32) -> Self {
Self {
transform,
zoom_factor,
z_index,
}
}
}

View File

@ -1,21 +0,0 @@
use crate::coords::WorldTileCoords;
/// The tile mask pattern assigns each tile a value which can be used for stencil testing.
/// The pattern can be reviewed [here](https://maxammann.org/mapr/docs/stencil-masking.html).
pub struct TileMaskPattern {}
impl TileMaskPattern {
pub fn new() -> Self {
Self {}
}
pub fn stencil_reference_value(&self, world_coords: &WorldTileCoords) -> u8 {
match (world_coords.x, world_coords.y) {
(x, y) if x % 2 == 0 && y % 2 == 0 => 2,
(x, y) if x % 2 == 0 && y % 2 != 0 => 1,
(x, y) if x % 2 != 0 && y % 2 == 0 => 4,
(x, y) if x % 2 != 0 && y % 2 != 0 => 3,
_ => unreachable!(),
}
}
}

View File

@ -0,0 +1,157 @@
use crate::coords::{Quadkey, ViewRegion, WorldTileCoords};
use crate::io::tile_cache::TileCache;
use crate::render::buffer_pool::{BackingBufferDescriptor, Queue};
use crate::render::camera::ViewProjection;
use crate::render::shaders::ShaderTileMetadata;
use cgmath::Matrix4;
use lyon::geom::euclid::approxeq::ApproxEq;
use std::collections::BTreeMap;
use std::marker::PhantomData;
use std::mem::size_of;
use std::ops::Range;
/// The tile mask pattern assigns each tile a value which can be used for stencil testing.
pub struct TileViewPattern<Q, B> {
in_view: Vec<TileInView>,
buffer: BackingBuffer<B>,
phantom_q: PhantomData<Q>,
}
pub struct TileShape {
pub zoom_factor: f64,
pub coords: WorldTileCoords,
pub transform: Matrix4<f64>,
pub buffer_range: Range<wgpu::BufferAddress>,
}
pub struct TileInView {
pub shape: TileShape,
pub fallback: Option<TileShape>,
}
#[derive(Debug)]
struct BackingBuffer<B> {
/// The internal structure which is used for storage
inner: B,
/// The size of the `inner` buffer
inner_size: wgpu::BufferAddress,
}
impl<B> BackingBuffer<B> {
fn new(inner: B, inner_size: wgpu::BufferAddress) -> Self {
Self { inner, inner_size }
}
}
impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
pub fn new(buffer: BackingBufferDescriptor<B>) -> Self {
Self {
in_view: Vec::with_capacity(64),
buffer: BackingBuffer::new(buffer.buffer, buffer.inner_size),
phantom_q: Default::default(),
}
}
#[tracing::instrument(skip_all)]
pub fn update_pattern(&mut self, view_region: &ViewRegion, tile_cache: &TileCache, zoom: f64) {
self.in_view.clear();
let stride = size_of::<ShaderTileMetadata>() as u64;
let mut index = 0;
for coords in view_region.iter() {
if let None = coords.build_quad_key() {
continue;
}
let shape = TileShape {
coords,
zoom_factor: 2.0_f64.powf(coords.z as f64 - zoom),
transform: coords.transform_for_zoom(zoom),
buffer_range: index as u64 * stride..(index as u64 + 1) * stride,
};
index += 1;
let fallback = {
if !tile_cache.has_tile(&coords) {
if let Some(fallback_coords) = tile_cache.get_tile_coords_fallback(&coords) {
let shape = TileShape {
coords: fallback_coords,
zoom_factor: 2.0_f64.powf(fallback_coords.z as f64 - zoom),
transform: fallback_coords.transform_for_zoom(zoom),
buffer_range: index as u64 * stride..(index as u64 + 1) * stride,
};
index += 1;
Some(shape)
} else {
None
}
} else {
None
}
};
self.in_view.push(TileInView { shape, fallback });
}
}
pub fn iter(&self) -> impl Iterator<Item = &TileInView> + '_ {
self.in_view.iter()
}
pub fn buffer(&self) -> &B {
&self.buffer.inner
}
#[tracing::instrument(skip_all)]
pub fn upload_pattern(&self, queue: &Q, view_proj: &ViewProjection) {
let mut buffer = Vec::with_capacity(self.in_view.len());
for tile in &self.in_view {
buffer.push(ShaderTileMetadata {
// We are casting here from 64bit to 32bit, because 32bit is more performant and is
// better supported.
transform: view_proj
.to_model_view_projection(tile.shape.transform)
.downcast()
.into(),
zoom_factor: tile.shape.zoom_factor as f32,
});
if let Some(fallback_shape) = &tile.fallback {
buffer.push(ShaderTileMetadata {
// We are casting here from 64bit to 32bit, because 32bit is more performant and is
// better supported.
transform: view_proj
.to_model_view_projection(fallback_shape.transform)
.downcast()
.into(),
zoom_factor: fallback_shape.zoom_factor as f32,
});
}
}
queue.write_buffer(
&self.buffer.inner,
0,
&bytemuck::cast_slice(&buffer.as_slice()),
);
}
pub fn stencil_reference_value(&self, world_coords: &WorldTileCoords) -> u8 {
world_coords.z * 5
+ match (world_coords.x, world_coords.y) {
(x, y) if x % 2 == 0 && y % 2 == 0 => 2,
(x, y) if x % 2 == 0 && y % 2 != 0 => 1,
(x, y) if x % 2 != 0 && y % 2 == 0 => 4,
(x, y) if x % 2 != 0 && y % 2 != 0 => 3,
_ => unreachable!(),
}
}
}