From 7332ccb377921d07c1b47942d1df87f3a87e2e7b Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Sat, 1 Jan 2022 02:08:24 -0500 Subject: [PATCH] Support dynamically changed terrain --- bin/convert/layers.rs | 4 +- bin/level/app.rs | 5 +- bin/road/game.rs | 10 +++- lib/ffi/src/lib.rs | 39 ++++++++++++--- src/level/mod.rs | 32 ++++++------ src/render/mipmap.rs | 2 +- src/render/mod.rs | 7 +-- src/render/terrain.rs | 113 ++++++++++++++++++++++++++++++++---------- 8 files changed, 151 insertions(+), 61 deletions(-) diff --git a/bin/convert/layers.rs b/bin/convert/layers.rs index 213a27f0..c0f43d9b 100644 --- a/bin/convert/layers.rs +++ b/bin/convert/layers.rs @@ -134,8 +134,8 @@ impl LevelLayers { LevelData { size: (self.size.0 as i32, self.size.1 as i32), - meta, - height, + meta: meta.into_boxed_slice(), + height: height.into_boxed_slice(), } } } diff --git a/bin/level/app.rs b/bin/level/app.rs index adf0e35f..803ef225 100644 --- a/bin/level/app.rs +++ b/bin/level/app.rs @@ -22,7 +22,7 @@ enum Input { pub struct LevelView { render: Render, - _level: level::Level, + level: level::Level, cam: space::Camera, input: Input, @@ -123,7 +123,7 @@ impl LevelView { LevelView { render, - _level: level, + level, cam: space::Camera { loc: cgmath::vec3(0.0, 0.0, 400.0), rot: cgmath::Quaternion::new(1.0, 0.0, 0.0, 0.0), @@ -372,6 +372,7 @@ impl Application for LevelView { self.render.draw_world( &mut encoder, &mut Batcher::new(), + &self.level, &self.cam, targets, device, diff --git a/bin/road/game.rs b/bin/road/game.rs index 839201b3..c7945876 100644 --- a/bin/road/game.rs +++ b/bin/road/game.rs @@ -923,8 +923,14 @@ impl Application for Game { label: Some("Draw"), }); - self.render - .draw_world(&mut encoder, &mut self.batcher, &self.cam, targets, device); + self.render.draw_world( + &mut encoder, + &mut self.batcher, + &self.level, + &self.cam, + targets, + device, + ); /* self.render.debug.draw_lines( diff --git a/lib/ffi/src/lib.rs b/lib/ffi/src/lib.rs index d5c26250..65cc22a6 100644 --- a/lib/ffi/src/lib.rs +++ b/lib/ffi/src/lib.rs @@ -1,5 +1,6 @@ //! Rusty Vangers FFI bindings. //! Matches "lib/renderer/src/renderer/scene/rust/vange_rs.h" +//! See https://github.com/KranX/Vangers/pull/517 use futures::executor::LocalPool; use std::ptr; @@ -46,6 +47,7 @@ pub struct CameraDescription { } #[repr(C)] +#[derive(Clone, Copy)] pub struct MapDescription { width: i32, height: i32, @@ -61,8 +63,14 @@ struct Camera { transform: Transform, } +struct LevelContext { + desc: MapDescription, + render: vangers::render::Render, + level: vangers::level::Level, +} + pub struct Context { - renderer: Option, + level: Option, render_config: vangers::config::settings::Render, color_format: wgpu::TextureFormat, queue: wgpu::Queue, @@ -72,6 +80,7 @@ pub struct Context { camera: Camera, } +#[no_mangle] pub extern "C" fn rv_init() -> Option> { use vangers::config::settings as st; @@ -95,7 +104,7 @@ pub extern "C" fn rv_init() -> Option> { .ok()?; let ctx = Context { - renderer: None, + level: None, render_config: st::Render { wgpu_trace_path: String::new(), light: st::Light { @@ -124,18 +133,22 @@ pub extern "C" fn rv_init() -> Option> { ptr::NonNull::new(ptr) } +#[no_mangle] pub unsafe extern "C" fn rv_exit(ctx: *mut Context) { let _ctx = Box::from_raw(ctx); } +#[no_mangle] pub extern "C" fn rv_camera_init(ctx: &mut Context, desc: CameraDescription) { ctx.camera.desc = desc; } +#[no_mangle] pub extern "C" fn rv_camera_set_transform(ctx: &mut Context, transform: Transform) { ctx.camera.transform = transform; } +#[no_mangle] pub extern "C" fn rv_map_init(ctx: &mut Context, desc: MapDescription) { let terrains = (0..desc.material_count) .map(|i| unsafe { @@ -149,10 +162,10 @@ pub extern "C" fn rv_map_init(ctx: &mut Context, desc: MapDescription) { let total = (desc.width * desc.height) as usize; let level = vangers::level::Level { size: (desc.width, desc.height), - flood_map: vec![], + flood_map: vec![].into_boxed_slice(), flood_section_power: 0, //TODO - height: vec![0; total], - meta: vec![0; total], + height: vec![0; total].into_boxed_slice(), + meta: vec![0; total].into_boxed_slice(), palette: [[0; 4]; 0x100], //TODO terrains, }; @@ -162,7 +175,7 @@ pub extern "C" fn rv_map_init(ctx: &mut Context, desc: MapDescription) { &ctx.queue, &ctx.downlevel_caps, &level, - &[], //TODO: objects palette + &[[0; 4]; 0x100], //TODO: objects palette &ctx.render_config, ctx.color_format, // extent only matters for "scatter" style rendering @@ -172,9 +185,19 @@ pub extern "C" fn rv_map_init(ctx: &mut Context, desc: MapDescription) { depth_or_array_layers: 0, }, ); - ctx.renderer = Some(render); + ctx.level = Some(LevelContext { + desc, + render, + level, + }); } +#[no_mangle] pub extern "C" fn rv_map_exit(ctx: &mut Context) { - ctx.renderer = None; + ctx.level = None; +} + +#[no_mangle] +pub extern "C" fn rv_map_request_update(ctx: &mut Context, region: Rect) { + //TODO } diff --git a/src/level/mod.rs b/src/level/mod.rs index 2ea5e72e..61a3f15f 100644 --- a/src/level/mod.rs +++ b/src/level/mod.rs @@ -22,10 +22,10 @@ pub const HEIGHT_SCALE: u32 = 128; pub struct Level { pub size: (i32, i32), - pub flood_map: Vec, + pub flood_map: Box<[u8]>, pub flood_section_power: usize, - pub height: Vec, - pub meta: Vec, + pub height: Box<[u8]>, + pub meta: Box<[u8]>, pub palette: [[u8; 4]; 0x100], pub terrains: Box<[TerrainConfig]>, } @@ -91,10 +91,10 @@ impl Level { }; Level { size: (2, 1), - flood_map: vec![0], + flood_map: vec![0].into_boxed_slice(), flood_section_power: 0, - height: vec![0, 0], - meta: vec![0, 0], + height: vec![0, 0].into_boxed_slice(), + meta: vec![0, 0].into_boxed_slice(), palette: [[0xFF; 4]; 0x100], terrains: (0..8).map(|_| tc.clone()).collect(), } @@ -206,14 +206,14 @@ pub fn read_palette(input: File, config: Option<&[TerrainConfig]>) -> [[u8; 4]; data } -pub fn load_flood(config: &LevelConfig) -> Vec { +pub fn load_flood(config: &LevelConfig) -> Box<[u8]> { profiling::scope!("Flood Map"); let size = (config.size.0.as_value(), config.size.1.as_value()); let flood_size = size.1 >> config.section.as_power(); let vpr_file = match File::open(&config.path_data.with_extension("vpr")) { Ok(file) => file, - Err(_) => return vec![0; flood_size as usize], + Err(_) => return vec![0; flood_size as usize].into_boxed_slice(), }; info!("Loading flood map..."); @@ -232,8 +232,8 @@ pub fn load_flood(config: &LevelConfig) -> Vec { } pub struct LevelData { - pub height: Vec, - pub meta: Vec, + pub height: Box<[u8]>, + pub meta: Box<[u8]>, pub size: (i32, i32), } @@ -289,8 +289,8 @@ impl LevelData { let total = (size.0 * size.1) as usize; assert_eq!(data.len(), total * 4); let mut level = LevelData { - height: vec![0u8; total], - meta: vec![0u8; total], + height: vec![0u8; total].into_boxed_slice(), + meta: vec![0u8; total].into_boxed_slice(), size, }; @@ -330,8 +330,8 @@ pub fn load_vmc(path: &Path, size: (i32, i32)) -> LevelData { info!("Loading height map..."); let total = (size.0 * size.1) as usize; let mut level = LevelData { - height: vec![0u8; total], - meta: vec![0u8; total], + height: vec![0u8; total].into_boxed_slice(), + meta: vec![0u8; total].into_boxed_slice(), size, }; @@ -381,8 +381,8 @@ pub fn load_vmc(path: &Path, size: (i32, i32)) -> LevelData { pub fn load_vmp(path: &Path, size: (i32, i32)) -> LevelData { let total = (size.0 * size.1) as usize; let mut level = LevelData { - height: vec![0u8; total], - meta: vec![0u8; total], + height: vec![0u8; total].into_boxed_slice(), + meta: vec![0u8; total].into_boxed_slice(), size, }; diff --git a/src/render/mipmap.rs b/src/render/mipmap.rs index 85182745..51c1e986 100644 --- a/src/render/mipmap.rs +++ b/src/render/mipmap.rs @@ -127,8 +127,8 @@ impl MaxMipper { pub fn update( &self, - rects: &[Rect], encoder: &mut wgpu::CommandEncoder, + rects: &[Rect], device: &wgpu::Device, ) { let mut vertex_data = Vec::with_capacity(rects.len() * 6); diff --git a/src/render/mod.rs b/src/render/mod.rs index 36bd546d..e40f7579 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -583,12 +583,15 @@ impl Render { &mut self, encoder: &mut wgpu::CommandEncoder, batcher: &mut Batcher, + level: &level::Level, cam: &Camera, targets: ScreenTargets<'_>, device: &wgpu::Device, ) { profiling::scope!("draw_world"); batcher.prepare(device); + self.terrain.update_dirty(encoder, level, device); + //TODO: common routine for draw passes //TODO: use `write_buffer` @@ -610,11 +613,9 @@ impl Render { mem::size_of::() as wgpu::BufferAddress, ); - self.terrain.prepare( + self.terrain.prepare_shadow( encoder, device, - &self.global, - &self.fog_config, cam, wgpu::Extent3d { width: shadow.size, diff --git a/src/render/terrain.rs b/src/render/terrain.rs index 1dffc436..1cb50400 100644 --- a/src/render/terrain.rs +++ b/src/render/terrain.rs @@ -190,6 +190,8 @@ pub struct Context { raytrace_geo: Geometry, kind: Kind, shadow_kind: Kind, + height_texture: wgpu::Texture, + meta_texture: wgpu::Texture, dirty_rects: Vec, } @@ -499,26 +501,6 @@ impl Context { usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, }); - queue.write_texture( - height_texture.as_image_copy(), - bytemuck::cast_slice(&level.height), - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: NonZeroU32::new(level.size.0 as u32), - rows_per_image: None, - }, - extent, - ); - queue.write_texture( - meta_texture.as_image_copy(), - bytemuck::cast_slice(&level.meta), - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: NonZeroU32::new(level.size.0 as u32), - rows_per_image: None, - }, - extent, - ); queue.write_texture( flood_texture.as_image_copy(), bytemuck::cast_slice(&level.flood_map), @@ -931,6 +913,8 @@ impl Context { raytrace_geo, kind, shadow_kind, + height_texture, + meta_texture, dirty_rects: vec![Rect { x: 0, y: 0, @@ -1027,6 +1011,88 @@ impl Context { } } + pub fn update_dirty( + &mut self, + encoder: &mut wgpu::CommandEncoder, + level: &level::Level, + device: &wgpu::Device, + ) { + if self.dirty_rects.is_empty() { + return; + } + + for rect in self.dirty_rects.iter() { + let origin = wgpu::Origin3d { + x: rect.x as u32, + y: rect.y as u32, + z: 0, + }; + let extent = wgpu::Extent3d { + width: rect.w as u32, + height: rect.h as u32, + depth_or_array_layers: 1, + }; + + let staging_stride = rect.w as u32 * 2; + let staging_buf = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("staging level update"), + size: staging_stride as wgpu::BufferAddress * rect.h as wgpu::BufferAddress, + usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::MAP_WRITE, + mapped_at_creation: true, + }); + { + let mut mapping = staging_buf.slice(..).get_mapped_range_mut(); + for (row, y) in mapping + .chunks_mut(staging_stride as usize) + .zip(rect.y as usize..) + { + let level_offset = y * level.size.0 as usize; + row[..rect.w as usize] + .copy_from_slice(&level.height[level_offset..][..rect.w as usize]); + row[rect.w as usize..] + .copy_from_slice(&level.meta[level_offset..][..rect.w as usize]); + } + } + staging_buf.unmap(); + + encoder.copy_buffer_to_texture( + wgpu::ImageCopyBuffer { + buffer: &staging_buf, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(staging_stride), + rows_per_image: None, + }, + }, + wgpu::ImageCopyTexture { + origin, + ..self.height_texture.as_image_copy() + }, + extent, + ); + encoder.copy_buffer_to_texture( + wgpu::ImageCopyBuffer { + buffer: &staging_buf, + layout: wgpu::ImageDataLayout { + offset: rect.w as wgpu::BufferAddress, + bytes_per_row: NonZeroU32::new(staging_stride), + rows_per_image: None, + }, + }, + wgpu::ImageCopyTexture { + origin, + ..self.meta_texture.as_image_copy() + }, + extent, + ); + } + + if let Kind::RayMip { ref mipper, .. } = self.kind { + mipper.update(encoder, &self.dirty_rects, device); + } + self.dirty_rects.clear(); + } + pub fn prepare( &mut self, encoder: &mut wgpu::CommandEncoder, @@ -1036,13 +1102,6 @@ impl Context { cam: &Camera, screen_size: wgpu::Extent3d, ) { - if !self.dirty_rects.is_empty() { - if let Kind::RayMip { ref mipper, .. } = self.kind { - mipper.update(&self.dirty_rects, encoder, device); - } - self.dirty_rects.clear(); - } - let params = match self.kind { Kind::RayMip { params, .. } => params, _ => [0; 4],