diff --git a/libs/render/buffer/global_layout/src/lib.rs b/libs/render/buffer/global_layout/src/lib.rs index 51bfe291..01b43698 100644 --- a/libs/render/buffer/global_layout/src/lib.rs +++ b/libs/render/buffer/global_layout/src/lib.rs @@ -14,10 +14,12 @@ // along with OpenFA. If not, see . pub enum GlobalSets { + Global = 0, Atmosphere = 1, Stars = 2, - ShapeTextures = 3, - ShapeBuffers = 4, + ShapeBuffers = 3, + ShapeTextures = 4, + ShapeArt = 5, } impl From for usize { diff --git a/libs/render/buffer/shape_chunk/examples/demo-instances.rs b/libs/render/buffer/shape_chunk/examples/demo-chunks.rs similarity index 75% rename from libs/render/buffer/shape_chunk/examples/demo-instances.rs rename to libs/render/buffer/shape_chunk/examples/demo-chunks.rs index 32f3f410..896c4e34 100644 --- a/libs/render/buffer/shape_chunk/examples/demo-instances.rs +++ b/libs/render/buffer/shape_chunk/examples/demo-chunks.rs @@ -16,11 +16,10 @@ use camera::{ArcBallCamera, CameraAbstract}; use failure::Fallible; use global_layout::GlobalSets; use input::{InputBindings, InputSystem}; -use log::trace; use nalgebra::Matrix4; use omnilib::OmniLib; use pal::Palette; -use shape_chunk::{ClosedChunk, DrawSelection, DrawState, OpenChunk, Vertex}; +use shape_chunk::{DrawSelection, DrawState, ShapeChunkManager, Vertex}; use std::{sync::Arc, time::Instant}; use vulkano::{ buffer::{BufferUsage, CpuAccessibleBuffer}, @@ -43,6 +42,30 @@ mod vs { include: ["./libs/render"], src: " #version 450 + #include + #include + + // Scene info + layout(push_constant) uniform PushConstantData { + mat4 view; + mat4 projection; + } pc; + + const uint MAX_XFORM_ID = 32; + + // Per shape input + layout(set = 3, binding = 0) buffer ChunkTransforms { + float data[]; + } shape_transforms; + layout(set = 3, binding = 1) buffer ChunkFlags { + uint data[]; + } shape_flags; + layout(set = 3, binding = 2) buffer ChunkXforms { + float data[]; + } shape_xforms; + layout(set = 3, binding = 3) buffer ChunkXformOffsets { + uint data[]; + } shape_xform_offsets; // Per Vertex input layout(location = 0) in vec3 position; @@ -52,51 +75,40 @@ mod vs { layout(location = 4) in uint flags1; layout(location = 5) in uint xform_id; - // Per shape input - layout(set = 4, binding = 0) buffer ChunkFlags { - uint flag_data[]; - } flags; - layout(set = 4, binding = 1) buffer ChunkTransforms { - float xform_data[]; - } xforms; - - layout(push_constant) uniform PushConstantData { - mat4 view; - mat4 projection; - } pc; - - #include - #include - layout(location = 0) smooth out vec4 v_color; layout(location = 1) smooth out vec2 v_tex_coord; layout(location = 2) flat out uint f_flags0; layout(location = 3) flat out uint f_flags1; void main() { - uint shape_base_flag = 0; - uint shape_base_xform = 0; - if (gl_InstanceIndex >= 10) { - shape_base_flag = 2; - shape_base_xform = 24; - } - - float xform[6] = { - xforms.xform_data[shape_base_xform + 6 * xform_id + 0], - xforms.xform_data[shape_base_xform + 6 * xform_id + 1], - xforms.xform_data[shape_base_xform + 6 * xform_id + 2], - xforms.xform_data[shape_base_xform + 6 * xform_id + 3], - xforms.xform_data[shape_base_xform + 6 * xform_id + 4], - xforms.xform_data[shape_base_xform + 6 * xform_id + 5], + uint base_transform = gl_InstanceIndex * 6; + uint base_flag = gl_InstanceIndex * 2; + uint base_xform = shape_xform_offsets.data[gl_InstanceIndex]; + + float transform[6] = { + shape_transforms.data[base_transform + 0], + shape_transforms.data[base_transform + 1], + shape_transforms.data[base_transform + 2], + shape_transforms.data[base_transform + 3], + shape_transforms.data[base_transform + 4], + shape_transforms.data[base_transform + 5] }; + float xform[6] = {0, 0, 0, 0, 0, 0}; + if (xform_id < MAX_XFORM_ID) { + xform[0] = shape_xforms.data[base_xform + 6 * xform_id + 0]; + xform[1] = shape_xforms.data[base_xform + 6 * xform_id + 1]; + xform[2] = shape_xforms.data[base_xform + 6 * xform_id + 2]; + xform[3] = shape_xforms.data[base_xform + 6 * xform_id + 3]; + xform[4] = shape_xforms.data[base_xform + 6 * xform_id + 4]; + xform[5] = shape_xforms.data[base_xform + 6 * xform_id + 5]; + } - gl_Position = pc.projection * pc.view * matrix_for_xform(xform) * vec4(position, 1.0); - gl_Position.x += float(gl_InstanceIndex) * 10.0; + gl_Position = pc.projection * pc.view * matrix_for_xform(transform) * matrix_for_xform(xform) * vec4(position, 1.0); v_color = color; v_tex_coord = tex_coord; - f_flags0 = flags0 & flags.flag_data[shape_base_flag + 0]; - f_flags1 = flags1 & flags.flag_data[shape_base_flag + 1]; + f_flags0 = flags0 & shape_flags.data[base_flag + 0]; + f_flags1 = flags1 & shape_flags.data[base_flag + 1]; }" } } @@ -117,7 +129,11 @@ mod fs { layout(location = 0) out vec4 f_color; - layout(set = 3, binding = 0) uniform sampler2DArray mega_atlas; + layout(set = 4, binding = 0) uniform sampler2DArray mega_atlas; + //layout(set = 5, binding = 1) uniform sampler2DArray nose_art; NOSE\\d\\d.PIC + //layout(set = 5, binding = 2) uniform sampler2DArray left_tail_art; LEFT\\d\\d.PIC + //layout(set = 5, binding = 3) uniform sampler2DArray right_tail_art; RIGHT\\d\\d.PIC + //layout(set = 5, binding = 4) uniform sampler2DArray round_art; ROUND\\d\\d.PIC void main() { if ((f_flags0 & 0xFFFFFFFE) == 0 && f_flags1 == 0) { @@ -199,7 +215,6 @@ impl vs::ty::PushConstantData { fn main() -> Fallible<()> { let mut window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; - window.hide_cursor()?; let bindings = InputBindings::new("base") .bind("exit", "Escape")? .bind("exit", "q")?; @@ -235,19 +250,30 @@ fn main() -> Fallible<()> { let lib = omni.library("FA"); let palette = Palette::from_bytes(&lib.load("PALETTE.PAL")?)?; - let mut open_chunk = OpenChunk::new(&window)?; - open_chunk.upload_shape("F8.SH", DrawSelection::NormalModel, &palette, &lib, &window)?; - open_chunk.upload_shape( + let mut chunk_man = ShapeChunkManager::new(pipeline.clone(), &window)?; + let (_f8_id, _) = + chunk_man.upload_shape("F8.SH", DrawSelection::NormalModel, &palette, &lib, &window)?; + let (f18_id, _) = chunk_man.upload_shape( "F18.SH", DrawSelection::NormalModel, &palette, &lib, &window, )?; - let (chunk, future) = ClosedChunk::new(open_chunk, pipeline.clone(), &window)?; + let future = chunk_man.finish(&window)?; future.then_signal_fence_and_flush()?.wait(None)?; - let f18_part = chunk.part_for("F18.SH")?; + let chunk = chunk_man.chunk(f18_id)?; + let f18_part = chunk_man.part(f18_id)?; + let _f18_widgets = f18_part.widgets().read().unwrap().clone(); + + // Upload transforms + let transforms = vec![0f32, 0f32, 0f32, 0f32, 0f32, 0f32]; + let transforms_buffer: Arc> = CpuAccessibleBuffer::from_iter( + window.device(), + BufferUsage::all(), + transforms.iter().cloned(), + )?; // Upload flags let mut draw_state: DrawState = Default::default(); @@ -264,7 +290,7 @@ fn main() -> Fallible<()> { flags_arr.iter().cloned(), )?; - // Upload transforms + // Upload xforms let now = Instant::now(); let xforms_len = f18_part.widgets().read().unwrap().num_transformer_floats(); let mut xforms = Vec::with_capacity(xforms_len); @@ -273,7 +299,7 @@ fn main() -> Fallible<()> { &draw_state, draw_state.time_origin(), &now, - &mut xforms[0..], + &mut xforms, )?; let xforms_buffer = CpuAccessibleBuffer::from_iter( window.device(), @@ -281,10 +307,20 @@ fn main() -> Fallible<()> { xforms.iter().cloned(), )?; + // Upload xform buffer offsets + let xform_offsets = vec![0]; + let xform_offsets_buffer = CpuAccessibleBuffer::from_iter( + window.device(), + BufferUsage::all(), + xform_offsets.iter().cloned(), + )?; + let shape_descriptor_set = Arc::new( PersistentDescriptorSet::start(pipeline.clone(), GlobalSets::ShapeBuffers.into()) + .add_buffer(transforms_buffer)? .add_buffer(flags_buffer)? .add_buffer(xforms_buffer)? + .add_buffer(xform_offsets_buffer)? .build()?, ); @@ -301,6 +337,7 @@ fn main() -> Fallible<()> { let empty0 = GraphicsWindow::empty_descriptor_set(pipeline.clone(), 0)?; let empty1 = GraphicsWindow::empty_descriptor_set(pipeline.clone(), 1)?; let empty2 = GraphicsWindow::empty_descriptor_set(pipeline.clone(), 2)?; + window.hide_cursor()?; loop { for command in input.poll(&mut window.events_loop) { match command.name.as_str() { @@ -314,7 +351,7 @@ fn main() -> Fallible<()> { command.displacement()?.1 / 4.0, ), "window-cursor-move" => {} - _ => trace!("unhandled command: {}", command.name), + _ => println!("unhandled command: {}", command.name), } } window.center_cursor()?; @@ -348,8 +385,8 @@ fn main() -> Fallible<()> { empty0.clone(), empty1.clone(), empty2.clone(), - chunk.atlas_descriptor_set_ref(), shape_descriptor_set.clone(), + chunk.atlas_descriptor_set_ref(), ), push_constants, )?; diff --git a/libs/render/buffer/shape_chunk/src/chunk.rs b/libs/render/buffer/shape_chunk/src/chunk.rs index 19b1d5ec..6f52fedd 100644 --- a/libs/render/buffer/shape_chunk/src/chunk.rs +++ b/libs/render/buffer/shape_chunk/src/chunk.rs @@ -69,6 +69,22 @@ pub enum Chunk { Closed(ClosedChunk), } +impl Chunk { + pub fn is_open(&self) -> bool { + match self { + Self::Open(_) => true, + _ => false, + } + } + + pub fn as_open_chunk_mut(&mut self) -> &mut OpenChunk { + match self { + Self::Open(chunk) => chunk, + _ => panic!("not an open chunk"), + } + } +} + // Where a shape lives in a chunk. pub struct ChunkPart { vertex_start: usize, diff --git a/libs/render/buffer/shape_chunk/src/chunk_manager.rs b/libs/render/buffer/shape_chunk/src/chunk_manager.rs index c7c3954d..f9a2a853 100644 --- a/libs/render/buffer/shape_chunk/src/chunk_manager.rs +++ b/libs/render/buffer/shape_chunk/src/chunk_manager.rs @@ -85,4 +85,12 @@ impl ShapeChunkManager { .ok_or_else(|| err_msg("no chunk for associated shape id"))?; self.closed_chunks[chunk_id].part(shape_id) } + + pub fn chunk(&self, shape_id: ShapeId) -> Fallible<&ClosedChunk> { + let chunk_id = self + .shape_to_chunk_map + .get(&shape_id) + .ok_or_else(|| err_msg("no chunk for associated shape id"))?; + Ok(&self.closed_chunks[chunk_id]) + } } diff --git a/libs/render/buffer/shape_chunk/src/upload.rs b/libs/render/buffer/shape_chunk/src/upload.rs index 1c067b59..61f33fad 100644 --- a/libs/render/buffer/shape_chunk/src/upload.rs +++ b/libs/render/buffer/shape_chunk/src/upload.rs @@ -509,6 +509,7 @@ pub struct ShapeWidgets { // Self contained vm/instructions for how to set up each required transform // to draw this shape buffer. + num_transformer_floats: usize, transformers: Vec, // Draw properties based on what's in the shape file. @@ -518,6 +519,7 @@ pub struct ShapeWidgets { impl ShapeWidgets { pub fn new(name: &str, transformers: Vec, errata: ShapeErrata) -> Self { Self { + num_transformer_floats: transformers.len() * 6, shape_name: name.to_owned(), transformers, errata, @@ -554,7 +556,7 @@ impl ShapeWidgets { } pub fn num_transformer_floats(&self) -> usize { - self.transformers.len() * 6 + self.num_transformer_floats } } @@ -584,7 +586,7 @@ impl ShapeUploader { .unwrap_or_else(|| BufferProps { context: "Static".to_owned(), flags: VertexFlags::STATIC, - xform_id: 0, + xform_id: std::u32::MAX, }); for v in vert_buf.vertices() { let v0 = Vector3::new(f32::from(v[0]), f32::from(-v[2]), -f32::from(v[1])); @@ -617,15 +619,15 @@ impl ShapeUploader { override_flags: Option, chunk: &mut OpenChunk, ) -> Fallible<()> { - // Load all vertices in this facet into the vertex upload - // buffer, copying in the color and texture coords for each - // face. The layout appears to be for triangle fans. + // Load all vertices in this facet into the vertex upload buffer, copying in the color and + // texture coords for each face. The layout appears to be for triangle fans. for i in 2..facet.indices.len() { - // Given that most facets are very short strips, and we - // need to copy the vertices anyway, it's not *that* - // must worse to just copy the tris over instead of - // trying to get strips or fans working. - // TODO: use triangle fans directly + // Unlike modern rendering systems, FA had all of the non-position properties on the + // face instead of the vertex. The upshot is that we end up needing to split most + // vertices per face in order to move the face properties to the vertices. We could + // take the time to reverse these into fans or strips, deduping vertices, but since the + // models were not designed with that in mind, we don't end up saving as much space as + // we'd like. Instead, we just upload three vertices per triangle and call it good. let js = [0, i - 1, i]; for j in &js { let index = facet.indices[*j] as usize; diff --git a/libs/render/draw/legacy_shape/src/upload.rs b/libs/render/draw/legacy_shape/src/upload.rs index 7aada4ed..650b313a 100644 --- a/libs/render/draw/legacy_shape/src/upload.rs +++ b/libs/render/draw/legacy_shape/src/upload.rs @@ -31,7 +31,7 @@ use sh::{Facet, FacetFlags, Instr, RawShape, VertexBuf, X86Code, X86Trampoline, use std::{ cell::RefCell, collections::{HashMap, HashSet}, - sync::Arc, + sync::{Arc, RwLock}, time::Instant, }; use vulkano::{ @@ -434,7 +434,7 @@ pub struct Transformer { // Note that mutability is an implementation detail here. We could construct // a new one for each frame, for each instance, for each transform, but that // would get expensive fast and we shouldn't actually be changing the state. - vm: RefCell, + vm: Arc>, code_offset: u32, data_offset: u32, inputs: Vec, @@ -452,7 +452,7 @@ impl Transformer { d * std::f32::consts::PI / 8192f32 } - let mut vm = self.vm.borrow_mut(); + let mut vm = self.vm.write().unwrap(); for input in &self.inputs { let (loc, value) = match input { TransformInput::CurrentTicks(loc) => { @@ -987,7 +987,7 @@ impl ShapeUploader { transformers.push(Transformer { xform_id, - vm: RefCell::new(interp), + vm: Arc::new(RwLock::new(interp)), code_offset: x86.code_offset(SHAPE_LOAD_BASE), data_offset: SHAPE_LOAD_BASE + xform.at_offset() as u32 + 2u32, inputs, diff --git a/libs/render/draw/shape_instance/Cargo.toml b/libs/render/draw/shape_instance/Cargo.toml new file mode 100644 index 00000000..ae654982 --- /dev/null +++ b/libs/render/draw/shape_instance/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "shape_instance" +version = "0.1.0" +authors = ["Terrence Cole "] +edition = "2018" + +[lib] +doctest = false + +[dependencies] +failure = "^ 0.1.2" +nalgebra = "^ 0.18" +rayon = "^ 1.2" +specs = "^ 0.15" +vulkano = "^ 0.14" +vulkano-shaders = "^ 0.14" +camera = { path = "../../../render/common/camera" } +global_layout = { path = "../../../render/buffer/global_layout" } +lib = { path = "../../../lib" } +omnilib = { path = "../../../omnilib" } +pal = { path = "../../../pal" } +shape_chunk = { path = "../../../render/buffer/shape_chunk" } +world = { path = "../../../world" } +window = { path = "../../../window" } + +[dev-dependencies] +rand = "^ 0.7" +camera = { path = "../../../render/common/camera" } +input = { path = "../../../input" } diff --git a/libs/render/draw/shape_instance/examples/demo-instances.rs b/libs/render/draw/shape_instance/examples/demo-instances.rs new file mode 100644 index 00000000..0253c020 --- /dev/null +++ b/libs/render/draw/shape_instance/examples/demo-instances.rs @@ -0,0 +1,127 @@ +// This file is part of OpenFA. +// +// OpenFA is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// OpenFA is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with OpenFA. If not, see . +use camera::ArcBallCamera; +use failure::Fallible; +use input::{InputBindings, InputSystem}; +use nalgebra::Point3; +use omnilib::OmniLib; +use shape_chunk::DrawSelection; +use shape_instance::{ShapeRenderSystem, ShapeRenderer}; +use specs::DispatcherBuilder; +use std::sync::Arc; +use vulkano::{command_buffer::AutoCommandBufferBuilder, sync::GpuFuture}; +use window::{GraphicsConfigBuilder, GraphicsWindow}; +use world::World; + +fn main() -> Fallible<()> { + let mut window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; + let bindings = InputBindings::new("base") + .bind("exit", "Escape")? + .bind("exit", "q")?; + let mut input = InputSystem::new(&[&bindings]); + let omni = OmniLib::new_for_test_in_games(&["FA"])?; + let lib = omni.library("FA"); + let world = Arc::new(World::new(lib)?); + + let mut shape_renderer = ShapeRenderer::new(world.clone(), &window)?; + let (f8_id, fut1) = + shape_renderer.upload_shape("F8.SH", DrawSelection::NormalModel, &window)?; + let (f18_id, fut2) = + shape_renderer.upload_shape("F18.SH", DrawSelection::NormalModel, &window)?; + //let (soldier_id, fut2) = + // shape_renderer.upload_shape("SOLDIER.SH", DrawSelection::NormalModel, &window)?; + let (_windmill_id, fut3) = + shape_renderer.upload_shape("WNDMLL.SH", DrawSelection::NormalModel, &window)?; + let future = shape_renderer.ensure_uploaded(&window)?; + + assert!(fut1.is_none()); + assert!(fut2.is_none()); + assert!(fut3.is_none()); + future.then_signal_fence_and_flush()?.wait(None)?; + + let _f18_ent1 = world.create_flyer( + f18_id, + Point3::new(0f64, 0f64, 0f64), + shape_renderer.chunks().part(f18_id), + )?; + let _f18_ent2 = world.create_flyer( + f8_id, + Point3::new(80f64, 0f64, 120f64), + shape_renderer.chunks().part(f8_id), + )?; + + let mut camera = ArcBallCamera::new(window.aspect_ratio_f64()?, 0.1, 3.4e+38); + camera.set_distance(120.0); + camera.on_mousebutton_down(1); + + window.hide_cursor()?; + loop { + for command in input.poll(&mut window.events_loop) { + match command.name.as_str() { + "window-resize" => { + window.note_resize(); + camera.set_aspect_ratio(window.aspect_ratio_f64()?); + } + "window-close" | "window-destroy" | "exit" => return Ok(()), + "mouse-move" => camera.on_mousemove( + command.displacement()?.0 / 4.0, + command.displacement()?.1 / 4.0, + ), + "window-cursor-move" => {} + _ => println!("unhandled command: {}", command.name), + } + } + window.center_cursor()?; + + // Upload entities' current state to the renderer. + { + let shape_render_system = ShapeRenderSystem::new(&mut shape_renderer); + let mut shape_instance_updater = DispatcherBuilder::new() + .with(shape_render_system, "", &[]) + .build(); + world.run(&mut shape_instance_updater); + } + + { + let frame = window.begin_frame()?; + if !frame.is_valid() { + continue; + } + + let mut cbb = AutoCommandBufferBuilder::primary_one_time_submit( + window.device(), + window.queue().family(), + )?; + + cbb = shape_renderer.update_buffers(cbb)?; + + cbb = cbb.begin_render_pass( + frame.framebuffer(&window), + false, + vec![[0f32, 0f32, 1f32, 1f32].into(), 0f32.into()], + )?; + + cbb = shape_renderer.render(cbb, &camera, &window)?; + + cbb = cbb.end_render_pass()?; + + let cb = cbb.build()?; + + frame.submit(cb, &mut window)?; + } + + shape_renderer.maintain(); + } +} diff --git a/libs/render/draw/shape_instance/examples/demo-life.rs b/libs/render/draw/shape_instance/examples/demo-life.rs new file mode 100644 index 00000000..48a4657a --- /dev/null +++ b/libs/render/draw/shape_instance/examples/demo-life.rs @@ -0,0 +1,208 @@ +// This file is part of OpenFA. +// +// OpenFA is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// OpenFA is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with OpenFA. If not, see . +use camera::ArcBallCamera; +use failure::Fallible; +use input::{InputBindings, InputSystem}; +use nalgebra::Point3; +use omnilib::OmniLib; +use rand::prelude::*; +use shape_chunk::DrawSelection; +use shape_instance::{ + ShapeRenderSystem, ShapeRenderer, ShapeUpdateFlagSystem, ShapeUpdateTransformSystem, +}; +use specs::{world::Index as EntityId, DispatcherBuilder}; +use std::{sync::Arc, time::Instant}; +use vulkano::{command_buffer::AutoCommandBufferBuilder, sync::GpuFuture}; +use window::{GraphicsConfigBuilder, GraphicsWindow}; +use world::World; + +fn main() -> Fallible<()> { + let mut window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; + let bindings = InputBindings::new("base") + .bind("exit", "Escape")? + .bind("exit", "q")?; + let mut input = InputSystem::new(&[&bindings]); + let omni = OmniLib::new_for_test_in_games(&["FA"])?; + let lib = omni.library("FA"); + let world = Arc::new(World::new(lib)?); + + let mut shape_renderer = ShapeRenderer::new(world.clone(), &window)?; + let (_f18_id, fut1) = + shape_renderer.upload_shape("F18.SH", DrawSelection::NormalModel, &window)?; + let (shape_id, fut1) = + shape_renderer.upload_shape("WNDMLL.SH", DrawSelection::NormalModel, &window)?; + let future = shape_renderer.ensure_uploaded(&window)?; + + assert!(fut1.is_none()); + future.then_signal_fence_and_flush()?.wait(None)?; + + const D: f64 = 120f64; + let mut rng = rand::thread_rng(); + const WIDTH: usize = 100; + const HEIGHT: usize = 100; + let mut life = [[None; WIDTH]; HEIGHT]; + for i in 0..WIDTH { + for j in 0..HEIGHT { + life[i][j] = if rng.gen::() > 0.5 { + let shape_part = shape_renderer.chunks().part(shape_id); + Some(world.create_flyer( + shape_id, + Point3::new(-50f64 * D + D * i as f64, 0f64, -50f64 * D + D * j as f64), + shape_part, + )?) + } else { + None + }; + } + } + + let mut camera = ArcBallCamera::new(window.aspect_ratio_f64()?, 0.1, 3.4e+38); + camera.set_distance(12000.0); + camera.on_mousebutton_down(1); + + let mut cnt = 0; + window.hide_cursor()?; + loop { + let frame_start = Instant::now(); + + cnt += 1; + for command in input.poll(&mut window.events_loop) { + match command.name.as_str() { + "window-resize" => { + window.note_resize(); + camera.set_aspect_ratio(window.aspect_ratio_f64()?); + } + "window-close" | "window-destroy" | "exit" => return Ok(()), + "mouse-wheel" => { + camera.on_mousescroll(command.displacement()?.0, command.displacement()?.1) + } + "mouse-move" => camera.on_mousemove( + command.displacement()?.0 / 4.0, + command.displacement()?.1 / 4.0, + ), + "window-cursor-move" => {} + _ => println!("unhandled command: {}", command.name), + } + } + window.center_cursor()?; + + //if cnt % 10 == 0 { + if false { + let start = std::time::Instant::now(); + let mut next = [[0u8; WIDTH]; HEIGHT]; + for i in 0..WIDTH { + for j in 0..HEIGHT { + let mut neighbors = 0; + if i > 0 && j > 0 && life[i - 1][j - 1].is_some() { + neighbors += 1; + } + if i > 0 && life[i - 1][j].is_some() { + neighbors += 1; + } + if i > 0 && j < HEIGHT - 1 && life[i - 1][j + 1].is_some() { + neighbors += 1; + } + if j > 0 && life[i][j - 1].is_some() { + neighbors += 1; + } + if j < HEIGHT - 1 && life[i][j + 1].is_some() { + neighbors += 1; + } + if i < WIDTH - 1 && j > 0 && life[i + 1][j - 1].is_some() { + neighbors += 1; + } + if i < WIDTH - 1 && life[i + 1][j].is_some() { + neighbors += 1; + } + if i < WIDTH - 1 && j < HEIGHT - 1 && life[i + 1][j + 1].is_some() { + neighbors += 1; + } + next[i][j] = neighbors as u8; + } + } + for i in 0..WIDTH { + for j in 0..HEIGHT { + let neighbors = next[i][j]; + if neighbors < 2 || neighbors > 3 { + if let Some(ent) = life[i][j] { + world.destroy_entity(ent)?; + life[i][j] = None; + } + } else if neighbors == 3 && life[i][j].is_none() { + let shape_part = shape_renderer.chunks().part(shape_id); + life[i][j] = Some(world.create_flyer( + shape_id, + Point3::new(-50f64 * D + D * i as f64, 0f64, -50f64 * D + D * j as f64), + shape_part, + )?); + } + } + } + println!("TICK: @{} => {:?}", cnt, start.elapsed()); + } + + // Upload entities' current state to the renderer. + let dup = Instant::now(); + { + //let shape_render_system = ShapeRenderSystem::new(&mut shape_renderer); + //let mut shape_instance_updater = DispatcherBuilder::new() + // .with(shape_render_system, "", &[]) + // .build(); + //world.run(&mut shape_instance_updater); + let mut disp = DispatcherBuilder::new() + .with(ShapeUpdateTransformSystem, "transform", &[]) + .with(ShapeUpdateFlagSystem, "flag", &[]) + .build(); + world.run(&mut disp); + } + println!("DUP: {:?}", dup.elapsed()); + + { + let frame = window.begin_frame()?; + if !frame.is_valid() { + continue; + } + + let mut cbb = AutoCommandBufferBuilder::primary_one_time_submit( + window.device(), + window.queue().family(), + )?; + + let update = Instant::now(); + cbb = shape_renderer.update_buffers(cbb)?; + println!("UPDATE: {:?}", update.elapsed()); + + cbb = cbb.begin_render_pass( + frame.framebuffer(&window), + false, + vec![[0f32, 0f32, 1f32, 1f32].into(), 0f32.into()], + )?; + + cbb = shape_renderer.render(cbb, &camera, &window)?; + + cbb = cbb.end_render_pass()?; + + let cb = cbb.build()?; + + frame.submit(cb, &mut window)?; + } + + let maint = Instant::now(); + shape_renderer.maintain(); + println!("MAINT: {:?}", maint.elapsed()); + + println!("FRAME: {:?}", frame_start.elapsed()); + } +} diff --git a/libs/render/draw/shape_instance/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs new file mode 100644 index 00000000..31ab3062 --- /dev/null +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -0,0 +1,906 @@ +// This file is part of OpenFA. +// +// OpenFA is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// OpenFA is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with OpenFA. If not, see . +use camera::CameraAbstract; +use failure::Fallible; +use global_layout::GlobalSets; +use nalgebra::Matrix4; +use rayon::iter::ParallelIterator; +use shape_chunk::{ChunkIndex, ClosedChunk, DrawSelection, ShapeChunkManager, ShapeId, Vertex}; +use specs::{world::Index as EntityId, Entities, Join, ParJoin, ReadStorage, System, WriteStorage}; +use std::{collections::HashMap, sync::Arc, time::Instant}; +use vulkano::buffer::CpuAccessibleBuffer; +use vulkano::{ + buffer::{BufferAccess, BufferUsage, CpuBufferPool, DeviceLocalBuffer}, + command_buffer::{AutoCommandBufferBuilder, DrawIndirectCommand}, + descriptor::descriptor_set::{DescriptorSet, PersistentDescriptorSet}, + device::Device, + framebuffer::Subpass, + pipeline::{ + depth_stencil::{Compare, DepthBounds, DepthStencil}, + GraphicsPipeline, GraphicsPipelineAbstract, + }, + sync::GpuFuture, +}; +use window::GraphicsWindow; +use world::{ + component::{ShapeMesh, ShapeMeshFlagBuffer, ShapeMeshTransformBuffer, Transform}, + World, +}; + +mod vs { + use vulkano_shaders::shader; + + shader! { + ty: "vertex", + include: ["./libs/render"], + src: " + #version 450 + #include + #include + + // Scene info + layout(push_constant) uniform PushConstantData { + mat4 view; + mat4 projection; + } pc; + + layout(set = 0, binding = 0) buffer GlobalData { + int dummy; + } globals; + + // Per shape input + const uint MAX_XFORM_ID = 32; + layout(set = 3, binding = 0) buffer ChunkBaseTransforms { + float data[]; + } shape_transforms; + layout(set = 3, binding = 1) buffer ChunkFlags { + uint data[]; + } shape_flags; + layout(set = 3, binding = 2) buffer ChunkXforms { + float data[]; + } shape_xforms; + layout(set = 3, binding = 3) buffer ChunkXformOffsets { + uint data[]; + } shape_xform_offsets; + + // Per Vertex input + layout(location = 0) in vec3 position; + layout(location = 1) in vec4 color; + layout(location = 2) in vec2 tex_coord; + layout(location = 3) in uint flags0; + layout(location = 4) in uint flags1; + layout(location = 5) in uint xform_id; + + layout(location = 0) smooth out vec4 v_color; + layout(location = 1) smooth out vec2 v_tex_coord; + layout(location = 2) flat out uint f_flags0; + layout(location = 3) flat out uint f_flags1; + + void main() { + uint base_transform = gl_InstanceIndex * 6; + uint base_flag = gl_InstanceIndex * 2; + uint base_xform = shape_xform_offsets.data[gl_InstanceIndex]; + + float transform[6] = { + shape_transforms.data[base_transform + 0], + shape_transforms.data[base_transform + 1], + shape_transforms.data[base_transform + 2], + shape_transforms.data[base_transform + 3], + shape_transforms.data[base_transform + 4], + shape_transforms.data[base_transform + 5] + }; + float xform[6] = {0, 0, 0, 0, 0, 0}; + if (xform_id < MAX_XFORM_ID) { + xform[0] = shape_xforms.data[base_xform + 6 * xform_id + 0]; + xform[1] = shape_xforms.data[base_xform + 6 * xform_id + 1]; + xform[2] = shape_xforms.data[base_xform + 6 * xform_id + 2]; + xform[3] = shape_xforms.data[base_xform + 6 * xform_id + 3]; + xform[4] = shape_xforms.data[base_xform + 6 * xform_id + 4]; + xform[5] = shape_xforms.data[base_xform + 6 * xform_id + 5]; + } + + gl_Position = pc.projection * + pc.view * + matrix_for_xform(transform) * + matrix_for_xform(xform) * + vec4(position, 1.0); + v_color = color; + v_tex_coord = tex_coord; + + f_flags0 = flags0 & shape_flags.data[base_flag + 0]; + f_flags1 = flags1 & shape_flags.data[base_flag + 1]; + }" + } +} + +mod fs { + use vulkano_shaders::shader; + + shader! { + ty: "fragment", + include: ["./libs/render"], + src: " + #version 450 + + layout(location = 0) smooth in vec4 v_color; + layout(location = 1) smooth in vec2 v_tex_coord; + layout(location = 2) flat in uint f_flags0; + layout(location = 3) flat in uint f_flags1; + + layout(location = 0) out vec4 f_color; + + layout(set = 4, binding = 0) uniform sampler2DArray mega_atlas; + //layout(set = 5, binding = 1) uniform sampler2DArray nose_art; NOSE\\d\\d.PIC + //layout(set = 5, binding = 2) uniform sampler2DArray left_tail_art; LEFT\\d\\d.PIC + //layout(set = 5, binding = 3) uniform sampler2DArray right_tail_art; RIGHT\\d\\d.PIC + //layout(set = 5, binding = 4) uniform sampler2DArray round_art; ROUND\\d\\d.PIC + + void main() { + if ((f_flags0 & 0xFFFFFFFE) == 0 && f_flags1 == 0) { + discard; + } else if (v_tex_coord.x == 0.0) { + f_color = v_color; + } else { + vec4 tex_color = texture(mega_atlas, vec3(v_tex_coord, 0)); + + if ((f_flags0 & 1) == 1) { + f_color = vec4((1.0 - tex_color[3]) * v_color.xyz + tex_color[3] * tex_color.xyz, 1.0); + } else { + if (tex_color.a < 0.5) + discard; + else + f_color = tex_color; + } + } + }" + } +} + +impl vs::ty::PushConstantData { + fn new() -> Self { + Self { + view: [ + [0.0f32, 0.0f32, 0.0f32, 0.0f32], + [0.0f32, 0.0f32, 0.0f32, 0.0f32], + [0.0f32, 0.0f32, 0.0f32, 0.0f32], + [0.0f32, 0.0f32, 0.0f32, 0.0f32], + ], + projection: [ + [0.0f32, 0.0f32, 0.0f32, 0.0f32], + [0.0f32, 0.0f32, 0.0f32, 0.0f32], + [0.0f32, 0.0f32, 0.0f32, 0.0f32], + [0.0f32, 0.0f32, 0.0f32, 0.0f32], + ], + } + } + + fn set_view(&mut self, mat: &Matrix4) { + self.view[0][0] = mat[0]; + self.view[0][1] = mat[1]; + self.view[0][2] = mat[2]; + self.view[0][3] = mat[3]; + self.view[1][0] = mat[4]; + self.view[1][1] = mat[5]; + self.view[1][2] = mat[6]; + self.view[1][3] = mat[7]; + self.view[2][0] = mat[8]; + self.view[2][1] = mat[9]; + self.view[2][2] = mat[10]; + self.view[2][3] = mat[11]; + self.view[3][0] = mat[12]; + self.view[3][1] = mat[13]; + self.view[3][2] = mat[14]; + self.view[3][3] = mat[15]; + } + + fn set_projection(&mut self, mat: &Matrix4) { + self.projection[0][0] = mat[0]; + self.projection[0][1] = mat[1]; + self.projection[0][2] = mat[2]; + self.projection[0][3] = mat[3]; + self.projection[1][0] = mat[4]; + self.projection[1][1] = mat[5]; + self.projection[1][2] = mat[6]; + self.projection[1][3] = mat[7]; + self.projection[2][0] = mat[8]; + self.projection[2][1] = mat[9]; + self.projection[2][2] = mat[10]; + self.projection[2][3] = mat[11]; + self.projection[3][0] = mat[12]; + self.projection[3][1] = mat[13]; + self.projection[3][2] = mat[14]; + self.projection[3][3] = mat[15]; + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct BlockIndex(usize); + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct SlotIndex(usize); + +const BLOCK_SIZE: usize = 128; + +// Fixed reservation blocks for upload of a number of entities. Unfortunately, because of +// xforms, we don't know exactly how many instances will fit in any given block. +pub struct DynamicInstanceBlock { + // Weak reference to the associated chunk in the Manager. + chunk_index: ChunkIndex, + //chunk_type: ChunkType, + + // Map from the entity to the stored offset and from the offset to the entity. + slot_reservations: [Option; BLOCK_SIZE], + entity_to_slot_map: HashMap, + reservation_offset: usize, // bump head + mark_buffer: [bool; BLOCK_SIZE], // GC marked set + + descriptor_set: Arc, + + // FIXME / BUG: most of these will be passed in; it crashes vulkano if we create empty sets + // FIXME / BUG: before our real sets, however, so we have to push these down for now. + pds0: Arc, + pds1: Arc, + pds2: Arc, + + // Buffers for all instances stored in this instance set. One command per unique entity. + // 16 bytes per entity; index unnecessary for draw + command_buffer_scratch: [DrawIndirectCommand; BLOCK_SIZE], + command_upload_set: Vec, + command_buffer_pool: CpuBufferPool, + command_buffer: Arc>, + + // Base position and orientation in xyz+euler angles stored as 6 adjacent floats. + // 24 bytes per entity; buffer index inferable from drawing index + transform_buffer_scratch: [[f32; 6]; BLOCK_SIZE], + transform_buffer_pool: CpuBufferPool<[f32; 6]>, + transform_buffer: Arc>, + + // 2 32bit flags words for each entity. + // 8 bytes per entity; buffer index inferable from drawing index + flag_buffer_scratch: [[u32; 2]; BLOCK_SIZE], + flag_buffer_pool: CpuBufferPool<[u32; 2]>, + flag_buffer: Arc>, + + // 4 bytes per entity; can infer position from index + xform_index_buffer_scratch: [u32; BLOCK_SIZE], + xform_index_buffer_pool: CpuBufferPool, + xform_index_buffer: Arc>, + + // 0 to 14 position/orientation [f32; 6], depending on the shape. + // assume 96 bytes per entity if we're talking about planes + // cannot infer position, so needs an index buffer + xform_buffer_scratch: [[f32; 6]; 14 * BLOCK_SIZE], + xform_buffer_pool: CpuBufferPool<[f32; 6]>, + xform_buffer: Arc>, +} + +impl DynamicInstanceBlock { + fn new( + chunk_index: ChunkIndex, + pipeline: Arc, + base_descriptors: &[Arc], + command_buffer_pool: CpuBufferPool, + transform_buffer_pool: CpuBufferPool<[f32; 6]>, + flag_buffer_pool: CpuBufferPool<[u32; 2]>, + xform_index_buffer_pool: CpuBufferPool, + xform_buffer_pool: CpuBufferPool<[f32; 6]>, + device: Arc, + ) -> Fallible { + let command_buffer = DeviceLocalBuffer::array( + device.clone(), + BLOCK_SIZE, + BufferUsage::all(), + device.active_queue_families(), + )?; + let transform_buffer = DeviceLocalBuffer::array( + device.clone(), + BLOCK_SIZE, + BufferUsage::all(), + device.active_queue_families(), + )?; + let flag_buffer = DeviceLocalBuffer::array( + device.clone(), + BLOCK_SIZE, + BufferUsage::all(), + device.active_queue_families(), + )?; + let xform_index_buffer = DeviceLocalBuffer::array( + device.clone(), + BLOCK_SIZE, + BufferUsage::all(), + device.active_queue_families(), + )?; + let xform_buffer = DeviceLocalBuffer::array( + device.clone(), + 14 * BLOCK_SIZE, + BufferUsage::all(), + device.active_queue_families(), + )?; + let descriptor_set = Arc::new( + PersistentDescriptorSet::start(pipeline.clone(), GlobalSets::ShapeBuffers.into()) + .add_buffer(transform_buffer.clone())? + .add_buffer(flag_buffer.clone())? + .add_buffer(xform_buffer.clone())? + .add_buffer(xform_index_buffer.clone())? + .build()?, + ); + Ok(Self { + chunk_index, + slot_reservations: [None; BLOCK_SIZE], + entity_to_slot_map: HashMap::new(), + reservation_offset: 0, + mark_buffer: [false; BLOCK_SIZE], + descriptor_set, + pds0: base_descriptors[0].clone(), + pds1: base_descriptors[1].clone(), + pds2: base_descriptors[2].clone(), + command_buffer_scratch: [DrawIndirectCommand { + vertex_count: 0u32, + instance_count: 0u32, + first_vertex: 0u32, + first_instance: 0u32, + }; BLOCK_SIZE], + command_buffer_pool, + command_upload_set: Vec::new(), + command_buffer, + transform_buffer_scratch: [[0f32; 6]; BLOCK_SIZE], + transform_buffer_pool, + transform_buffer, + flag_buffer_scratch: [[0u32; 2]; BLOCK_SIZE], + flag_buffer_pool, + flag_buffer, + xform_index_buffer_scratch: [0u32; BLOCK_SIZE], + xform_index_buffer_pool, + xform_index_buffer, + xform_buffer_scratch: [[0f32; 6]; 14 * BLOCK_SIZE], + xform_buffer_pool, + xform_buffer, + }) + } + + // We expect entities to be added and removed frequently, but not every entity on every frame. + // We take some pains here to make sure we only copy change entities up to the GPU each frame. + fn allocate_entity_slot( + &mut self, + id: EntityId, + chunk_index: ChunkIndex, + mut draw_command: DrawIndirectCommand, + ) -> Option { + if chunk_index != self.chunk_index { + return None; // block not for this chunk + } + + // GC compacts all ids so that allocation can just bump. + if self.reservation_offset >= BLOCK_SIZE { + return None; // block full + } + let slot = SlotIndex(self.reservation_offset); + self.reservation_offset += 1; + self.slot_reservations[slot.0] = Some(id); + self.entity_to_slot_map.insert(id, slot); + + // Update the draw commands and add an upload slot for the new one. + draw_command.first_instance = slot.0 as u32; + self.command_buffer_scratch[slot.0] = draw_command; + self.command_upload_set.push(slot.0); + + Some(slot) + } + + // Lookup the existing slot (may be newly created), return the slot index for direct O(1) + // access to all of the scratch buffers we need to update. Also marks the entity as alive. + fn get_existing_slot(&mut self, id: EntityId) -> SlotIndex { + let slot = self.entity_to_slot_map[&id]; + self.mark_buffer[slot.0] = true; + slot + } + + // Maintain is our GC routine. It must be called at the end of every frame. + fn maintain(&mut self, removed: &mut Vec) -> bool { + fn swap(arr: &mut [T], a: usize, b: usize) + where + T: Copy, + { + let tmp = arr[a]; + arr[a] = arr[b]; + arr[b] = tmp; + } + + let mut head = 0; + let mut tail = self.reservation_offset - 1; + + // Already empty; should not be reachable because we should already be cleaned. + assert!(tail >= head); + + // Since we're using swapping, we need to handle the last element specially. + if head == tail && !self.mark_buffer[head] { + removed.push(self.slot_reservations[0].unwrap()); + return true; + } + + // Walk from the front of the buffer to the end of the buffer. If an entity is not marked, + // swap it with the tail and shrink the buffer. + while head < tail { + if !self.mark_buffer[head] { + let maybe_head_entity = self.slot_reservations[head]; + let maybe_tail_entity = self.slot_reservations[tail]; + if let Some(head_entity) = maybe_head_entity { + removed.push(head_entity); + self.entity_to_slot_map.insert(head_entity, SlotIndex(tail)); + } + if let Some(tail_entity) = maybe_tail_entity { + self.entity_to_slot_map.insert(tail_entity, SlotIndex(head)); + } + swap(&mut self.mark_buffer, head, tail); + swap(&mut self.command_buffer_scratch, head, tail); + swap(&mut self.transform_buffer_scratch, head, tail); + swap(&mut self.flag_buffer_scratch, head, tail); + swap(&mut self.xform_index_buffer_scratch, head, tail); + tail -= 1; + } else { + // Note that this is an `else` because we need to re-check head in case tail + // was itself unmarked. + self.mark_buffer[head] = false; + head += 1; + } + } + + self.reservation_offset = tail + 1; + return false; + } + + fn get_transform_buffer_slot(&mut self, slot_index: SlotIndex) -> &mut [f32; 6] { + &mut self.transform_buffer_scratch[slot_index.0] + } + + fn get_flag_buffer_slot(&mut self, slot_index: SlotIndex) -> &mut [u32; 2] { + &mut self.flag_buffer_scratch[slot_index.0] + } + + fn get_xform_buffer_slot(&mut self, slot_index: SlotIndex) -> &mut [f32] { + &mut self.xform_buffer_scratch[self.xform_index_buffer_scratch[slot_index.0] as usize] + } + + fn update_buffers( + &self, + mut cbb: AutoCommandBufferBuilder, + ) -> Fallible { + let dic = self.command_buffer_scratch.to_vec(); + let command_buffer_upload = self.command_buffer_pool.chunk(dic)?; + cbb = cbb.copy_buffer(command_buffer_upload, self.command_buffer.clone())?; + + let tr = self.transform_buffer_scratch.to_vec(); + let transform_buffer_upload = self.transform_buffer_pool.chunk(tr)?; + cbb = cbb.copy_buffer(transform_buffer_upload, self.transform_buffer.clone())?; + + let fl = self.flag_buffer_scratch.to_vec(); + let flag_buffer_upload = self.flag_buffer_pool.chunk(fl)?; + cbb = cbb.copy_buffer(flag_buffer_upload, self.flag_buffer.clone())?; + + let xfi = self.xform_index_buffer_scratch.to_vec(); + let xform_index_buffer_upload = self.xform_index_buffer_pool.chunk(xfi)?; + cbb = cbb.copy_buffer(xform_index_buffer_upload, self.xform_index_buffer.clone())?; + + Ok(cbb) + } + + pub fn render( + &self, + cbb: AutoCommandBufferBuilder, + pipeline: Arc, + chunk: &ClosedChunk, + camera: &dyn CameraAbstract, + window: &GraphicsWindow, + ) -> Fallible { + let mut push_constants = vs::ty::PushConstantData::new(); + push_constants.set_projection(&camera.projection_matrix()); + push_constants.set_view(&camera.view_matrix()); + + let ib = self.command_buffer.clone(); + Ok(cbb.draw_indirect( + pipeline.clone(), + &window.dynamic_state, + vec![chunk.vertex_buffer()], + ib.into_buffer_slice() + .slice(0..self.reservation_offset) + .unwrap(), + ( + self.pds0.clone(), + self.pds1.clone(), + self.pds2.clone(), + self.descriptor_set.clone(), + chunk.atlas_descriptor_set_ref(), + ), + push_constants, + )?) + } +} + +pub struct ShapeRenderer { + device: Arc, + world: Arc, + pipeline: Arc, + start_time: Instant, + base_descriptors: [Arc; 3], + + // TODO: push mutability down further -- we'd like to parallelize upload, but in practice we + // TODO: can currently push all shapes into chunks in under a second, so it may not matter. + chunks: ShapeChunkManager, + + // All upload blocks. We will do one draw call per instance block each frame. + next_block_index: usize, + blocks: HashMap, + + // Map from the index to the block that it has a reserved upload slot in. + upload_block_map: HashMap, + + // FIXME: We need to move our empty and atmosphere descriptor sets here, but vulkano is bugged + // FIXME: and won't let us create empty sets before our filled sets, so we've pushed these down. + // pds0: Arc, + // pds1: Arc, + // pds2: Arc, + + // Buffer pools are shared by all blocks for maximum re-use. + command_buffer_pool: CpuBufferPool, + transform_buffer_pool: CpuBufferPool<[f32; 6]>, + flag_buffer_pool: CpuBufferPool<[u32; 2]>, + xform_index_buffer_pool: CpuBufferPool, + xform_buffer_pool: CpuBufferPool<[f32; 6]>, // FIXME: hunt down this max somewhere +} + +impl ShapeRenderer { + pub fn new(world: Arc, window: &GraphicsWindow) -> Fallible { + let pipeline = Self::build_pipeline(&window)?; + let chunks = ShapeChunkManager::new(pipeline.clone(), &window)?; + + //let empty0 = GraphicsWindow::empty_descriptor_set(pipeline.clone(), 0)?; + let globals_buffer = + CpuAccessibleBuffer::from_data(window.device(), BufferUsage::all(), 0u32)?; + let global0 = Arc::new( + PersistentDescriptorSet::start(pipeline.clone(), GlobalSets::Global.into()) + .add_buffer(globals_buffer)? + .build()?, + ); + let empty1 = GraphicsWindow::empty_descriptor_set(pipeline.clone(), 1)?; + let empty2 = GraphicsWindow::empty_descriptor_set(pipeline.clone(), 2)?; + + Ok(Self { + device: window.device(), + world, + pipeline, + start_time: Instant::now(), + base_descriptors: [global0, empty1, empty2], + chunks, + next_block_index: 0, + blocks: HashMap::new(), + upload_block_map: HashMap::new(), + command_buffer_pool: CpuBufferPool::new(window.device(), BufferUsage::all()), + transform_buffer_pool: CpuBufferPool::new(window.device(), BufferUsage::all()), + flag_buffer_pool: CpuBufferPool::new(window.device(), BufferUsage::all()), + xform_index_buffer_pool: CpuBufferPool::new(window.device(), BufferUsage::all()), + xform_buffer_pool: CpuBufferPool::new(window.device(), BufferUsage::all()), + }) + } + + fn build_pipeline( + window: &GraphicsWindow, + ) -> Fallible> { + let vert_shader = vs::Shader::load(window.device())?; + let frag_shader = fs::Shader::load(window.device())?; + Ok(Arc::new( + GraphicsPipeline::start() + .vertex_input_single_buffer::() + .vertex_shader(vert_shader.main_entry_point(), ()) + .triangle_list() + .cull_mode_back() + .front_face_clockwise() + .viewports_dynamic_scissors_irrelevant(1) + .fragment_shader(frag_shader.main_entry_point(), ()) + .depth_stencil(DepthStencil { + depth_write: true, + depth_compare: Compare::GreaterOrEqual, + depth_bounds_test: DepthBounds::Disabled, + stencil_front: Default::default(), + stencil_back: Default::default(), + }) + .blend_alpha_blending() + .render_pass( + Subpass::from(window.render_pass(), 0) + .expect("gfx: did not find a render pass"), + ) + .build(window.device())?, + ) as Arc) + } + + pub fn pipeline(&self) -> Arc { + self.pipeline.clone() + } + + pub fn upload_shape( + &mut self, + name: &str, + selection: DrawSelection, + window: &GraphicsWindow, + ) -> Fallible<(ShapeId, Option>)> { + self.chunks.upload_shape( + name, + selection, + self.world.system_palette(), + self.world.library(), + window, + ) + } + + // Close any outstanding chunks and prepare to render. + pub fn ensure_uploaded(&mut self, window: &GraphicsWindow) -> Fallible> { + self.chunks.finish(window) + } + + fn knows_entity(&self, id: EntityId) -> bool { + self.upload_block_map.contains_key(&id) + } + + fn allocate_entity_slot( + &mut self, + id: EntityId, + shape_id: ShapeId, + draw_command: DrawIndirectCommand, + ) -> Fallible<()> { + assert!(!self.knows_entity(id)); + + let chunk = self.chunks.get_chunk_for_shape(shape_id); + + // Note that we do not bother sorting blocks by chunk because we only have to care about + // that mapping when adding new entries. We do a simple chunk_id check to filter out + // non-matching blocks. The assumption is that we will have few enough chunks that a large + // fraction of blocks will be relevant, usually. + for (block_index, block) in self.blocks.iter_mut() { + if let Some(_) = block.allocate_entity_slot(id, chunk.index(), draw_command) { + self.upload_block_map.insert(id, *block_index); + return Ok(()); + } + } + + // No free slots in any blocks. Build a new one. + let next_block_index = BlockIndex(self.next_block_index); + self.next_block_index += 1; + let mut block = DynamicInstanceBlock::new( + chunk.index(), + self.pipeline.clone(), + &self.base_descriptors, + self.command_buffer_pool.clone(), + self.transform_buffer_pool.clone(), + self.flag_buffer_pool.clone(), + self.xform_index_buffer_pool.clone(), + self.xform_buffer_pool.clone(), + self.device.clone(), + )?; + block + .allocate_entity_slot(id, chunk.index(), draw_command) + .unwrap(); + self.blocks.insert(next_block_index, block); + self.upload_block_map.insert(id, next_block_index); + Ok(()) + } + + fn get_entity_block_mut(&mut self, id: EntityId) -> Option<&mut DynamicInstanceBlock> { + let block_index = &self.upload_block_map[&id]; + self.blocks.get_mut(block_index) + } + + fn get_entity_block(&self, id: EntityId) -> &DynamicInstanceBlock { + &self.blocks[&self.upload_block_map[&id]] + } + + pub fn chunks(&self) -> &ShapeChunkManager { + &self.chunks + } + + fn get_chunk_for_block(&self, block: &DynamicInstanceBlock) -> &ClosedChunk { + self.chunks.get_chunk(block.chunk_index) + } + + pub fn update_buffers( + &self, + mut cbb: AutoCommandBufferBuilder, + ) -> Fallible { + for (_, block) in self.blocks.iter() { + cbb = block.update_buffers(cbb)?; + } + Ok(cbb) + } + + pub fn render( + &self, + mut cbb: AutoCommandBufferBuilder, + camera: &dyn CameraAbstract, + window: &GraphicsWindow, + ) -> Fallible { + let chunk_man = &self.chunks; + for (_, block) in self.blocks.iter() { + let chunk = chunk_man.get_chunk(block.chunk_index); + cbb = block.render(cbb, self.pipeline(), &chunk, camera, window)?; + } + Ok(cbb) + } + + pub fn maintain(&mut self) { + let mut finished_blocks = Vec::new(); + let mut removals = Vec::new(); + for (block_index, block) in self.blocks.iter_mut() { + if block.maintain(&mut removals) { + finished_blocks.push(*block_index); + } + } + for removal in &removals { + self.upload_block_map.remove(removal); + } + for finished in &finished_blocks { + self.blocks.remove(finished); + } + } + + fn update_entity( + &mut self, + now: &Instant, + id: EntityId, + transform: &Transform, + shape_mesh: &ShapeMesh, + ) { + let start = self.start_time; + + if !self.knows_entity(id) { + let chunk_part = self.chunks.part(shape_mesh.shape_id()); + let draw_command = chunk_part.draw_command(0, 1); + self.allocate_entity_slot(id, shape_mesh.shape_id(), draw_command) + .expect("unable to reserve instance slot"); + } + + let chunk_part = self.chunks.part(shape_mesh.shape_id()); + let errata = chunk_part.widgets().errata(); + + let next_transform = transform.compact(); + let mut next_flags = [0u32; 2]; + let mut next_xforms = [0f32; 6 * 14]; + let xform_value_count = chunk_part.widgets().num_transformer_floats(); + shape_mesh + .draw_state() + .build_mask_into(&start, errata, &mut next_flags) + .expect("failed to build flags for shape"); + chunk_part + .widgets() + .animate_into(shape_mesh.draw_state(), &start, now, &mut next_xforms) + .expect(""); + + let block = self.get_entity_block_mut(id).unwrap(); + let slot = block.get_existing_slot(id); + *block.get_transform_buffer_slot(slot) = next_transform; + *block.get_flag_buffer_slot(slot) = next_flags; + block + .get_xform_buffer_slot(slot) + .copy_from_slice(&next_xforms[0..xform_value_count]); + } +} + +pub struct ShapeRenderSystem<'b> { + renderer: &'b mut ShapeRenderer, +} + +impl<'b> ShapeRenderSystem<'b> { + pub fn new(renderer: &'b mut ShapeRenderer) -> Self { + Self { renderer } + } +} + +impl<'a, 'b> System<'a> for ShapeRenderSystem<'b> { + // These are the resources required for execution. + // You can also define a struct and `#[derive(SystemData)]`, + // see the `full` example. + type SystemData = ( + Entities<'a>, + ReadStorage<'a, Transform>, + ReadStorage<'a, ShapeMesh>, + ); + + fn run(&mut self, (entities, transform, shape_mesh): Self::SystemData) { + let now = Instant::now(); + for (entity, transform, shape_mesh) in (&entities, &transform, &shape_mesh).join() { + self.renderer + .update_entity(&now, entity.id(), &transform, &shape_mesh); + } + } +} + +pub struct ShapeUpdateTransformSystem; +impl<'a> System<'a> for ShapeUpdateTransformSystem { + type SystemData = ( + ReadStorage<'a, Transform>, + WriteStorage<'a, ShapeMeshTransformBuffer>, + ); + + fn run(&mut self, (transforms, mut results): Self::SystemData) { + (&transforms, &mut results) + .par_join() + .for_each(|(transform, mut result)| { + result.buffer = transform.compact(); + }); + } +} + +pub struct ShapeUpdateFlagSystem; +impl<'a> System<'a> for ShapeUpdateFlagSystem { + type SystemData = ( + ReadStorage<'a, ShapeMesh>, + WriteStorage<'a, ShapeMeshFlagBuffer>, + ); + + fn run(&mut self, (shape_meshes, mut results): Self::SystemData) { + (&shape_meshes, &mut results) + .par_join() + .for_each(|(shape_mesh, mut result)| { + let state = shape_mesh.draw_state(); + state + .build_mask_into(&state.time_origin(), result.errata, &mut result.buffer) + .expect("failed to build flags for shape"); + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use nalgebra::Point3; + use omnilib::OmniLib; + use specs::DispatcherBuilder; + use vulkano::pipeline::GraphicsPipeline; + use window::GraphicsConfigBuilder; + use world::World; + + #[test] + fn it_works() -> Fallible<()> { + let omni = OmniLib::new_for_test_in_games(&["FA"])?; + + let window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; + let lib = omni.library("FA"); + + let world = Arc::new(World::new(lib)?); + + let mut shape_renderer = ShapeRenderer::new(world.clone(), &window)?; + let (t80_id, fut1) = + shape_renderer.upload_shape("T80.SH", DrawSelection::NormalModel, &window)?; + let future = shape_renderer.ensure_uploaded(&window)?; + future.then_signal_fence_and_flush()?.wait(None)?; + + let t80_ent1 = world.create_ground_mover(t80_id, Point3::new(0f64, 0f64, 0f64))?; + + let shape_render_system = ShapeRenderSystem::new(&mut shape_renderer); + let mut dispatcher = DispatcherBuilder::new() + .with(shape_render_system, "", &[]) + .build(); + + world.run(&mut dispatcher); + let t80_ent2 = world.create_ground_mover(t80_id, Point3::new(0f64, 0f64, 0f64))?; + world.run(&mut dispatcher); + let _t80_ent3 = world.create_ground_mover(t80_id, Point3::new(0f64, 0f64, 0f64))?; + world.run(&mut dispatcher); + + world.destroy_entity(t80_ent2)?; + world.run(&mut dispatcher); + world.destroy_entity(t80_ent1)?; + world.run(&mut dispatcher); + + Ok(()) + } +} diff --git a/libs/window/src/lib.rs b/libs/window/src/lib.rs index e54b2d09..10da45b8 100644 --- a/libs/window/src/lib.rs +++ b/libs/window/src/lib.rs @@ -335,6 +335,10 @@ impl GraphicsWindow { Ok(f64::from(dim[1] / dim[0])) } + pub fn now(&self) -> Box { + Box::new(sync::now(self.device())) as Box + } + pub fn device(&self) -> Arc { self.device.clone() }