From 5e34fcbcf1adb3a4dd2a99c75f9c05129b317ca6 Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sat, 31 Aug 2019 07:47:04 -0700 Subject: [PATCH 01/16] Create the packages; we need an ECS. --- Cargo.toml | 1 + libs/render/draw/shape_instance/Cargo.toml | 16 ++ libs/render/draw/shape_instance/src/lib.rs | 161 +++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 libs/render/draw/shape_instance/Cargo.toml create mode 100644 libs/render/draw/shape_instance/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 687b08ac..9e532b78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ "libs/render/common/camera", "libs/render/common/geometry", "libs/render/draw/shape", + "libs/render/draw/shape_instance", "libs/render/draw/skybox", "libs/render/draw/text", "libs/resource", diff --git a/libs/render/draw/shape_instance/Cargo.toml b/libs/render/draw/shape_instance/Cargo.toml new file mode 100644 index 00000000..d1b828ad --- /dev/null +++ b/libs/render/draw/shape_instance/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "shape_instance" +version = "0.1.0" +authors = ["Terrence Cole "] +edition = "2018" + +[dependencies] +failure = "^ 0.1.2" +nalgebra = "^ 0.18" +vulkano = "^ 0.14" +vulkano-shaders = "^ 0.14" +lib = { path = "../../../lib" } +omnilib = { path = "../../../omnilib" } +pal = { path = "../../../pal" } +shape_chunk = { path = "../../../render/buffer/shape_chunk" } +window = { path = "../../../window" } 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..dd6c90fb --- /dev/null +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -0,0 +1,161 @@ +// 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 failure::{ensure, Fallible}; +use lib::Library; +use nalgebra::Matrix4; +use omnilib::OmniLib; +use pal::Palette; +use shape_chunk::{ClosedChunk, DrawSelection, OpenChunk}; +use std::collections::HashMap; +use vulkano::{ + buffer::CpuAccessibleBuffer, + command_buffer::{AutoCommandBufferBuilder, CommandBuffer, DrawIndirectCommand}, +}; +use window::GraphicsWindow; + +pub struct Entity { + id: u64, +} + +// Types of data we want to be able to deal with. +// +// Static Immortal: +// CommandBuf: [ Name1(0...N), Name2(0...M), ...] +// BaseBuffer: [A, A, A, ... A{N}, B, B, B, ... B{M}]; A/B: [f32; 6] +// FlagsBuffer: [] +// XFormBuffer: [] +// +// We need to accumulate before uploading the command buffer, which means we need to be +// careful with the order in BaseBuffer. Assert that there are no xforms or flags on any of these. +// How much can we simplify the renderer if we know there are no xforms? +// +// Xforms vs no xforms -- most shapes have no xforms, even if they can be destroyed, or +// move around and be destroyed. How much can we simplify the renderer if we don't have +// xforms? Probably quite a bit. Is it worth having two pipelines? Benchmark to figure out +// how many fully dynamic shapes we can have. +// +// Fully dynamic: +// CommandBuf: [ E0, E1, E2, E3, ... EN ] <- updated on add/remove entity (as are all) +// BaseBuffer: [ B0, B1, B2, B3, ... BN ] <- updated every frame for movers, never for static +// FlagsBuffer: [ F0, F1, F2, F3, ... FN ] <- updated occasionally +// XformBuffer: [ X0..M, X0...L, X0...H ... X0...I ] <- updated every frame for some things +// +// Implement fullest feature set first. If we can render a million SOLDIER.SH, we can easily +// render a million TREE.SH. + +pub struct OpenChunkInstance { + open_chunk: OpenChunk, + command_buf: Vec, + base_buffer: Vec>, + flags_buffer: Vec<[u32; 2]>, +} + +pub struct InstanceSet { + // Offset of the chunk these instances draw from. + chunk_reference: usize, + + // Buffers for all instances stored in this instance set. One command per unique entity. + // 16 bytes per entity; index unnecessary for draw + command_buf: CpuAccessibleBuffer<[DrawIndirectCommand]>, + + // Base position and orientation in xyz+euler angles stored as 6 adjacent floats. + // 24 bytes per entity; buffer index inferable from drawing index + base_buffer: CpuAccessibleBuffer<[f32]>, // Flags buffers + + // 2 32bit flags words for each entity. + // 8 bytes per entity; buffer index inferable from drawing index + flags_buffer: CpuAccessibleBuffer<[u32]>, + + // 0 to 14 position/orientation [f32; 6], depending on the shape. + // assume 240 bytes per entity if we're talking about planes + // cannot infer position, so needs an index buffer + xform_buffer: CpuAccessibleBuffer<[f32]>, + + // 4 bytes per entity; can infer position from index + xform_index_buffer: CpuAccessibleBuffer<[i32]>, + // + // Total cost per entity is: 16 + 24 + 8 + 240 + 4 ~ 300 bytes per entity + // We cannot really upload more than 1MiB per frame, so... ~3000 planes +} + +pub struct ShapeInstanceRenderer { + open_chunk: Option, + chunks: Vec, + + // Map from the shape name to the chunk that shape is uploaded in. + chunk_map: HashMap, + + // Map from the entity to the chunk instance they belong in. + instance_map: HashMap, +} + +impl ShapeInstanceRenderer { + pub fn new(window: &GraphicsWindow) -> Fallible { + Ok(Self { + open_chunk: Some(OpenChunk::new(window)?), + chunks: Vec::new(), + }) + } + + pub fn add_static_immortal_model( + &mut self, + // TODO: position and orientation + shape_name: &str, + pal: &Palette, + lib: &Library, + window: &GraphicsWindow, + ) -> Fallible<()> { + ensure!( + self.open_chunk.is_some(), + "shape instances are already finished" + ); + + // Note: immortal implies a non-damage model + self.open_chunk.as_mut().unwrap().upload_shape( + shape_name, + DrawSelection::NormalModel, + &pal, + &lib, + &window, + )?; + + Ok(()) + } + + // Close the last open chunk and prepare for rendering. + pub fn finish_loading() {} +} + +#[cfg(test)] +mod tests { + use super::*; + use window::GraphicsConfigBuilder; + + #[test] + fn it_works() -> Fallible<()> { + let mut window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; + + let omni = OmniLib::new_for_test_in_games(&["FA"])?; + let lib = omni.library("FA"); + let palette = Palette::from_bytes(&lib.load("PALETTE.PAL")?)?; + + let mut shapes = ShapeInstanceRenderer::new(&window)?; + shapes.add_static_immortal_model("TREEA.SH", &palette, &lib, &window); + + Ok(()) + } +} +*/ From 37a38a0d4317b01dabcea13c507840c46eb9dceb Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sat, 31 Aug 2019 20:38:05 -0700 Subject: [PATCH 02/16] I think the general approach is workable. --- Cargo.toml | 1 + libs/render/buffer/shape_chunk/src/chunk.rs | 42 +++++-- libs/render/buffer/shape_chunk/src/lib.rs | 2 +- libs/render/draw/shape_instance/Cargo.toml | 2 + libs/render/draw/shape_instance/src/lib.rs | 117 ++++++++++++++++---- libs/world/Cargo.toml | 12 ++ libs/world/src/lib.rs | 72 ++++++++++++ 7 files changed, 217 insertions(+), 31 deletions(-) create mode 100644 libs/world/Cargo.toml create mode 100644 libs/world/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 9e532b78..d67a95a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ members = [ "libs/star_catalog", "libs/t2", "libs/window", + "libs/world", "libs/xt", ] diff --git a/libs/render/buffer/shape_chunk/src/chunk.rs b/libs/render/buffer/shape_chunk/src/chunk.rs index b40dd8f7..ea3cc98d 100644 --- a/libs/render/buffer/shape_chunk/src/chunk.rs +++ b/libs/render/buffer/shape_chunk/src/chunk.rs @@ -77,15 +77,21 @@ impl ChunkPart { } } +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct ShapeId((u32, u32)); + pub struct OpenChunk { + base_shape_id: u32, vertex_upload_buffer: Arc>, atlas_builder: MegaAtlas, vertex_offset: usize, - chunk_parts: HashMap, + last_shape_id: u32, + shape_ids: HashMap, + chunk_parts: HashMap, } impl OpenChunk { - pub fn new(window: &GraphicsWindow) -> Fallible { + pub fn new(base_shape_id: u32, window: &GraphicsWindow) -> Fallible { let vertex_upload_buffer: Arc> = unsafe { CpuAccessibleBuffer::raw( window.device(), @@ -96,9 +102,12 @@ impl OpenChunk { }; Ok(Self { + base_shape_id, vertex_offset: 0, atlas_builder: MegaAtlas::new(window)?, vertex_upload_buffer, + last_shape_id: 0, + shape_ids: HashMap::new(), chunk_parts: HashMap::new(), }) } @@ -115,7 +124,20 @@ impl OpenChunk { palette: &Palette, lib: &Library, window: &GraphicsWindow, - ) -> Fallible<()> { + ) -> Fallible { + if let Some(shape_id) = self.shape_ids.get(name) { + return Ok(*shape_id); + } + + let shape_id = self + .shape_ids + .insert( + name.to_owned(), + ShapeId((self.base_shape_id, self.last_shape_id)), + ) + .unwrap(); + self.last_shape_id += 1; + let sh = RawShape::from_bytes(&lib.load(&name)?)?; let start_vertex = self.vertex_offset; @@ -123,8 +145,8 @@ impl OpenChunk { ShapeUploader::draw_model(name, &sh, selection, palette, lib, window, self)?; let part = ChunkPart::new(start_vertex, self.vertex_offset, shape_widgets); - self.chunk_parts.insert(name.to_owned(), part); - Ok(()) + self.chunk_parts.insert(shape_id, part); + Ok(shape_id) } pub fn push_vertex(&mut self, vertex: Vertex) -> Fallible<()> { @@ -145,7 +167,8 @@ impl OpenChunk { pub struct ClosedChunk { vertex_buffer: Arc>, - chunk_parts: HashMap, + shape_ids: HashMap, + chunk_parts: HashMap, atlas_descriptor_set: Arc, } @@ -190,6 +213,7 @@ impl ClosedChunk { Ok(( ClosedChunk { vertex_buffer, + shape_ids: chunk.shape_ids, chunk_parts: chunk.chunk_parts, atlas_descriptor_set, }, @@ -206,8 +230,10 @@ impl ClosedChunk { } pub fn part_for(&self, name: &str) -> Fallible<&ChunkPart> { - self.chunk_parts + let id = self + .shape_ids .get(name) - .ok_or_else(|| err_msg("shape not found")) + .ok_or_else(|| err_msg("shape not found"))?; + Ok(&self.chunk_parts[id]) } } diff --git a/libs/render/buffer/shape_chunk/src/lib.rs b/libs/render/buffer/shape_chunk/src/lib.rs index ed7e9c7f..1c60d392 100644 --- a/libs/render/buffer/shape_chunk/src/lib.rs +++ b/libs/render/buffer/shape_chunk/src/lib.rs @@ -17,7 +17,7 @@ mod draw_state; mod texture_atlas; mod upload; -pub use chunk::{Chunk, ClosedChunk, OpenChunk}; +pub use chunk::{Chunk, ClosedChunk, OpenChunk, ShapeId}; pub use draw_state::DrawState; pub use upload::{DrawSelection, Vertex}; diff --git a/libs/render/draw/shape_instance/Cargo.toml b/libs/render/draw/shape_instance/Cargo.toml index d1b828ad..7bd30a51 100644 --- a/libs/render/draw/shape_instance/Cargo.toml +++ b/libs/render/draw/shape_instance/Cargo.toml @@ -7,10 +7,12 @@ edition = "2018" [dependencies] failure = "^ 0.1.2" nalgebra = "^ 0.18" +specs = "^ 0.15" vulkano = "^ 0.14" vulkano-shaders = "^ 0.14" lib = { path = "../../../lib" } omnilib = { path = "../../../omnilib" } pal = { path = "../../../pal" } shape_chunk = { path = "../../../render/buffer/shape_chunk" } +world = { path = "../../../world" } window = { path = "../../../window" } diff --git a/libs/render/draw/shape_instance/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs index dd6c90fb..aaf01756 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -12,20 +12,112 @@ // // You should have received a copy of the GNU General Public License // along with OpenFA. If not, see . -/* use failure::{ensure, Fallible}; use lib::Library; use nalgebra::Matrix4; +use nalgebra::Point3; use omnilib::OmniLib; use pal::Palette; -use shape_chunk::{ClosedChunk, DrawSelection, OpenChunk}; +use shape_chunk::{Chunk, ClosedChunk, DrawSelection, OpenChunk}; use std::collections::HashMap; use vulkano::{ - buffer::CpuAccessibleBuffer, + buffer::{CpuAccessibleBuffer, CpuBufferPool}, command_buffer::{AutoCommandBufferBuilder, CommandBuffer, DrawIndirectCommand}, }; use window::GraphicsWindow; +use world::Entity; + +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 { + // Buffers for all instances stored in this instance set. One command per unique entity. + // 16 bytes per entity; index unnecessary for draw + command_buf: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, + + // Base position and orientation in xyz+euler angles stored as 6 adjacent floats. + // 24 bytes per entity; buffer index inferable from drawing index + base_buffer: CpuBufferPool<[[f32; 6]; BLOCK_SIZE]>, // Flags buffers + + // 2 32bit flags words for each entity. + // 8 bytes per entity; buffer index inferable from drawing index + flags_buffer: CpuBufferPool<[[u32; 2]; BLOCK_SIZE]>, + + // 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: CpuBufferPool<[[f32; 6]; 4 * BLOCK_SIZE]>, + // 4 bytes per entity; can infer position from index + xform_index_buffer: CpuBufferPool<[i32; BLOCK_SIZE]>, +} + +// Combines a single shape chunk with a collection of instance blocks. +// +// We are uploading data on every frame, so we need fixed sized upload pools. +// Each pool can only handle so many instances though, so we may need more than +// one block of pools to service every instance that needs vertices in a chunk. +pub struct ChunkInstances { + chunk: Chunk, + + // FIXME: we probably want to store these as traits so that we can have + // FIXME: blocks with different upload characteristics. + blocks: Vec, +} + +pub struct ShapeInstanceRenderer { + per_chunk: Vec, +} + +impl ShapeInstanceRenderer { + pub fn new() -> Self { + Self { + per_chunk: Vec::new(), + } + } + + // pub fn create_building + + // pub fn create_airplane -- need to hook into shape state? + + pub fn create_ground_mover( + &mut self, + position: Point3, + name: &str, + world: &mut World, + window: &GraphicsWindow, + ) -> Fallible { + let entity = world.create_ground_mover(position); + + Ok(entity) + } +} + +#[cfg(test)] +mod tests { + use super::*; + 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 palette = Palette::from_bytes(&lib.load("PALETTE.PAL")?)?; + + let mut world = World::new(lib, window)?; + let mut shape_system = ShapeInstanceRenderer::new(); + + shape_system.create_ground_mover(Point3::zeros(), "SOLDIER.SH", &mut world, &window)?; + + Ok(()) + } +} + +/* pub struct Entity { id: u64, } @@ -139,23 +231,4 @@ impl ShapeInstanceRenderer { pub fn finish_loading() {} } -#[cfg(test)] -mod tests { - use super::*; - use window::GraphicsConfigBuilder; - - #[test] - fn it_works() -> Fallible<()> { - let mut window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; - - let omni = OmniLib::new_for_test_in_games(&["FA"])?; - let lib = omni.library("FA"); - let palette = Palette::from_bytes(&lib.load("PALETTE.PAL")?)?; - - let mut shapes = ShapeInstanceRenderer::new(&window)?; - shapes.add_static_immortal_model("TREEA.SH", &palette, &lib, &window); - - Ok(()) - } -} */ diff --git a/libs/world/Cargo.toml b/libs/world/Cargo.toml new file mode 100644 index 00000000..b8f9a12b --- /dev/null +++ b/libs/world/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "world" +version = "0.1.0" +authors = ["Terrence Cole "] +edition = "2018" + +[dependencies] +failure = "^ 0.1.2" +nalgebra = "^ 0.18" +specs = "^ 0.15" +lib = { path = "../lib" } +pal = { path = "../pal" } diff --git a/libs/world/src/lib.rs b/libs/world/src/lib.rs new file mode 100644 index 00000000..a73f4e9b --- /dev/null +++ b/libs/world/src/lib.rs @@ -0,0 +1,72 @@ +// 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 failure::Fallible; +use lib::Library; +use nalgebra::{Point3, UnitQuaternion, Vector3}; +use pal::Palette; +use specs::{Builder, Component, VecStorage, World as SpecsWorld, WorldExt}; + +pub use specs::Entity; + +// Components +struct Position(Point3); +impl Component for Position { + type Storage = VecStorage; +} + +struct Rotation(UnitQuaternion); +impl Component for Rotation { + type Storage = VecStorage; +} + +struct Velocity(Vector3); +impl Component for Velocity { + type Storage = VecStorage; +} + +// Entities / World +pub struct World { + ecs: SpecsWorld, + lib: Library, + system_palette: Palette, +} + +impl World { + pub fn new(lib: Library) -> Fallible { + let mut ecs = SpecsWorld::new(); + ecs.register::(); + ecs.register::(); + ecs.register::(); + + Ok(Self { + ecs, + system_palette: Palette::from_bytes(&lib.load("PALETTE.PAL")?)?, + lib, + }) + } + + pub fn create_ground_mover(&mut self, position: Point3) -> Entity { + self.ecs + .create_entity() + .with(Position(position)) + .with(Rotation(UnitQuaternion::identity())) + .with(Velocity(Vector3::zeros())) + .build() + } + + pub fn destroy_entity(&mut self, entity: Entity) -> Fallible<()> { + Ok(self.ecs.delete_entity(entity)?) + } +} From ffbe720b226c366b20c3e1ce860507c4fdc3aaa5 Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sun, 1 Sep 2019 08:38:33 -0700 Subject: [PATCH 03/16] Sketch out a framework of the world. --- libs/render/buffer/shape_chunk/src/chunk.rs | 9 +- libs/world/Cargo.toml | 5 ++ libs/world/src/component/flight_dynamics.rs | 24 +++++ libs/world/src/component/mod.rs | 18 ++++ libs/world/src/component/shape_mesh.rs | 34 +++++++ libs/world/src/component/transform.rs | 35 ++++++++ libs/world/src/component/wheeled_dynamics.rs | 32 +++++++ libs/world/src/lib.rs | 94 +++++++++++++------- 8 files changed, 214 insertions(+), 37 deletions(-) create mode 100644 libs/world/src/component/flight_dynamics.rs create mode 100644 libs/world/src/component/mod.rs create mode 100644 libs/world/src/component/shape_mesh.rs create mode 100644 libs/world/src/component/transform.rs create mode 100644 libs/world/src/component/wheeled_dynamics.rs diff --git a/libs/render/buffer/shape_chunk/src/chunk.rs b/libs/render/buffer/shape_chunk/src/chunk.rs index ea3cc98d..a58d2488 100644 --- a/libs/render/buffer/shape_chunk/src/chunk.rs +++ b/libs/render/buffer/shape_chunk/src/chunk.rs @@ -129,14 +129,9 @@ impl OpenChunk { return Ok(*shape_id); } - let shape_id = self - .shape_ids - .insert( - name.to_owned(), - ShapeId((self.base_shape_id, self.last_shape_id)), - ) - .unwrap(); + let shape_id = ShapeId((self.base_shape_id, self.last_shape_id)); self.last_shape_id += 1; + self.shape_ids.insert(name.to_owned(), shape_id); let sh = RawShape::from_bytes(&lib.load(&name)?)?; diff --git a/libs/world/Cargo.toml b/libs/world/Cargo.toml index b8f9a12b..2d5238d8 100644 --- a/libs/world/Cargo.toml +++ b/libs/world/Cargo.toml @@ -10,3 +10,8 @@ nalgebra = "^ 0.18" specs = "^ 0.15" lib = { path = "../lib" } pal = { path = "../pal" } +shape_chunk = { path = "../render/buffer/shape_chunk" } + +[dev-dependencies] +omnilib = { path = "../omnilib" } +window = { path = "../window" } \ No newline at end of file diff --git a/libs/world/src/component/flight_dynamics.rs b/libs/world/src/component/flight_dynamics.rs new file mode 100644 index 00000000..40e78307 --- /dev/null +++ b/libs/world/src/component/flight_dynamics.rs @@ -0,0 +1,24 @@ +// 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 nalgebra::Vector3; +use specs::{Component, VecStorage}; + +pub struct FlightDynamics { + velocity: Vector3, +} + +impl Component for FlightDynamics { + type Storage = VecStorage; +} diff --git a/libs/world/src/component/mod.rs b/libs/world/src/component/mod.rs new file mode 100644 index 00000000..fca32b53 --- /dev/null +++ b/libs/world/src/component/mod.rs @@ -0,0 +1,18 @@ +// 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 . +pub mod flight_dynamics; +pub mod shape_mesh; +pub mod transform; +pub mod wheeled_dynamics; diff --git a/libs/world/src/component/shape_mesh.rs b/libs/world/src/component/shape_mesh.rs new file mode 100644 index 00000000..5fc7d83a --- /dev/null +++ b/libs/world/src/component/shape_mesh.rs @@ -0,0 +1,34 @@ +// 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 shape_chunk::{DrawState, ShapeId}; +use specs::{Component, VecStorage}; + +pub struct ShapeMesh { + shape_id: ShapeId, + draw_state: DrawState, +} + +impl Component for ShapeMesh { + type Storage = VecStorage; +} + +impl ShapeMesh { + pub fn new(shape_id: ShapeId) -> Self { + Self { + shape_id, + draw_state: Default::default(), + } + } +} diff --git a/libs/world/src/component/transform.rs b/libs/world/src/component/transform.rs new file mode 100644 index 00000000..66dc78ec --- /dev/null +++ b/libs/world/src/component/transform.rs @@ -0,0 +1,35 @@ +// 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 nalgebra::{Point3, UnitQuaternion}; +use specs::{Component, VecStorage}; + +pub struct Transform { + position: Point3, + rotation: UnitQuaternion, + // scale: Vector3, // we don't have an upload slot for this currently. +} + +impl Component for Transform { + type Storage = VecStorage; +} + +impl Transform { + pub fn new(position: Point3) -> Self { + Self { + position, + rotation: UnitQuaternion::identity(), + } + } +} diff --git a/libs/world/src/component/wheeled_dynamics.rs b/libs/world/src/component/wheeled_dynamics.rs new file mode 100644 index 00000000..7b1223e6 --- /dev/null +++ b/libs/world/src/component/wheeled_dynamics.rs @@ -0,0 +1,32 @@ +// 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 nalgebra::Vector3; +use specs::{Component, VecStorage}; + +pub struct WheeledDynamics { + velocity: Vector3, +} + +impl Component for WheeledDynamics { + type Storage = VecStorage; +} + +impl WheeledDynamics { + pub fn new() -> Self { + Self { + velocity: Vector3::zeros(), + } + } +} diff --git a/libs/world/src/lib.rs b/libs/world/src/lib.rs index a73f4e9b..dd35f423 100644 --- a/libs/world/src/lib.rs +++ b/libs/world/src/lib.rs @@ -14,59 +14,93 @@ // along with OpenFA. If not, see . use failure::Fallible; use lib::Library; -use nalgebra::{Point3, UnitQuaternion, Vector3}; +use nalgebra::Point3; use pal::Palette; -use specs::{Builder, Component, VecStorage, World as SpecsWorld, WorldExt}; +use shape_chunk::{DrawSelection, ShapeId}; +use specs::{Builder, World as SpecsWorld, WorldExt}; +use std::sync::Arc; pub use specs::Entity; -// Components -struct Position(Point3); -impl Component for Position { - type Storage = VecStorage; -} - -struct Rotation(UnitQuaternion); -impl Component for Rotation { - type Storage = VecStorage; -} +mod component; -struct Velocity(Vector3); -impl Component for Velocity { - type Storage = VecStorage; -} +use component::{ + flight_dynamics::FlightDynamics, shape_mesh::ShapeMesh, transform::Transform, + wheeled_dynamics::WheeledDynamics, +}; -// Entities / World pub struct World { ecs: SpecsWorld, - lib: Library, - system_palette: Palette, + + // Resources + lib: Arc>, + palette: Arc, } impl World { - pub fn new(lib: Library) -> Fallible { + pub fn new(lib: Arc>) -> Fallible { let mut ecs = SpecsWorld::new(); - ecs.register::(); - ecs.register::(); - ecs.register::(); + ecs.register::(); + ecs.register::(); + ecs.register::(); + ecs.register::(); Ok(Self { ecs, - system_palette: Palette::from_bytes(&lib.load("PALETTE.PAL")?)?, + palette: Arc::new(Palette::from_bytes(&lib.load("PALETTE.PAL")?)?), lib, }) } - pub fn create_ground_mover(&mut self, position: Point3) -> Entity { - self.ecs + pub fn library(&self) -> &Library { + &self.lib + } + + pub fn system_palette(&self) -> &Palette { + &self.palette + } + + pub fn create_ground_mover( + &mut self, + shape_id: ShapeId, + position: Point3, + ) -> Fallible { + Ok(self + .ecs .create_entity() - .with(Position(position)) - .with(Rotation(UnitQuaternion::identity())) - .with(Velocity(Vector3::zeros())) - .build() + .with(Transform::new(position)) + .with(WheeledDynamics::new()) + .with(ShapeMesh::new(shape_id)) + .build()) } pub fn destroy_entity(&mut self, entity: Entity) -> Fallible<()> { Ok(self.ecs.delete_entity(entity)?) } } + +#[cfg(test)] +mod test { + use super::*; + use omnilib::OmniLib; + use shape_chunk::{ClosedChunk, OpenChunk}; + use window::{GraphicsConfigBuilder, GraphicsWindow}; + + #[test] + fn test_it_works() -> Fallible<()> { + let omni = OmniLib::new_for_test_in_games(&["FA"])?; + let mut world = World::new(omni.library("FA"))?; + let window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; + let mut upload = OpenChunk::new(0, &window)?; + let shape_id = upload.upload_shape( + "T80.SH", + DrawSelection::NormalModel, + world.system_palette(), + world.library(), + &window, + )?; + let ent = world.create_ground_mover(shape_id, Point3::new(0f64, 0f64, 0f64))?; + world.destroy_entity(ent)?; + Ok(()) + } +} From 52b7b6ca04ce2db74a19894b0f9021a317d67f9b Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sun, 1 Sep 2019 09:49:32 -0700 Subject: [PATCH 04/16] Flesh out the other buffers in our demo. --- libs/render/buffer/global_layout/src/lib.rs | 5 +- .../shape_chunk/examples/demo-instances.rs | 119 +++++++++++------- libs/render/buffer/shape_chunk/src/upload.rs | 20 +-- 3 files changed, 88 insertions(+), 56 deletions(-) diff --git a/libs/render/buffer/global_layout/src/lib.rs b/libs/render/buffer/global_layout/src/lib.rs index 51bfe291..2acd3167 100644 --- a/libs/render/buffer/global_layout/src/lib.rs +++ b/libs/render/buffer/global_layout/src/lib.rs @@ -16,8 +16,9 @@ pub enum GlobalSets { 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-instances.rs index 38c95409..4ff82e1a 100644 --- a/libs/render/buffer/shape_chunk/examples/demo-instances.rs +++ b/libs/render/buffer/shape_chunk/examples/demo-instances.rs @@ -43,6 +43,29 @@ mod vs { include: ["./libs/render"], src: " #version 450 + #include + #include + + // Scene info + layout(push_constant) uniform PushConstantData { + mat4 view; + mat4 projection; + } pc; + + // 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; @@ -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,7 +250,7 @@ fn main() -> Fallible<()> { let lib = omni.library("FA"); let palette = Palette::from_bytes(&lib.load("PALETTE.PAL")?)?; - let mut open_chunk = OpenChunk::new(&window)?; + let mut open_chunk = OpenChunk::new(0, &window)?; open_chunk.upload_shape("F8.SH", DrawSelection::NormalModel, &palette, &lib, &window)?; open_chunk.upload_shape( "F18.SH", @@ -249,6 +264,14 @@ fn main() -> Fallible<()> { let f18_part = chunk.part_for("F18.SH")?; + // Upload tranforms + let transforms = vec![0, 0, 0, 0, 0, 0]; + let transforms_buffer = CpuAccessibleBuffer::from_iter( + window.device(), + BufferUsage::all(), + transforms.iter().cloned(), + )?; + // Upload flags let mut draw_state: DrawState = Default::default(); draw_state.toggle_gear(&Instant::now()); @@ -264,27 +287,34 @@ fn main() -> Fallible<()> { flags_arr.iter().cloned(), )?; - // Upload transforms + // Upload xforms let now = Instant::now(); let xforms_len = f18_part.widgets().num_transformer_floats(); let mut xforms = Vec::with_capacity(xforms_len); xforms.resize(xforms_len, 0f32); - f18_part.widgets().animate_into( - &draw_state, - draw_state.time_origin(), - &now, - &mut xforms[0..], - )?; + f18_part + .widgets() + .animate_into(&draw_state, draw_state.time_origin(), &now, &mut xforms)?; let xforms_buffer = CpuAccessibleBuffer::from_iter( window.device(), BufferUsage::all(), 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 +331,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() { diff --git a/libs/render/buffer/shape_chunk/src/upload.rs b/libs/render/buffer/shape_chunk/src/upload.rs index d07ab820..6143269b 100644 --- a/libs/render/buffer/shape_chunk/src/upload.rs +++ b/libs/render/buffer/shape_chunk/src/upload.rs @@ -553,7 +553,7 @@ impl ShapeWidgets { let mut offset = 0; for transformer in self.transformers.iter() { let xform = transformer.transform(draw_state, start, now)?; - buffer[offset..].copy_from_slice(&xform); + buffer[offset..offset + 6].copy_from_slice(&xform); offset += 6; } Ok(()) @@ -594,7 +594,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])); @@ -627,15 +627,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; From ff1edb6a28eac98021cdbf7e46efa3167ab2797a Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sun, 1 Sep 2019 11:02:27 -0700 Subject: [PATCH 05/16] Most of the right framework is in place, with the wrong names. --- .../shape_chunk/examples/demo-instances.rs | 2 +- libs/render/buffer/shape_chunk/src/chunk.rs | 41 ++++++- libs/render/buffer/shape_chunk/src/upload.rs | 2 - libs/render/draw/shape_instance/src/lib.rs | 104 ++++++++++++++---- libs/world/src/component/mod.rs | 5 + libs/world/src/component/shape_mesh.rs | 4 + libs/world/src/lib.rs | 16 +-- 7 files changed, 140 insertions(+), 34 deletions(-) diff --git a/libs/render/buffer/shape_chunk/examples/demo-instances.rs b/libs/render/buffer/shape_chunk/examples/demo-instances.rs index 4ff82e1a..0cff98d2 100644 --- a/libs/render/buffer/shape_chunk/examples/demo-instances.rs +++ b/libs/render/buffer/shape_chunk/examples/demo-instances.rs @@ -250,7 +250,7 @@ fn main() -> Fallible<()> { let lib = omni.library("FA"); let palette = Palette::from_bytes(&lib.load("PALETTE.PAL")?)?; - let mut open_chunk = OpenChunk::new(0, &window)?; + let mut open_chunk = OpenChunk::new(&window)?; open_chunk.upload_shape("F8.SH", DrawSelection::NormalModel, &palette, &lib, &window)?; open_chunk.upload_shape( "F18.SH", diff --git a/libs/render/buffer/shape_chunk/src/chunk.rs b/libs/render/buffer/shape_chunk/src/chunk.rs index a58d2488..0f79574c 100644 --- a/libs/render/buffer/shape_chunk/src/chunk.rs +++ b/libs/render/buffer/shape_chunk/src/chunk.rs @@ -18,10 +18,15 @@ use crate::{ }; use failure::{ensure, err_msg, Fallible}; use global_layout::GlobalSets; +use lazy_static::lazy_static; use lib::Library; use pal::Palette; use sh::RawShape; -use std::{collections::HashMap, mem, sync::Arc}; +use std::{ + collections::HashMap, + mem, + sync::{Arc, Mutex}, +}; use vulkano::{ buffer::{BufferUsage, CpuAccessibleBuffer, DeviceLocalBuffer}, command_buffer::{AutoCommandBufferBuilder, CommandBuffer, DrawIndirectCommand}, @@ -41,11 +46,39 @@ const VERTEX_CHUNK_HIGH_WATER_COUNT: usize = const VERTEX_CHUNK_BYTES: usize = VERTEX_CHUNK_HIGH_WATER_BYTES + MAX_VERTEX_BYTES; const VERTEX_CHUNK_COUNT: usize = VERTEX_CHUNK_BYTES / mem::size_of::(); +lazy_static! { + static ref GLOBAL_CHUNK_ID: Mutex = Mutex::new(0); +} + +fn allocate_base_shape_id() -> u32 { + let mut global = GLOBAL_CHUNK_ID.lock().unwrap(); + let base_id = *global; + assert!(base_id < std::u32::MAX, "overflowed base shape id"); + *global += 1; + base_id +} + pub enum Chunk { Open(OpenChunk), 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, @@ -77,7 +110,7 @@ impl ChunkPart { } } -#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct ShapeId((u32, u32)); pub struct OpenChunk { @@ -91,7 +124,7 @@ pub struct OpenChunk { } impl OpenChunk { - pub fn new(base_shape_id: u32, window: &GraphicsWindow) -> Fallible { + pub fn new(window: &GraphicsWindow) -> Fallible { let vertex_upload_buffer: Arc> = unsafe { CpuAccessibleBuffer::raw( window.device(), @@ -102,7 +135,7 @@ impl OpenChunk { }; Ok(Self { - base_shape_id, + base_shape_id: allocate_base_shape_id(), vertex_offset: 0, atlas_builder: MegaAtlas::new(window)?, vertex_upload_buffer, diff --git a/libs/render/buffer/shape_chunk/src/upload.rs b/libs/render/buffer/shape_chunk/src/upload.rs index 6143269b..c5328674 100644 --- a/libs/render/buffer/shape_chunk/src/upload.rs +++ b/libs/render/buffer/shape_chunk/src/upload.rs @@ -998,8 +998,6 @@ impl ShapeUploader { window: &GraphicsWindow, chunk: &mut OpenChunk, ) -> Fallible { - println!("MODEL: {}", name); - // Outputs let mut transformers = Vec::new(); let mut xforms = Vec::new(); diff --git a/libs/render/draw/shape_instance/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs index aaf01756..11975f46 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -12,20 +12,20 @@ // // You should have received a copy of the GNU General Public License // along with OpenFA. If not, see . -use failure::{ensure, Fallible}; +use failure::{bail, ensure, Fallible}; use lib::Library; use nalgebra::Matrix4; use nalgebra::Point3; use omnilib::OmniLib; use pal::Palette; -use shape_chunk::{Chunk, ClosedChunk, DrawSelection, OpenChunk}; -use std::collections::HashMap; +use shape_chunk::{Chunk, ClosedChunk, DrawSelection, OpenChunk, ShapeId}; +use std::{collections::HashMap, mem}; use vulkano::{ buffer::{CpuAccessibleBuffer, CpuBufferPool}, command_buffer::{AutoCommandBufferBuilder, CommandBuffer, DrawIndirectCommand}, }; use window::GraphicsWindow; -use world::Entity; +use world::{Entity, World}; const BLOCK_SIZE: usize = 128; @@ -67,30 +67,88 @@ pub struct ChunkInstances { } pub struct ShapeInstanceRenderer { - per_chunk: Vec, + //per_chunk: Vec, + open_mover_chunk: OpenChunk, + mover_chunks: Vec, } impl ShapeInstanceRenderer { - pub fn new() -> Self { - Self { - per_chunk: Vec::new(), - } + pub fn new(window: &GraphicsWindow) -> Fallible { + Ok(Self { + open_mover_chunk: OpenChunk::new(window)?, + mover_chunks: Vec::new(), + }) } // pub fn create_building // pub fn create_airplane -- need to hook into shape state? - pub fn create_ground_mover( + pub fn upload_mover( &mut self, - position: Point3, name: &str, - world: &mut World, + selection: DrawSelection, + world: &World, window: &GraphicsWindow, - ) -> Fallible { - let entity = world.create_ground_mover(position); + ) -> Fallible { + if self.open_mover_chunk.chunk_is_full() { + let mut open_chunk = OpenChunk::new(window)?; + mem::swap(&mut open_chunk, &mut self.open_mover_chunk); + //self.mover_chunks.push(ClosedChunk::new(open_chunk, pipeline, window)) + } + let shape_id = self.open_mover_chunk.upload_shape( + name, + selection, + world.system_palette(), + world.library(), + window, + )?; + Ok(shape_id) + } - Ok(entity) + /* + fn find_open_mover_chunk(&mut self, window: &GraphicsWindow) -> Fallible<&mut OpenChunk> { + for instances in &mut self.per_chunk { + if instances.chunk.is_open() { + return Ok(instances.chunk.as_open_chunk_mut()); + } + } + + unimplemented!() + /* + self.per_chunk.push(ChunkInstances { + chunk: Chunk::Open(OpenChunk::new(window)?), + blocks: Vec::new(), + }); + self.find_open_mover_chunk(window) + */ + } + */ +} + +use specs::prelude::*; +use world::component::{ShapeMesh, Transform}; + +struct ShapeRenderSystem; + +impl<'a> System<'a> for ShapeRenderSystem { + // These are the resources required for execution. + // You can also define a struct and `#[derive(SystemData)]`, + // see the `full` example. + type SystemData = (ReadStorage<'a, Transform>, ReadStorage<'a, ShapeMesh>); + + fn run(&mut self, (transform, shape_mesh): Self::SystemData) { + // The `.join()` combines multiple components, + // so we only access those entities which have + // both of them. + + // This joins the component storages for Position + // and Velocity together; it's also possible to do this + // in parallel using rayon's `ParallelIterator`s. + // See `ParJoin` for more. + for (transform, shape_mesh) in (&transform, &shape_mesh).join() { + println!("shape_id: {:?}", shape_mesh.shape_id()); + } } } @@ -106,12 +164,20 @@ mod tests { let window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; let lib = omni.library("FA"); - let palette = Palette::from_bytes(&lib.load("PALETTE.PAL")?)?; - let mut world = World::new(lib, window)?; - let mut shape_system = ShapeInstanceRenderer::new(); + let mut world = World::new(lib)?; + + let mut shape_renderer = ShapeInstanceRenderer::new(&window)?; + let t80_id = + shape_renderer.upload_mover("T80.SH", DrawSelection::NormalModel, &world, &window)?; + + let t80_ent = world.create_ground_mover(t80_id, Point3::new(0f64, 0f64, 0f64))?; - shape_system.create_ground_mover(Point3::zeros(), "SOLDIER.SH", &mut world, &window)?; + let mut dispatcher = DispatcherBuilder::new() + .with(ShapeRenderSystem, "", &[]) + .build(); + //dispatcher.dispatch(&mut world); + world.run(&mut dispatcher); Ok(()) } diff --git a/libs/world/src/component/mod.rs b/libs/world/src/component/mod.rs index fca32b53..a4c500de 100644 --- a/libs/world/src/component/mod.rs +++ b/libs/world/src/component/mod.rs @@ -16,3 +16,8 @@ pub mod flight_dynamics; pub mod shape_mesh; pub mod transform; pub mod wheeled_dynamics; + +pub use flight_dynamics::FlightDynamics; +pub use shape_mesh::ShapeMesh; +pub use transform::Transform; +pub use wheeled_dynamics::WheeledDynamics; diff --git a/libs/world/src/component/shape_mesh.rs b/libs/world/src/component/shape_mesh.rs index 5fc7d83a..2634f80d 100644 --- a/libs/world/src/component/shape_mesh.rs +++ b/libs/world/src/component/shape_mesh.rs @@ -31,4 +31,8 @@ impl ShapeMesh { draw_state: Default::default(), } } + + pub fn shape_id(&self) -> ShapeId { + self.shape_id + } } diff --git a/libs/world/src/lib.rs b/libs/world/src/lib.rs index dd35f423..31645a75 100644 --- a/libs/world/src/lib.rs +++ b/libs/world/src/lib.rs @@ -12,23 +12,19 @@ // // You should have received a copy of the GNU General Public License // along with OpenFA. If not, see . +pub mod component; + +use crate::component::*; use failure::Fallible; use lib::Library; use nalgebra::Point3; use pal::Palette; use shape_chunk::{DrawSelection, ShapeId}; -use specs::{Builder, World as SpecsWorld, WorldExt}; +use specs::{Builder, Dispatcher, World as SpecsWorld, WorldExt}; use std::sync::Arc; pub use specs::Entity; -mod component; - -use component::{ - flight_dynamics::FlightDynamics, shape_mesh::ShapeMesh, transform::Transform, - wheeled_dynamics::WheeledDynamics, -}; - pub struct World { ecs: SpecsWorld, @@ -52,6 +48,10 @@ impl World { }) } + pub fn run(&mut self, dispatcher: &mut Dispatcher) { + dispatcher.dispatch(&mut self.ecs) + } + pub fn library(&self) -> &Library { &self.lib } From fea557a51b988cbdfc149a3ca781d441f4347aca Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sun, 1 Sep 2019 19:57:35 -0700 Subject: [PATCH 06/16] Still on the right track, mostly. --- libs/i386/src/disassembler.rs | 9 +- libs/i386/src/interpreter.rs | 20 +- .../shape_chunk/examples/demo-instances.rs | 8 +- libs/render/buffer/shape_chunk/src/upload.rs | 12 +- libs/render/draw/shape/src/upload.rs | 13 +- libs/render/draw/shape_instance/Cargo.toml | 3 + libs/render/draw/shape_instance/src/lib.rs | 415 ++++++++++++------ libs/sh/src/instr/code.rs | 17 +- libs/world/src/lib.rs | 4 +- 9 files changed, 336 insertions(+), 165 deletions(-) diff --git a/libs/i386/src/disassembler.rs b/libs/i386/src/disassembler.rs index ec140a14..30a7e65c 100644 --- a/libs/i386/src/disassembler.rs +++ b/libs/i386/src/disassembler.rs @@ -19,7 +19,10 @@ use ansi::ansi; use failure::{bail, ensure, Error, Fail, Fallible}; use log::trace; use reverse::bs2s; -use std::{cell::RefCell, fmt, mem, rc::Rc}; +use std::{ + fmt, mem, + sync::{Arc, RwLock}, +}; pub use crate::lut::{Memonic, HAS_INLINE_REG, OPCODES, PREFIX_CODES, USE_REG_OPCODES}; @@ -1052,8 +1055,8 @@ impl ByteCode { sz } - pub fn into_rc(self) -> Rc> { - Rc::new(RefCell::new(self)) + pub fn into_arc(self) -> Arc> { + Arc::new(RwLock::new(self)) } pub fn show_relative(&self, base: usize) -> String { diff --git a/libs/i386/src/interpreter.rs b/libs/i386/src/interpreter.rs index 2a7197af..9d12d04d 100644 --- a/libs/i386/src/interpreter.rs +++ b/libs/i386/src/interpreter.rs @@ -20,7 +20,11 @@ use crate::{ }; use failure::{bail, ensure, Fallible}; use log::trace; -use std::{cell::RefCell, collections::HashMap, mem, rc::Rc}; +use std::{ + collections::HashMap, + mem, + sync::{Arc, RwLock}, +}; #[derive(Debug)] pub enum ExitInfo { @@ -74,8 +78,8 @@ pub struct Interpreter { stack: Vec, memmap_w: Vec, memmap_r: Vec, - bytecode: Vec>>, - ports_r: HashMap u32>>, + bytecode: Vec>>, + ports_r: HashMap u32 + Send + Sync>>, trampolines: HashMap, } @@ -112,7 +116,7 @@ impl Interpreter { self.trampolines.insert(addr, (name.to_owned(), arg_count)); } - pub fn add_read_port(&mut self, addr: u32, func: Box u32>) { + pub fn add_read_port(&mut self, addr: u32, func: Box u32 + Send + Sync>) { self.ports_r.insert(addr, func); } @@ -165,7 +169,7 @@ impl Interpreter { ); } - pub fn add_code(&mut self, bc: Rc>) { + pub fn add_code(&mut self, bc: Arc>) { self.bytecode.push(bc); } @@ -173,10 +177,10 @@ impl Interpreter { self.bytecode.clear(); } - fn find_instr(&self) -> Fallible<(Rc>, usize)> { + fn find_instr(&self) -> Fallible<(Arc>, usize)> { trace!("searching for instr at ip: {:08X}", self.eip()); for bc_ref in self.bytecode.iter() { - let bc = bc_ref.borrow(); + let bc = bc_ref.read().unwrap(); if self.eip() >= bc.start_addr && self.eip() < bc.start_addr + bc.size { trace!("in bc at {:08X}", bc.start_addr); let mut pos = bc.start_addr; @@ -203,7 +207,7 @@ impl Interpreter { pub fn interpret(&mut self, at: u32) -> Fallible { *self.eip_mut() = at; let (bc_ref, mut offset) = self.find_instr()?; - let bc = bc_ref.borrow(); + let bc = bc_ref.read().unwrap(); while offset < bc.instrs.len() { let instr = &bc.instrs[offset]; trace!("{:3}:{:04X}: {}", offset, self.eip(), instr); diff --git a/libs/render/buffer/shape_chunk/examples/demo-instances.rs b/libs/render/buffer/shape_chunk/examples/demo-instances.rs index 0cff98d2..e44a450f 100644 --- a/libs/render/buffer/shape_chunk/examples/demo-instances.rs +++ b/libs/render/buffer/shape_chunk/examples/demo-instances.rs @@ -130,10 +130,10 @@ mod fs { 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 + //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) { diff --git a/libs/render/buffer/shape_chunk/src/upload.rs b/libs/render/buffer/shape_chunk/src/upload.rs index c5328674..521d6364 100644 --- a/libs/render/buffer/shape_chunk/src/upload.rs +++ b/libs/render/buffer/shape_chunk/src/upload.rs @@ -24,8 +24,8 @@ use pal::Palette; use pic::Pic; use sh::{Facet, FacetFlags, Instr, RawShape, VertexBuf, X86Code, X86Trampoline, SHAPE_LOAD_BASE}; use std::{ - cell::RefCell, collections::{HashMap, HashSet}, + sync::{Arc, RwLock}, time::Instant, }; use vulkano::impl_vertex; @@ -428,7 +428,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, @@ -454,7 +454,7 @@ impl Transformer { let bay_position = draw_state.bay_position() as u32; let thrust_vectoring = draw_state.thrust_vector_position() as i32 as u32; let wing_sweep = i32::from(draw_state.wing_sweep_angle()) as u32; - let mut vm = self.vm.borrow_mut(); + let mut vm = self.vm.write().unwrap(); let t = (((*now - *start).as_millis() as u32) >> 4) & 0x0FFF; for input in &self.inputs { match input { @@ -683,7 +683,7 @@ impl ShapeUploader { sh: &'a RawShape, ) -> HashMap<&'a str, &'a X86Trampoline> { let mut out = HashMap::new(); - for instr in &x86.bytecode.borrow().instrs { + for instr in &x86.bytecode.read().unwrap().instrs { for operand in &instr.operands { if let i386::Operand::Memory(memref) = operand { if let Ok(tramp) = sh.lookup_trampoline_by_offset( @@ -703,7 +703,7 @@ impl ShapeUploader { ) -> Fallible> { let mut out = HashMap::new(); let mut push_value = 0; - for instr in &x86.bytecode.borrow().instrs { + for instr in &x86.bytecode.read().unwrap().instrs { if instr.memonic == i386::Memonic::Push { if let i386::Operand::Imm32s(v) = instr.operands[0] { push_value = (v as u32).wrapping_sub(SHAPE_LOAD_BASE); @@ -979,7 +979,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/src/upload.rs b/libs/render/draw/shape/src/upload.rs index 78bb15a9..f8128259 100644 --- a/libs/render/draw/shape/src/upload.rs +++ b/libs/render/draw/shape/src/upload.rs @@ -29,9 +29,8 @@ use pal::Palette; use pic::Pic; use sh::{Facet, FacetFlags, Instr, RawShape, VertexBuf, X86Code, X86Trampoline, SHAPE_LOAD_BASE}; use std::{ - cell::RefCell, collections::{HashMap, HashSet}, - sync::Arc, + sync::{Arc, RwLock}, time::Instant, }; use vulkano::{ @@ -434,7 +433,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, @@ -456,7 +455,7 @@ impl Transformer { let bay_position = draw_state.bay_position() as u32; let thrust_vectoring = draw_state.thrust_vector_position() as i32 as u32; let wing_sweep = i32::from(draw_state.wing_sweep_angle()) as u32; - let mut vm = self.vm.borrow_mut(); + let mut vm = self.vm.write().unwrap(); let t = (((*now - *start).as_millis() as u32) >> 4) & 0x0FFF; for input in &self.inputs { match input { @@ -712,7 +711,7 @@ impl ShapeUploader { sh: &'a RawShape, ) -> HashMap<&'a str, &'a X86Trampoline> { let mut out = HashMap::new(); - for instr in &x86.bytecode.borrow().instrs { + for instr in &x86.bytecode.read().unwrap().instrs { for operand in &instr.operands { if let i386::Operand::Memory(memref) = operand { if let Ok(tramp) = sh.lookup_trampoline_by_offset( @@ -732,7 +731,7 @@ impl ShapeUploader { ) -> Fallible> { let mut out = HashMap::new(); let mut push_value = 0; - for instr in &x86.bytecode.borrow().instrs { + for instr in &x86.bytecode.read().unwrap().instrs { if instr.memonic == i386::Memonic::Push { if let i386::Operand::Imm32s(v) = instr.operands[0] { push_value = (v as u32).wrapping_sub(SHAPE_LOAD_BASE); @@ -1008,7 +1007,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 index 7bd30a51..9abe1018 100644 --- a/libs/render/draw/shape_instance/Cargo.toml +++ b/libs/render/draw/shape_instance/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" authors = ["Terrence Cole "] edition = "2018" +[lib] +doctest = false + [dependencies] failure = "^ 0.1.2" nalgebra = "^ 0.18" diff --git a/libs/render/draw/shape_instance/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs index 11975f46..fe29bb6d 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -13,68 +13,163 @@ // You should have received a copy of the GNU General Public License // along with OpenFA. If not, see . use failure::{bail, ensure, Fallible}; -use lib::Library; use nalgebra::Matrix4; use nalgebra::Point3; use omnilib::OmniLib; -use pal::Palette; -use shape_chunk::{Chunk, ClosedChunk, DrawSelection, OpenChunk, ShapeId}; -use std::{collections::HashMap, mem}; +use shape_chunk::{Chunk, ClosedChunk, DrawSelection, OpenChunk, ShapeId, Vertex}; +use specs::{world::Index, DispatcherBuilder, Entities, Join, ReadStorage, System, VecStorage}; +use std::{collections::HashMap, mem, sync::Arc}; use vulkano::{ buffer::{CpuAccessibleBuffer, CpuBufferPool}, command_buffer::{AutoCommandBufferBuilder, CommandBuffer, DrawIndirectCommand}, + framebuffer::Subpass, + pipeline::{ + depth_stencil::{Compare, DepthBounds, DepthStencil}, + GraphicsPipeline, GraphicsPipelineAbstract, + }, }; use window::GraphicsWindow; -use world::{Entity, World}; - -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 { - // Buffers for all instances stored in this instance set. One command per unique entity. - // 16 bytes per entity; index unnecessary for draw - command_buf: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, - - // Base position and orientation in xyz+euler angles stored as 6 adjacent floats. - // 24 bytes per entity; buffer index inferable from drawing index - base_buffer: CpuBufferPool<[[f32; 6]; BLOCK_SIZE]>, // Flags buffers +use world::{ + component::{ShapeMesh, Transform}, + Entity, World, +}; - // 2 32bit flags words for each entity. - // 8 bytes per entity; buffer index inferable from drawing index - flags_buffer: CpuBufferPool<[[u32; 2]; BLOCK_SIZE]>, +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; + + // 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]; + } - // 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: CpuBufferPool<[[f32; 6]; 4 * BLOCK_SIZE]>, + 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; - // 4 bytes per entity; can infer position from index - xform_index_buffer: CpuBufferPool<[i32; BLOCK_SIZE]>, + f_flags0 = flags0 & shape_flags.data[base_flag + 0]; + f_flags1 = flags1 & shape_flags.data[base_flag + 1]; + }" + } } -// Combines a single shape chunk with a collection of instance blocks. -// -// We are uploading data on every frame, so we need fixed sized upload pools. -// Each pool can only handle so many instances though, so we may need more than -// one block of pools to service every instance that needs vertices in a chunk. -pub struct ChunkInstances { - chunk: Chunk, - - // FIXME: we probably want to store these as traits so that we can have - // FIXME: blocks with different upload characteristics. - blocks: Vec, +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; + } + } + }" + } } +const BLOCK_SIZE: usize = 128; + +pub struct ShapeChunkManager { + pipeline: Arc, -pub struct ShapeInstanceRenderer { - //per_chunk: Vec, open_mover_chunk: OpenChunk, mover_chunks: Vec, } -impl ShapeInstanceRenderer { - pub fn new(window: &GraphicsWindow) -> Fallible { +impl ShapeChunkManager { + pub fn new( + pipeline: Arc, + window: &GraphicsWindow, + ) -> Fallible { Ok(Self { + pipeline, open_mover_chunk: OpenChunk::new(window)?, mover_chunks: Vec::new(), }) @@ -105,49 +200,148 @@ impl ShapeInstanceRenderer { )?; Ok(shape_id) } +} - /* - fn find_open_mover_chunk(&mut self, window: &GraphicsWindow) -> Fallible<&mut OpenChunk> { - for instances in &mut self.per_chunk { - if instances.chunk.is_open() { - return Ok(instances.chunk.as_open_chunk_mut()); - } - } +/* +// Combines a single shape chunk with a collection of instance blocks. +// +// We are uploading data on every frame, so we need fixed sized upload pools. +// Each pool can only handle so many instances though, so we may need more than +// one block of pools to service every instance that needs vertices in a chunk. +pub struct ChunkInstances { + chunk: Chunk, + + // FIXME: we probably want to store these as traits so that we can have + // FIXME: blocks with different upload characteristics. + blocks: Vec, +} +*/ + +// 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_offset: usize, + //chunk_type: ChunkType, - unimplemented!() - /* - self.per_chunk.push(ChunkInstances { - chunk: Chunk::Open(OpenChunk::new(window)?), + // Map from the entity to the stored offset and from the offset to the entity. + slot_map: HashMap, + reverse_slot_map: HashMap, + + // Buffers for all instances stored in this instance set. One command per unique entity. + // 16 bytes per entity; index unnecessary for draw + command_buf: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, + + // Base position and orientation in xyz+euler angles stored as 6 adjacent floats. + // 24 bytes per entity; buffer index inferable from drawing index + base_buffer: CpuBufferPool<[[f32; 6]; BLOCK_SIZE]>, // Flags buffers + + // 2 32bit flags words for each entity. + // 8 bytes per entity; buffer index inferable from drawing index + flags_buffer: CpuBufferPool<[[u32; 2]; BLOCK_SIZE]>, + + // 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: CpuBufferPool<[[f32; 6]; 4 * BLOCK_SIZE]>, + + // 4 bytes per entity; can infer position from index + xform_index_buffer: CpuBufferPool<[i32; BLOCK_SIZE]>, +} + +impl DynamicInstanceBlock {} + +struct ShapeRenderSystem { + chunks: ShapeChunkManager, + + // All upload blocks. We will do one draw call per instance block each frame. + blocks: Vec, + + // Map from the index to the block that it has a reserved upload slot in. + upload_block_map: HashMap, +} + +impl ShapeRenderSystem { + pub fn new(chunks: ShapeChunkManager) -> Self { + Self { + chunks, blocks: Vec::new(), - }); - self.find_open_mover_chunk(window) - */ + upload_block_map: HashMap::new(), + } } - */ -} -use specs::prelude::*; -use world::component::{ShapeMesh, Transform}; + pub 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) + } -struct ShapeRenderSystem; + // First fit: find the first block with a free upload slot. + fn reserve_free_slot(&mut self) -> (usize, usize) { + for (block_index, block) in self.blocks.iter().enumerate() { + if let Some(slot_index) = block.reserve_free_slot(id) { + return (block_index, slot_index); + } + } + + // No free slots in any blocks. Build a new one. + let block_index = self.blocks.len(); + self.blocks.push(DynamicInstanceBlock::new()); + let slot_index = self.blocks.last().unwrap().reserve_free_slot(id).unwrap(); + return (block_index, slot_index); + } + + pub fn reserve_entity_slot(&mut self, id: Index) -> Fallible<()> { + if self.upload_slots.contains_key(&id) { + return Ok(()); + } + let (block_index, slot_index) = self.reserve_free_slot(); + } +} impl<'a> System<'a> for ShapeRenderSystem { // These are the resources required for execution. // You can also define a struct and `#[derive(SystemData)]`, // see the `full` example. - type SystemData = (ReadStorage<'a, Transform>, ReadStorage<'a, ShapeMesh>); - - fn run(&mut self, (transform, shape_mesh): Self::SystemData) { - // The `.join()` combines multiple components, - // so we only access those entities which have - // both of them. - - // This joins the component storages for Position - // and Velocity together; it's also possible to do this - // in parallel using rayon's `ParallelIterator`s. - // See `ParJoin` for more. - for (transform, shape_mesh) in (&transform, &shape_mesh).join() { - println!("shape_id: {:?}", shape_mesh.shape_id()); + type SystemData = ( + Entities<'a>, + ReadStorage<'a, Transform>, + ReadStorage<'a, ShapeMesh>, + ); + + fn run(&mut self, (entities, transform, shape_mesh): Self::SystemData) { + for (entity, transform, shape_mesh) in (&entities, &transform, &shape_mesh).join() { + self.reserve_entity_slot(entity.id()); + // Check if entity has been uploaded. + // if not, then allocate slot and add map to mapping + // Update slot with new values from transform, flags, and xforms + // after all updates, push all buffer pools? + println!("{:?} => shape_id: {:?}", entity.id(), shape_mesh.shape_id()); + //self.push_mesh(transform, shape_mesh); } } } @@ -155,6 +349,7 @@ impl<'a> System<'a> for ShapeRenderSystem { #[cfg(test)] mod tests { use super::*; + use vulkano::pipeline::GraphicsPipeline; use window::GraphicsConfigBuilder; use world::World; @@ -167,16 +362,25 @@ mod tests { let mut world = World::new(lib)?; - let mut shape_renderer = ShapeInstanceRenderer::new(&window)?; + let pipeline = ShapeRenderSystem::build_pipeline(&window)?; + let mut shape_chunk_man = ShapeChunkManager::new(pipeline, &window)?; let t80_id = - shape_renderer.upload_mover("T80.SH", DrawSelection::NormalModel, &world, &window)?; + shape_chunk_man.upload_mover("T80.SH", DrawSelection::NormalModel, &world, &window)?; - let t80_ent = world.create_ground_mover(t80_id, Point3::new(0f64, 0f64, 0f64))?; + let t80_ent1 = world.create_ground_mover(t80_id, Point3::new(0f64, 0f64, 0f64))?; let mut dispatcher = DispatcherBuilder::new() - .with(ShapeRenderSystem, "", &[]) + .with(ShapeRenderSystem::new(shape_chunk_man), "", &[]) .build(); - //dispatcher.dispatch(&mut world); + 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(()) @@ -184,10 +388,6 @@ mod tests { } /* -pub struct Entity { - id: u64, -} - // Types of data we want to be able to deal with. // // Static Immortal: @@ -248,53 +448,4 @@ pub struct InstanceSet { // Total cost per entity is: 16 + 24 + 8 + 240 + 4 ~ 300 bytes per entity // We cannot really upload more than 1MiB per frame, so... ~3000 planes } - -pub struct ShapeInstanceRenderer { - open_chunk: Option, - chunks: Vec, - - // Map from the shape name to the chunk that shape is uploaded in. - chunk_map: HashMap, - - // Map from the entity to the chunk instance they belong in. - instance_map: HashMap, -} - -impl ShapeInstanceRenderer { - pub fn new(window: &GraphicsWindow) -> Fallible { - Ok(Self { - open_chunk: Some(OpenChunk::new(window)?), - chunks: Vec::new(), - }) - } - - pub fn add_static_immortal_model( - &mut self, - // TODO: position and orientation - shape_name: &str, - pal: &Palette, - lib: &Library, - window: &GraphicsWindow, - ) -> Fallible<()> { - ensure!( - self.open_chunk.is_some(), - "shape instances are already finished" - ); - - // Note: immortal implies a non-damage model - self.open_chunk.as_mut().unwrap().upload_shape( - shape_name, - DrawSelection::NormalModel, - &pal, - &lib, - &window, - )?; - - Ok(()) - } - - // Close the last open chunk and prepare for rendering. - pub fn finish_loading() {} -} - */ diff --git a/libs/sh/src/instr/code.rs b/libs/sh/src/instr/code.rs index d86201e7..52350cc4 100644 --- a/libs/sh/src/instr/code.rs +++ b/libs/sh/src/instr/code.rs @@ -20,7 +20,12 @@ use lazy_static::lazy_static; use log::trace; use peff::{Thunk, PE}; use reverse::bs2s; -use std::{cell::RefCell, cmp, collections::HashSet, mem, rc::Rc}; +use std::{ + cmp, + collections::HashSet, + mem, + sync::{Arc, RwLock}, +}; lazy_static! { pub static ref DATA_RELOCATIONS: HashSet = { @@ -231,7 +236,7 @@ pub struct X86Code { pub offset: usize, pub length: usize, pub data: *const u8, - pub bytecode: Rc>, + pub bytecode: Arc>, pub have_header: bool, } @@ -410,7 +415,7 @@ impl X86Code { offset: section_offset, length: section_length, data: pe.code[section_offset..].as_ptr(), - bytecode: bc.into_rc(), + bytecode: bc.into_arc(), have_header, }) } @@ -599,7 +604,11 @@ impl X86Code { ansi().green().bold(), hdr, ansi(), - self.bytecode.borrow().show_relative(show_offset).trim() + self.bytecode + .read() + .unwrap() + .show_relative(show_offset) + .trim() ) } } diff --git a/libs/world/src/lib.rs b/libs/world/src/lib.rs index 31645a75..c7609c7c 100644 --- a/libs/world/src/lib.rs +++ b/libs/world/src/lib.rs @@ -49,7 +49,9 @@ impl World { } pub fn run(&mut self, dispatcher: &mut Dispatcher) { - dispatcher.dispatch(&mut self.ecs) + println!("RUN:"); + dispatcher.dispatch(&mut self.ecs); + self.ecs.maintain(); } pub fn library(&self) -> &Library { From eb0223592c0743252f210d448641c975e5530111 Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sun, 1 Sep 2019 21:47:29 -0700 Subject: [PATCH 07/16] Fix basic bustage from our library changes. --- apps/dump-sh/src/main.rs | 4 ++-- libs/fnt/src/lib.rs | 12 ++++++++---- libs/render/draw/shape/src/upload.rs | 3 ++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/dump-sh/src/main.rs b/apps/dump-sh/src/main.rs index dc95c694..ceb04038 100644 --- a/apps/dump-sh/src/main.rs +++ b/apps/dump-sh/src/main.rs @@ -104,7 +104,7 @@ fn main() -> Fallible<()> { for sh_instr in shape.instrs.iter() { if let sh::Instr::X86Code(x86) = sh_instr { let mut pos = 0; - for instr in &x86.bytecode.borrow().instrs { + for instr in &x86.bytecode.read().unwrap().instrs { for operand in &instr.operands { if let i386::Operand::Memory(memref) = operand { if let Ok(tramp) = shape.lookup_trampoline_by_offset( @@ -146,7 +146,7 @@ fn main() -> Fallible<()> { let mut dedup = HashMap::new(); for vinstr in shape.instrs { if let sh::Instr::X86Code(x86) = vinstr { - for instr in &x86.bytecode.borrow().instrs { + for instr in &x86.bytecode.read().unwrap().instrs { for operand in &instr.operands { if let i386::Operand::Memory(memref) = operand { let key = format!("{}", memref); diff --git a/libs/fnt/src/lib.rs b/libs/fnt/src/lib.rs index 6eaf75a3..b60d6851 100644 --- a/libs/fnt/src/lib.rs +++ b/libs/fnt/src/lib.rs @@ -19,7 +19,11 @@ use failure::{bail, ensure, Fallible}; use i386::{ByteCode, Interpreter, Reg}; use image::{ImageBuffer, LumaA}; use peff::PE; -use std::{cell::RefCell, collections::HashMap, mem, rc::Rc}; +use std::{ + collections::HashMap, + mem, + sync::{Arc, RwLock}, +}; // Save chars to png when testing. const DUMP_CHARS: bool = false; @@ -28,7 +32,7 @@ pub struct GlyphInfo { pub glyph_index: u8, pub glyph_char: String, pub width: i32, - pub bytecode: Rc>, + pub bytecode: Arc>, } pub struct Fnt { @@ -92,7 +96,7 @@ impl Fnt { glyph_index, glyph_char, width, - bytecode: bytecode.into_rc(), + bytecode: bytecode.into_arc(), }, ); } @@ -109,7 +113,7 @@ impl Fnt { } let glyph = &self.glyphs[&glyph_index]; println!("{:<2} - {:04X}:", glyph.glyph_char, glyph.glyph_index); - println!("{}", glyph.bytecode.borrow().show_relative(0)); + println!("{}", glyph.bytecode.read().unwrap().show_relative(0)); { let mut interp = Interpreter::new(); diff --git a/libs/render/draw/shape/src/upload.rs b/libs/render/draw/shape/src/upload.rs index f8128259..49961815 100644 --- a/libs/render/draw/shape/src/upload.rs +++ b/libs/render/draw/shape/src/upload.rs @@ -29,6 +29,7 @@ use pal::Palette; use pic::Pic; use sh::{Facet, FacetFlags, Instr, RawShape, VertexBuf, X86Code, X86Trampoline, SHAPE_LOAD_BASE}; use std::{ + cell::RefCell, collections::{HashMap, HashSet}, sync::{Arc, RwLock}, time::Instant, @@ -433,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: Arc>, + vm: Arc>, code_offset: u32, data_offset: u32, inputs: Vec, From 0bd29b3b4c4f6be98d69a07cdba7da84c3256050 Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Mon, 2 Sep 2019 09:35:43 -0700 Subject: [PATCH 08/16] Slot reservations. --- libs/render/buffer/shape_chunk/src/chunk.rs | 8 + libs/render/draw/shape_instance/src/lib.rs | 167 +++++++++++++++----- libs/window/src/lib.rs | 4 + libs/world/src/lib.rs | 2 +- 4 files changed, 142 insertions(+), 39 deletions(-) diff --git a/libs/render/buffer/shape_chunk/src/chunk.rs b/libs/render/buffer/shape_chunk/src/chunk.rs index 0f79574c..5c184d27 100644 --- a/libs/render/buffer/shape_chunk/src/chunk.rs +++ b/libs/render/buffer/shape_chunk/src/chunk.rs @@ -191,6 +191,10 @@ impl OpenChunk { pub fn atlas_mut(&mut self) -> &mut MegaAtlas { &mut self.atlas_builder } + + pub fn part(&self, id: ShapeId) -> Option<&ChunkPart> { + self.chunk_parts.get(&id) + } } pub struct ClosedChunk { @@ -257,6 +261,10 @@ impl ClosedChunk { self.vertex_buffer.clone() } + pub fn part(&self, id: ShapeId) -> Option<&ChunkPart> { + self.chunk_parts.get(&id) + } + pub fn part_for(&self, name: &str) -> Fallible<&ChunkPart> { let id = self .shape_ids diff --git a/libs/render/draw/shape_instance/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs index fe29bb6d..397c37a1 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -17,16 +17,20 @@ use nalgebra::Matrix4; use nalgebra::Point3; use omnilib::OmniLib; use shape_chunk::{Chunk, ClosedChunk, DrawSelection, OpenChunk, ShapeId, Vertex}; -use specs::{world::Index, DispatcherBuilder, Entities, Join, ReadStorage, System, VecStorage}; +use specs::{ + world::Index as EntityId, DispatcherBuilder, Entities, Join, ReadStorage, System, VecStorage, +}; use std::{collections::HashMap, mem, sync::Arc}; use vulkano::{ - buffer::{CpuAccessibleBuffer, CpuBufferPool}, - command_buffer::{AutoCommandBufferBuilder, CommandBuffer, DrawIndirectCommand}, + buffer::{BufferUsage, CpuBufferPool}, + command_buffer::DrawIndirectCommand, + device::Device, framebuffer::Subpass, pipeline::{ depth_stencil::{Compare, DepthBounds, DepthStencil}, GraphicsPipeline, GraphicsPipelineAbstract, }, + sync::GpuFuture, }; use window::GraphicsWindow; use world::{ @@ -179,18 +183,30 @@ impl ShapeChunkManager { // pub fn create_airplane -- need to hook into shape state? + pub fn finish(&mut self, window: &GraphicsWindow) -> Fallible> { + self.finish_open_chunk(window) + } + + pub fn finish_open_chunk(&mut self, window: &GraphicsWindow) -> Fallible> { + let mut open_chunk = OpenChunk::new(window)?; + mem::swap(&mut open_chunk, &mut self.open_mover_chunk); + let (chunk, future) = ClosedChunk::new(open_chunk, self.pipeline.clone(), window)?; + self.mover_chunks.push(chunk); + Ok(future) + } + pub fn upload_mover( &mut self, name: &str, selection: DrawSelection, world: &World, window: &GraphicsWindow, - ) -> Fallible { - if self.open_mover_chunk.chunk_is_full() { - let mut open_chunk = OpenChunk::new(window)?; - mem::swap(&mut open_chunk, &mut self.open_mover_chunk); - //self.mover_chunks.push(ClosedChunk::new(open_chunk, pipeline, window)) - } + ) -> Fallible<(ShapeId, Box)> { + let future = if self.open_mover_chunk.chunk_is_full() { + self.finish_open_chunk(window)? + } else { + window.now() + }; let shape_id = self.open_mover_chunk.upload_shape( name, selection, @@ -198,7 +214,17 @@ impl ShapeChunkManager { world.library(), window, )?; - Ok(shape_id) + Ok((shape_id, future)) + } + + // TODO: we should maybe speed this up with a hash from shape_id to chunk_index + fn find_chunk_for_shape(&mut self, shape_id: ShapeId) -> Fallible { + for (chunk_offset, chunk) in self.mover_chunks.iter().enumerate() { + if chunk.part(shape_id).is_some() { + return Ok(ChunkIndex(chunk_offset)); + } + } + bail!("shape_id {:?} has not been uploaded", shape_id) } } @@ -217,20 +243,29 @@ pub struct ChunkInstances { } */ +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct ChunkIndex(usize); + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct BlockIndex(usize); + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct SlotIndex(usize); + // 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_offset: usize, + chunk_index: ChunkIndex, //chunk_type: ChunkType, // Map from the entity to the stored offset and from the offset to the entity. - slot_map: HashMap, - reverse_slot_map: HashMap, + slot_reservations: [Option; BLOCK_SIZE], + entity_to_slot_map: HashMap, // Buffers for all instances stored in this instance set. One command per unique entity. // 16 bytes per entity; index unnecessary for draw - command_buf: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, + command_buffer: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, // Base position and orientation in xyz+euler angles stored as 6 adjacent floats. // 24 bytes per entity; buffer index inferable from drawing index @@ -249,7 +284,39 @@ pub struct DynamicInstanceBlock { xform_index_buffer: CpuBufferPool<[i32; BLOCK_SIZE]>, } -impl DynamicInstanceBlock {} +impl DynamicInstanceBlock { + fn new(chunk_index: ChunkIndex, device: Arc) -> Fallible { + Ok(Self { + chunk_index, + slot_reservations: [None; BLOCK_SIZE], + entity_to_slot_map: HashMap::new(), + command_buffer: CpuBufferPool::new(device.clone(), BufferUsage::indirect_buffer()), + base_buffer: CpuBufferPool::new(device.clone(), BufferUsage::index_buffer()), + flags_buffer: CpuBufferPool::new(device.clone(), BufferUsage::index_buffer()), + xform_buffer: CpuBufferPool::new(device.clone(), BufferUsage::index_buffer()), + xform_index_buffer: CpuBufferPool::new(device, BufferUsage::index_buffer()), + }) + } + + fn reserve_slot_for(&mut self, slot: SlotIndex, id: EntityId) { + self.slot_reservations[slot.0] = Some(id); + self.entity_to_slot_map.insert(id, slot); + } + + fn reserve_free_slot(&mut self, id: EntityId, chunk_index: ChunkIndex) -> Option { + if chunk_index != self.chunk_index { + return None; + } + for (slot_offset, entity_id) in self.slot_reservations.iter().enumerate() { + if entity_id.is_none() { + let slot = SlotIndex(slot_offset); + self.reserve_slot_for(slot, id); + return Some(slot); + } + } + None + } +} struct ShapeRenderSystem { chunks: ShapeChunkManager, @@ -258,15 +325,18 @@ struct ShapeRenderSystem { blocks: Vec, // Map from the index to the block that it has a reserved upload slot in. - upload_block_map: HashMap, + upload_block_map: HashMap, + + device: Arc, } impl ShapeRenderSystem { - pub fn new(chunks: ShapeChunkManager) -> Self { + pub fn new(chunks: ShapeChunkManager, device: Arc) -> Self { Self { chunks, blocks: Vec::new(), upload_block_map: HashMap::new(), + device, } } @@ -301,25 +371,43 @@ impl ShapeRenderSystem { } // First fit: find the first block with a free upload slot. - fn reserve_free_slot(&mut self) -> (usize, usize) { - for (block_index, block) in self.blocks.iter().enumerate() { - if let Some(slot_index) = block.reserve_free_slot(id) { - return (block_index, slot_index); + fn reserve_free_slot( + &mut self, + id: EntityId, + shape_id: ShapeId, + device: Arc, + ) -> Fallible { + let chunk_index = self.chunks.find_chunk_for_shape(shape_id)?; + + for (block_index, block) in self.blocks.iter_mut().enumerate() { + if let Some(_) = block.reserve_free_slot(id, chunk_index) { + return Ok(BlockIndex(block_index)); } } // No free slots in any blocks. Build a new one. - let block_index = self.blocks.len(); - self.blocks.push(DynamicInstanceBlock::new()); - let slot_index = self.blocks.last().unwrap().reserve_free_slot(id).unwrap(); - return (block_index, slot_index); + let next_block_index = BlockIndex(self.blocks.len()); + self.blocks + .push(DynamicInstanceBlock::new(chunk_index, device)?); + let slot_index = self + .blocks + .last_mut() + .unwrap() + .reserve_free_slot(id, chunk_index) + .unwrap(); + Ok(next_block_index) } - pub fn reserve_entity_slot(&mut self, id: Index) -> Fallible<()> { - if self.upload_slots.contains_key(&id) { - return Ok(()); + pub fn reserve_entity_slot( + &mut self, + id: EntityId, + shape_id: ShapeId, + device: Arc, + ) -> Fallible { + if let Some(block_index) = self.upload_block_map.get(&id) { + return Ok(*block_index); } - let (block_index, slot_index) = self.reserve_free_slot(); + self.reserve_free_slot(id, shape_id, device) } } @@ -335,13 +423,11 @@ impl<'a> System<'a> for ShapeRenderSystem { fn run(&mut self, (entities, transform, shape_mesh): Self::SystemData) { for (entity, transform, shape_mesh) in (&entities, &transform, &shape_mesh).join() { - self.reserve_entity_slot(entity.id()); - // Check if entity has been uploaded. - // if not, then allocate slot and add map to mapping - // Update slot with new values from transform, flags, and xforms - // after all updates, push all buffer pools? - println!("{:?} => shape_id: {:?}", entity.id(), shape_mesh.shape_id()); - //self.push_mesh(transform, shape_mesh); + let block_index = self + .reserve_entity_slot(entity.id(), shape_mesh.shape_id(), self.device.clone()) + .expect("unable to reserve instance slot"); + //self.blocks[block_index]; + println!("{:?} => block_index: {:?}", entity.id(), block_index); } } } @@ -364,13 +450,18 @@ mod tests { let pipeline = ShapeRenderSystem::build_pipeline(&window)?; let mut shape_chunk_man = ShapeChunkManager::new(pipeline, &window)?; - let t80_id = + let (t80_id, fut1) = shape_chunk_man.upload_mover("T80.SH", DrawSelection::NormalModel, &world, &window)?; + let future = shape_chunk_man.finish(&window)?; let t80_ent1 = world.create_ground_mover(t80_id, Point3::new(0f64, 0f64, 0f64))?; let mut dispatcher = DispatcherBuilder::new() - .with(ShapeRenderSystem::new(shape_chunk_man), "", &[]) + .with( + ShapeRenderSystem::new(shape_chunk_man, window.device()), + "", + &[], + ) .build(); world.run(&mut dispatcher); let t80_ent2 = world.create_ground_mover(t80_id, Point3::new(0f64, 0f64, 0f64))?; 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() } diff --git a/libs/world/src/lib.rs b/libs/world/src/lib.rs index c7609c7c..ef14710b 100644 --- a/libs/world/src/lib.rs +++ b/libs/world/src/lib.rs @@ -93,7 +93,7 @@ mod test { let omni = OmniLib::new_for_test_in_games(&["FA"])?; let mut world = World::new(omni.library("FA"))?; let window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; - let mut upload = OpenChunk::new(0, &window)?; + let mut upload = OpenChunk::new(&window)?; let shape_id = upload.upload_shape( "T80.SH", DrawSelection::NormalModel, From a458bc1dd8bcd9423b822ea10284a02744e915f5 Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Tue, 3 Sep 2019 11:06:17 -0700 Subject: [PATCH 09/16] Everything but the upload. --- .../{demo-instances.rs => demo-chunks.rs} | 31 +- .../buffer/shape_chunk/src/chunk_manager.rs | 100 ++++ libs/render/buffer/shape_chunk/src/lib.rs | 2 + libs/render/draw/shape_instance/Cargo.toml | 6 + .../shape_instance/examples/demo-instances.rs | 306 +++++++++++ libs/render/draw/shape_instance/src/lib.rs | 515 ++++++++++++------ libs/world/src/component/flight_dynamics.rs | 8 + libs/world/src/lib.rs | 33 +- 8 files changed, 826 insertions(+), 175 deletions(-) rename libs/render/buffer/shape_chunk/examples/{demo-instances.rs => demo-chunks.rs} (94%) create mode 100644 libs/render/buffer/shape_chunk/src/chunk_manager.rs create mode 100644 libs/render/draw/shape_instance/examples/demo-instances.rs diff --git a/libs/render/buffer/shape_chunk/examples/demo-instances.rs b/libs/render/buffer/shape_chunk/examples/demo-chunks.rs similarity index 94% rename from libs/render/buffer/shape_chunk/examples/demo-instances.rs rename to libs/render/buffer/shape_chunk/examples/demo-chunks.rs index e44a450f..1e11bc76 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}, @@ -52,9 +51,10 @@ mod vs { mat4 projection; } pc; - // Per shape input const uint MAX_XFORM_ID = 32; - layout(set = 3, binding = 0) buffer ChunkBaseTransforms { + + // Per shape input + layout(set = 3, binding = 0) buffer ChunkTransforms { float data[]; } shape_transforms; layout(set = 3, binding = 1) buffer ChunkFlags { @@ -250,23 +250,26 @@ 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_index = chunk_man.find_chunk_for_shape(f18_id)?; + let chunk = chunk_man.at(chunk_index); + let f18_part = chunk.part(f18_id).unwrap(); - // Upload tranforms - let transforms = vec![0, 0, 0, 0, 0, 0]; - let transforms_buffer = CpuAccessibleBuffer::from_iter( + // 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(), @@ -345,7 +348,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()?; @@ -379,8 +382,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_manager.rs b/libs/render/buffer/shape_chunk/src/chunk_manager.rs new file mode 100644 index 00000000..50c5f3f7 --- /dev/null +++ b/libs/render/buffer/shape_chunk/src/chunk_manager.rs @@ -0,0 +1,100 @@ +// 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 crate::{ + chunk::{ClosedChunk, OpenChunk, ShapeId}, + upload::DrawSelection, +}; +use failure::{bail, Fallible}; +use lib::Library; +use pal::Palette; +use std::{mem, sync::Arc}; +use vulkano::{pipeline::GraphicsPipelineAbstract, sync::GpuFuture}; +use window::GraphicsWindow; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct ChunkIndex(usize); + +pub struct ShapeChunkManager { + pipeline: Arc, + + open_chunk: OpenChunk, + closed_chunks: Vec, +} + +impl ShapeChunkManager { + pub fn new( + pipeline: Arc, + window: &GraphicsWindow, + ) -> Fallible { + Ok(Self { + pipeline, + open_chunk: OpenChunk::new(window)?, + closed_chunks: Vec::new(), + }) + } + + // pub fn create_building + + // pub fn create_airplane -- need to hook into shape state? + + pub fn get_chunk(&self, chunk_index: ChunkIndex) -> &ClosedChunk { + &self.closed_chunks[chunk_index.0] + } + + pub fn finish(&mut self, window: &GraphicsWindow) -> Fallible> { + self.finish_open_chunk(window) + } + + pub fn finish_open_chunk(&mut self, window: &GraphicsWindow) -> Fallible> { + let mut open_chunk = OpenChunk::new(window)?; + mem::swap(&mut open_chunk, &mut self.open_chunk); + let (chunk, future) = ClosedChunk::new(open_chunk, self.pipeline.clone(), window)?; + self.closed_chunks.push(chunk); + Ok(future) + } + + pub fn upload_shape( + &mut self, + name: &str, + selection: DrawSelection, + palette: &Palette, + lib: &Library, + window: &GraphicsWindow, + ) -> Fallible<(ShapeId, Box)> { + let future = if self.open_chunk.chunk_is_full() { + self.finish_open_chunk(window)? + } else { + window.now() + }; + let shape_id = self + .open_chunk + .upload_shape(name, selection, palette, lib, window)?; + Ok((shape_id, future)) + } + + // TODO: we should maybe speed this up with a hash from shape_id to chunk_index + pub fn find_chunk_for_shape(&self, shape_id: ShapeId) -> Fallible { + for (chunk_offset, chunk) in self.closed_chunks.iter().enumerate() { + if chunk.part(shape_id).is_some() { + return Ok(ChunkIndex(chunk_offset)); + } + } + bail!("shape_id {:?} has not been uploaded", shape_id) + } + + pub fn at(&self, chunk_index: ChunkIndex) -> &ClosedChunk { + &self.closed_chunks[chunk_index.0] + } +} diff --git a/libs/render/buffer/shape_chunk/src/lib.rs b/libs/render/buffer/shape_chunk/src/lib.rs index 1c60d392..3f506c11 100644 --- a/libs/render/buffer/shape_chunk/src/lib.rs +++ b/libs/render/buffer/shape_chunk/src/lib.rs @@ -13,11 +13,13 @@ // You should have received a copy of the GNU General Public License // along with OpenFA. If not, see . mod chunk; +mod chunk_manager; mod draw_state; mod texture_atlas; mod upload; pub use chunk::{Chunk, ClosedChunk, OpenChunk, ShapeId}; +pub use chunk_manager::{ChunkIndex, ShapeChunkManager}; pub use draw_state::DrawState; pub use upload::{DrawSelection, Vertex}; diff --git a/libs/render/draw/shape_instance/Cargo.toml b/libs/render/draw/shape_instance/Cargo.toml index 9abe1018..35232b6a 100644 --- a/libs/render/draw/shape_instance/Cargo.toml +++ b/libs/render/draw/shape_instance/Cargo.toml @@ -13,9 +13,15 @@ nalgebra = "^ 0.18" 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] +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..6ee862c6 --- /dev/null +++ b/libs/render/draw/shape_instance/examples/demo-instances.rs @@ -0,0 +1,306 @@ +// 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, CameraAbstract}; +use failure::Fallible; +use global_layout::GlobalSets; +use input::{InputBindings, InputSystem}; +use nalgebra::{Matrix4, Point3}; +use omnilib::OmniLib; +use pal::Palette; +use shape_chunk::{DrawSelection, DrawState, ShapeChunkManager, Vertex}; +use shape_instance::{ShapeRenderSystem, ShapeRenderer}; +use specs::{ + world::Index as EntityId, DispatcherBuilder, Entities, Join, ReadStorage, System, VecStorage, +}; +use std::{sync::Arc, time::Instant}; +use vulkano::{ + buffer::{BufferUsage, CpuAccessibleBuffer}, + command_buffer::AutoCommandBufferBuilder, + descriptor::descriptor_set::PersistentDescriptorSet, + framebuffer::Subpass, + pipeline::{ + depth_stencil::{Compare, DepthBounds, DepthStencil}, + GraphicsPipeline, GraphicsPipelineAbstract, + }, + sync::GpuFuture, +}; +use window::{GraphicsConfigBuilder, GraphicsWindow}; +use world::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; + + 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; + 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; + } + } + }" + } +} + +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 shape_renderer = Arc::new(ShapeRenderer::new(world.clone(), &window)?); + let (f8_id, _) = shape_renderer.upload_shape("F8.SH", DrawSelection::NormalModel, &window)?; + let (f18_id, _) = shape_renderer.upload_shape("F18.SH", DrawSelection::NormalModel, &window)?; + let future = shape_renderer.ensure_uploaded(&window)?; + + future.then_signal_fence_and_flush()?.wait(None)?; + + let f18_ent1 = world.create_flyer(f18_id, Point3::new(0f64, 0f64, 0f64))?; + let f18_ent2 = world.create_flyer(f18_id, Point3::new(40f64, -10f64, 10f64))?; + + // Pump the renderer once to upload all of our buffers + let shape_render_system = ShapeRenderSystem::new(shape_renderer.clone()); + let mut shape_instance_updater = DispatcherBuilder::new() + .with(shape_render_system, "", &[]) + .build(); + world.run(&mut shape_instance_updater); + + /* + let chunk_index = chunk_man.find_chunk_for_shape(f18_id)?; + let chunk = chunk_man.at(chunk_index); + let f18_part = chunk.part(f18_id).unwrap(); + + // 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(); + draw_state.toggle_gear(&Instant::now()); + let mut flags_arr = [0u32; 2]; + draw_state.build_mask_into( + draw_state.time_origin(), + f18_part.widgets().errata(), + &mut flags_arr[0..2], + )?; + let flags_buffer = CpuAccessibleBuffer::from_iter( + window.device(), + BufferUsage::all(), + flags_arr.iter().cloned(), + )?; + + // Upload xforms + let now = Instant::now(); + let xforms_len = f18_part.widgets().num_transformer_floats(); + let mut xforms = Vec::with_capacity(xforms_len); + xforms.resize(xforms_len, 0f32); + f18_part + .widgets() + .animate_into(&draw_state, draw_state.time_origin(), &now, &mut xforms)?; + let xforms_buffer = CpuAccessibleBuffer::from_iter( + window.device(), + BufferUsage::all(), + 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()?, + ); + + let indirect_buffer = CpuAccessibleBuffer::from_iter( + window.device(), + BufferUsage::all(), + [f18_part.draw_command(0, 1)].iter().cloned(), + )?; + */ + + let mut camera = ArcBallCamera::new(window.aspect_ratio_f64()?, 0.1, 3.4e+38); + camera.set_distance(80.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()?; + + { + let frame = window.begin_frame()?; + if !frame.is_valid() { + continue; + } + + let mut cbb = AutoCommandBufferBuilder::primary_one_time_submit( + window.device(), + window.queue().family(), + )?; + + 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)?; + } + } +} diff --git a/libs/render/draw/shape_instance/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs index 397c37a1..fccf6288 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -12,20 +12,31 @@ // // You should have received a copy of the GNU General Public License // along with OpenFA. If not, see . +use camera::CameraAbstract; use failure::{bail, ensure, Fallible}; +use global_layout::GlobalSets; use nalgebra::Matrix4; use nalgebra::Point3; use omnilib::OmniLib; -use shape_chunk::{Chunk, ClosedChunk, DrawSelection, OpenChunk, ShapeId, Vertex}; +use shape_chunk::{ + Chunk, ChunkIndex, ClosedChunk, DrawSelection, OpenChunk, ShapeChunkManager, ShapeId, Vertex, +}; use specs::{ world::Index as EntityId, DispatcherBuilder, Entities, Join, ReadStorage, System, VecStorage, }; -use std::{collections::HashMap, mem, sync::Arc}; +use std::{ + collections::HashMap, + mem, + sync::{Arc, RwLock}, +}; +use vulkano::command_buffer::AutoCommandBufferBuilder; use vulkano::{ - buffer::{BufferUsage, CpuBufferPool}, + buffer::{BufferAccess, BufferSlice, BufferUsage, CpuBufferPool, DeviceLocalBuffer}, command_buffer::DrawIndirectCommand, + descriptor::descriptor_set::{DescriptorSet, PersistentDescriptorSet}, device::Device, framebuffer::Subpass, + instance::QueueFamily, pipeline::{ depth_stencil::{Compare, DepthBounds, DepthStencil}, GraphicsPipeline, GraphicsPipelineAbstract, @@ -158,93 +169,63 @@ mod fs { }" } } -const BLOCK_SIZE: usize = 128; - -pub struct ShapeChunkManager { - pipeline: Arc, - - open_mover_chunk: OpenChunk, - mover_chunks: Vec, -} - -impl ShapeChunkManager { - pub fn new( - pipeline: Arc, - window: &GraphicsWindow, - ) -> Fallible { - Ok(Self { - pipeline, - open_mover_chunk: OpenChunk::new(window)?, - mover_chunks: Vec::new(), - }) - } - - // pub fn create_building - // pub fn create_airplane -- need to hook into shape state? - - pub fn finish(&mut self, window: &GraphicsWindow) -> Fallible> { - self.finish_open_chunk(window) +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], + ], + } } - pub fn finish_open_chunk(&mut self, window: &GraphicsWindow) -> Fallible> { - let mut open_chunk = OpenChunk::new(window)?; - mem::swap(&mut open_chunk, &mut self.open_mover_chunk); - let (chunk, future) = ClosedChunk::new(open_chunk, self.pipeline.clone(), window)?; - self.mover_chunks.push(chunk); - Ok(future) + 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]; } - pub fn upload_mover( - &mut self, - name: &str, - selection: DrawSelection, - world: &World, - window: &GraphicsWindow, - ) -> Fallible<(ShapeId, Box)> { - let future = if self.open_mover_chunk.chunk_is_full() { - self.finish_open_chunk(window)? - } else { - window.now() - }; - let shape_id = self.open_mover_chunk.upload_shape( - name, - selection, - world.system_palette(), - world.library(), - window, - )?; - Ok((shape_id, future)) + 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]; } - - // TODO: we should maybe speed this up with a hash from shape_id to chunk_index - fn find_chunk_for_shape(&mut self, shape_id: ShapeId) -> Fallible { - for (chunk_offset, chunk) in self.mover_chunks.iter().enumerate() { - if chunk.part(shape_id).is_some() { - return Ok(ChunkIndex(chunk_offset)); - } - } - bail!("shape_id {:?} has not been uploaded", shape_id) - } -} - -/* -// Combines a single shape chunk with a collection of instance blocks. -// -// We are uploading data on every frame, so we need fixed sized upload pools. -// Each pool can only handle so many instances though, so we may need more than -// one block of pools to service every instance that needs vertices in a chunk. -pub struct ChunkInstances { - chunk: Chunk, - - // FIXME: we probably want to store these as traits so that we can have - // FIXME: blocks with different upload characteristics. - blocks: Vec, } -*/ - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct ChunkIndex(usize); #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct BlockIndex(usize); @@ -252,6 +233,8 @@ 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 { @@ -260,87 +243,233 @@ pub struct DynamicInstanceBlock { //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, + slot_reservations: RwLock<[Option; BLOCK_SIZE]>, + entity_to_slot_map: RwLock>, + command_buffer_dirty_bit: RwLock, + + 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: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, + command_buffer: Arc>, + command_buffer_pool: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, // Base position and orientation in xyz+euler angles stored as 6 adjacent floats. // 24 bytes per entity; buffer index inferable from drawing index - base_buffer: CpuBufferPool<[[f32; 6]; BLOCK_SIZE]>, // Flags buffers + transform_buffer: Arc>, + transform_buffer_pool: CpuBufferPool<[[f32; 6]; BLOCK_SIZE]>, // 2 32bit flags words for each entity. // 8 bytes per entity; buffer index inferable from drawing index - flags_buffer: CpuBufferPool<[[u32; 2]; BLOCK_SIZE]>, + flag_buffer: Arc>, + flag_buffer_pool: CpuBufferPool<[[u32; 2]; BLOCK_SIZE]>, + + // 4 bytes per entity; can infer position from index + xform_index_buffer: Arc>, + xform_index_buffer_pool: CpuBufferPool<[i32; BLOCK_SIZE]>, // 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: CpuBufferPool<[[f32; 6]; 4 * BLOCK_SIZE]>, - - // 4 bytes per entity; can infer position from index - xform_index_buffer: CpuBufferPool<[i32; BLOCK_SIZE]>, + xform_buffer: Arc>, + xform_buffer_pool: CpuBufferPool<[[f32; 6]; 14 * BLOCK_SIZE]>, } impl DynamicInstanceBlock { - fn new(chunk_index: ChunkIndex, device: Arc) -> Fallible { + fn new( + chunk_index: ChunkIndex, + pipeline: Arc, + command_buffer_pool: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, + transform_buffer_pool: CpuBufferPool<[[f32; 6]; BLOCK_SIZE]>, + flag_buffer_pool: CpuBufferPool<[[u32; 2]; BLOCK_SIZE]>, + xform_index_buffer_pool: CpuBufferPool<[i32; BLOCK_SIZE]>, + xform_buffer_pool: CpuBufferPool<[[f32; 6]; 14 * BLOCK_SIZE]>, + device: Arc, + ) -> Fallible { + let command_buffer = DeviceLocalBuffer::array( + device.clone(), + BLOCK_SIZE, + BufferUsage::indirect_buffer(), + device.active_queue_families(), + )?; + let transform_buffer = DeviceLocalBuffer::new( + device.clone(), + BufferUsage::all(), + device.active_queue_families(), + )?; + let flag_buffer = DeviceLocalBuffer::new( + device.clone(), + BufferUsage::all(), + device.active_queue_families(), + )?; + let xform_index_buffer = DeviceLocalBuffer::new( + device.clone(), + BufferUsage::all(), + device.active_queue_families(), + )?; + let xform_buffer = DeviceLocalBuffer::new( + device.clone(), + 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(), - command_buffer: CpuBufferPool::new(device.clone(), BufferUsage::indirect_buffer()), - base_buffer: CpuBufferPool::new(device.clone(), BufferUsage::index_buffer()), - flags_buffer: CpuBufferPool::new(device.clone(), BufferUsage::index_buffer()), - xform_buffer: CpuBufferPool::new(device.clone(), BufferUsage::index_buffer()), - xform_index_buffer: CpuBufferPool::new(device, BufferUsage::index_buffer()), + slot_reservations: RwLock::new([None; BLOCK_SIZE]), + entity_to_slot_map: RwLock::new(HashMap::new()), + command_buffer_dirty_bit: RwLock::new(false), + descriptor_set, + pds0: GraphicsWindow::empty_descriptor_set(pipeline.clone(), 0)?, + pds1: GraphicsWindow::empty_descriptor_set(pipeline.clone(), 1)?, + pds2: GraphicsWindow::empty_descriptor_set(pipeline.clone(), 2)?, + command_buffer, + command_buffer_pool, + transform_buffer, + transform_buffer_pool, + flag_buffer, + flag_buffer_pool, + xform_index_buffer, + xform_index_buffer_pool, + xform_buffer, + xform_buffer_pool, }) } - fn reserve_slot_for(&mut self, slot: SlotIndex, id: EntityId) { - self.slot_reservations[slot.0] = Some(id); - self.entity_to_slot_map.insert(id, slot); + fn reserve_slot_for(&self, slot: SlotIndex, id: EntityId) { + self.slot_reservations.write().unwrap()[slot.0] = Some(id); + self.entity_to_slot_map.write().unwrap().insert(id, slot); + *self.command_buffer_dirty_bit.write().unwrap() = true; } - fn reserve_free_slot(&mut self, id: EntityId, chunk_index: ChunkIndex) -> Option { - if chunk_index != self.chunk_index { - return None; - } - for (slot_offset, entity_id) in self.slot_reservations.iter().enumerate() { + fn find_free_slot(&self) -> Option { + for (slot_offset, entity_id) in self.slot_reservations.read().unwrap().iter().enumerate() { if entity_id.is_none() { - let slot = SlotIndex(slot_offset); - self.reserve_slot_for(slot, id); - return Some(slot); + return Some(SlotIndex(slot_offset)); } } None } + + fn reserve_free_slot(&self, id: EntityId, chunk_index: ChunkIndex) -> Option { + if chunk_index != self.chunk_index { + return None; + } + let maybe_slot_index = self.find_free_slot(); + if let Some(slot_index) = maybe_slot_index { + self.reserve_slot_for(slot_index, id); + } + maybe_slot_index + } + + fn get_slot(&self, id: EntityId) -> SlotIndex { + *self.entity_to_slot_map.read().unwrap().get(&id).unwrap() + } + + fn update_buffers( + &self, + mut cbb: AutoCommandBufferBuilder, + pipeline: Arc, + chunk: &ClosedChunk, + ) -> Fallible { + if *self.command_buffer_dirty_bit.read().unwrap() { + //let mut raw = [Default::default(); 128]; + //let buffer = self.command_buffer_pool.next(raw)?; + } + *self.command_buffer_dirty_bit.write().unwrap() = false; + + Ok(cbb) + } + + fn render( + &self, + mut cbb: AutoCommandBufferBuilder, + pipeline: Arc, + chunk: &ClosedChunk, + push_constants: &vs::ty::PushConstantData, + window: &GraphicsWindow, + ) -> Fallible { + Ok(cbb.draw_indirect( + pipeline.clone(), + &window.dynamic_state, + vec![chunk.vertex_buffer()], + self.command_buffer.clone(), + ( + self.pds0.clone(), + self.pds1.clone(), + self.pds2.clone(), + self.descriptor_set.clone(), + chunk.atlas_descriptor_set_ref(), + ), + push_constants, + )?) + } } -struct ShapeRenderSystem { - chunks: ShapeChunkManager, +pub struct ShapeRenderer { + device: Arc, + world: Arc, + pipeline: Arc, + + // 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: RwLock, // All upload blocks. We will do one draw call per instance block each frame. - blocks: Vec, + blocks: RwLock>, // Map from the index to the block that it has a reserved upload slot in. - upload_block_map: HashMap, - - device: Arc, + upload_block_map: RwLock>, + + // 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<[DrawIndirectCommand; BLOCK_SIZE]>, + transform_buffer_pool: CpuBufferPool<[[f32; 6]; BLOCK_SIZE]>, + flag_buffer_pool: CpuBufferPool<[[u32; 2]; BLOCK_SIZE]>, + xform_index_buffer_pool: CpuBufferPool<[i32; BLOCK_SIZE]>, + xform_buffer_pool: CpuBufferPool<[[f32; 6]; 14 * BLOCK_SIZE]>, // FIXME: hunt down this max somewhere } -impl ShapeRenderSystem { - pub fn new(chunks: ShapeChunkManager, device: Arc) -> Self { - Self { +impl ShapeRenderer { + pub fn new(world: Arc, window: &GraphicsWindow) -> Fallible { + let pipeline = Self::build_pipeline(&window)?; + let mut chunks = RwLock::new(ShapeChunkManager::new(pipeline.clone(), &window)?); + Ok(Self { + device: window.device(), + world, + pipeline, chunks, - blocks: Vec::new(), - upload_block_map: HashMap::new(), - device, - } + blocks: RwLock::new(Vec::new()), + upload_block_map: RwLock::new(HashMap::new()), + command_buffer_pool: CpuBufferPool::new( + window.device(), + BufferUsage::indirect_buffer(), + ), + 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()), + }) } - pub fn build_pipeline( + fn build_pipeline( window: &GraphicsWindow, ) -> Fallible> { let vert_shader = vs::Shader::load(window.device())?; @@ -370,44 +499,120 @@ impl ShapeRenderSystem { ) as Arc) } + pub fn pipeline(&self) -> Arc { + self.pipeline.clone() + } + + pub fn upload_shape( + &self, + name: &str, + selection: DrawSelection, + window: &GraphicsWindow, + ) -> Fallible<(ShapeId, Box)> { + self.chunks.write().unwrap().upload_shape( + name, + selection, + self.world.system_palette(), + self.world.library(), + window, + ) + } + + // Close any outstanding chunks and prepare to render. + pub fn ensure_uploaded(&self, window: &GraphicsWindow) -> Fallible> { + self.chunks.write().unwrap().finish(window) + } + // First fit: find the first block with a free upload slot. fn reserve_free_slot( - &mut self, + &self, id: EntityId, shape_id: ShapeId, - device: Arc, - ) -> Fallible { - let chunk_index = self.chunks.find_chunk_for_shape(shape_id)?; - - for (block_index, block) in self.blocks.iter_mut().enumerate() { - if let Some(_) = block.reserve_free_slot(id, chunk_index) { - return Ok(BlockIndex(block_index)); + ) -> Fallible<(BlockIndex, SlotIndex)> { + let chunk_index = self.chunks.read().unwrap().find_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.read().unwrap().iter().enumerate() { + if let Some(slot_index) = block.reserve_free_slot(id, chunk_index) { + return Ok((BlockIndex(block_index), slot_index)); } } // No free slots in any blocks. Build a new one. - let next_block_index = BlockIndex(self.blocks.len()); - self.blocks - .push(DynamicInstanceBlock::new(chunk_index, device)?); - let slot_index = self - .blocks - .last_mut() + let next_block_index = BlockIndex(self.blocks.read().unwrap().len()); + let block = DynamicInstanceBlock::new( + chunk_index, + self.pipeline.clone(), + 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(), + )?; + let slot_index = block.reserve_free_slot(id, chunk_index).unwrap(); + self.blocks.write().unwrap().push(block); + self.upload_block_map + .write() .unwrap() - .reserve_free_slot(id, chunk_index) - .unwrap(); - Ok(next_block_index) + .insert(id, next_block_index); + Ok((next_block_index, slot_index)) } pub fn reserve_entity_slot( - &mut self, + &self, id: EntityId, shape_id: ShapeId, - device: Arc, - ) -> Fallible { - if let Some(block_index) = self.upload_block_map.get(&id) { - return Ok(*block_index); + ) -> Fallible<(BlockIndex, SlotIndex)> { + // Fast path: in most cases we'll already have a block. + if let Some(&block_index) = self.upload_block_map.read().unwrap().get(&id) { + let slot_index = self.blocks.read().unwrap()[block_index.0].get_slot(id); + return Ok((block_index, slot_index)); + } + + self.reserve_free_slot(id, shape_id) + } + + fn update_buffers( + &self, + mut cbb: AutoCommandBufferBuilder, + ) -> Fallible { + let chunk_man = self.chunks.read().unwrap(); + for block in self.blocks.read().unwrap().iter() { + let chunk = chunk_man.get_chunk(block.chunk_index); + cbb = block.update_buffers(cbb, self.pipeline(), &chunk)?; + } + Ok(cbb) + } + + pub fn render( + &self, + mut cbb: AutoCommandBufferBuilder, + camera: &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 chunk_man = self.chunks.read().unwrap(); + for block in self.blocks.read().unwrap().iter() { + let chunk = chunk_man.get_chunk(block.chunk_index); + cbb = block.render(cbb, self.pipeline(), &chunk, &push_constants, window)?; } - self.reserve_free_slot(id, shape_id, device) + Ok(cbb) + } +} + +pub struct ShapeRenderSystem { + renderer: Arc, +} + +impl ShapeRenderSystem { + pub fn new(renderer: Arc) -> Self { + Self { renderer } } } @@ -423,8 +628,9 @@ impl<'a> System<'a> for ShapeRenderSystem { fn run(&mut self, (entities, transform, shape_mesh): Self::SystemData) { for (entity, transform, shape_mesh) in (&entities, &transform, &shape_mesh).join() { - let block_index = self - .reserve_entity_slot(entity.id(), shape_mesh.shape_id(), self.device.clone()) + let (block_index, slot_index) = self + .renderer + .reserve_entity_slot(entity.id(), shape_mesh.shape_id()) .expect("unable to reserve instance slot"); //self.blocks[block_index]; println!("{:?} => block_index: {:?}", entity.id(), block_index); @@ -450,8 +656,13 @@ mod tests { let pipeline = ShapeRenderSystem::build_pipeline(&window)?; let mut shape_chunk_man = ShapeChunkManager::new(pipeline, &window)?; - let (t80_id, fut1) = - shape_chunk_man.upload_mover("T80.SH", DrawSelection::NormalModel, &world, &window)?; + let (t80_id, fut1) = shape_chunk_man.upload_shape( + "T80.SH", + DrawSelection::NormalModel, + world.system_palette(), + world.library(), + &window, + )?; let future = shape_chunk_man.finish(&window)?; let t80_ent1 = world.create_ground_mover(t80_id, Point3::new(0f64, 0f64, 0f64))?; diff --git a/libs/world/src/component/flight_dynamics.rs b/libs/world/src/component/flight_dynamics.rs index 40e78307..0977b5d5 100644 --- a/libs/world/src/component/flight_dynamics.rs +++ b/libs/world/src/component/flight_dynamics.rs @@ -22,3 +22,11 @@ pub struct FlightDynamics { impl Component for FlightDynamics { type Storage = VecStorage; } + +impl FlightDynamics { + pub fn new() -> Self { + Self { + velocity: Vector3::zeros(), + } + } +} diff --git a/libs/world/src/lib.rs b/libs/world/src/lib.rs index ef14710b..5faff24b 100644 --- a/libs/world/src/lib.rs +++ b/libs/world/src/lib.rs @@ -21,12 +21,12 @@ use nalgebra::Point3; use pal::Palette; use shape_chunk::{DrawSelection, ShapeId}; use specs::{Builder, Dispatcher, World as SpecsWorld, WorldExt}; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; pub use specs::Entity; pub struct World { - ecs: SpecsWorld, + ecs: RwLock, // Resources lib: Arc>, @@ -42,16 +42,16 @@ impl World { ecs.register::(); Ok(Self { - ecs, + ecs: RwLock::new(ecs), palette: Arc::new(Palette::from_bytes(&lib.load("PALETTE.PAL")?)?), lib, }) } - pub fn run(&mut self, dispatcher: &mut Dispatcher) { - println!("RUN:"); - dispatcher.dispatch(&mut self.ecs); - self.ecs.maintain(); + pub fn run(&self, dispatcher: &mut Dispatcher) { + let mut ecs = self.ecs.write().unwrap(); + dispatcher.dispatch(&mut ecs); + ecs.maintain(); } pub fn library(&self) -> &Library { @@ -63,12 +63,14 @@ impl World { } pub fn create_ground_mover( - &mut self, + &self, shape_id: ShapeId, position: Point3, ) -> Fallible { Ok(self .ecs + .write() + .unwrap() .create_entity() .with(Transform::new(position)) .with(WheeledDynamics::new()) @@ -76,8 +78,21 @@ impl World { .build()) } + pub fn create_flyer(&self, shape_id: ShapeId, position: Point3) -> Fallible { + Ok(self + .ecs + .write() + .unwrap() + .create_entity() + .with(Transform::new(position)) + .with(WheeledDynamics::new()) + .with(FlightDynamics::new()) + .with(ShapeMesh::new(shape_id)) + .build()) + } + pub fn destroy_entity(&mut self, entity: Entity) -> Fallible<()> { - Ok(self.ecs.delete_entity(entity)?) + Ok(self.ecs.write().unwrap().delete_entity(entity)?) } } From 2321265b2ad15035a7cb868ab986614ff558245c Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sat, 7 Sep 2019 07:49:48 -0700 Subject: [PATCH 10/16] Black triangle! --- .../buffer/shape_chunk/src/chunk_manager.rs | 14 +- libs/render/buffer/shape_chunk/src/lib.rs | 2 +- .../shape_instance/examples/demo-instances.rs | 129 +++++++- libs/render/draw/shape_instance/src/lib.rs | 298 ++++++++++++------ libs/world/src/component/shape_mesh.rs | 4 + libs/world/src/lib.rs | 2 +- 6 files changed, 328 insertions(+), 121 deletions(-) diff --git a/libs/render/buffer/shape_chunk/src/chunk_manager.rs b/libs/render/buffer/shape_chunk/src/chunk_manager.rs index 50c5f3f7..7bffae52 100644 --- a/libs/render/buffer/shape_chunk/src/chunk_manager.rs +++ b/libs/render/buffer/shape_chunk/src/chunk_manager.rs @@ -49,10 +49,6 @@ impl ShapeChunkManager { // pub fn create_airplane -- need to hook into shape state? - pub fn get_chunk(&self, chunk_index: ChunkIndex) -> &ClosedChunk { - &self.closed_chunks[chunk_index.0] - } - pub fn finish(&mut self, window: &GraphicsWindow) -> Fallible> { self.finish_open_chunk(window) } @@ -72,11 +68,11 @@ impl ShapeChunkManager { palette: &Palette, lib: &Library, window: &GraphicsWindow, - ) -> Fallible<(ShapeId, Box)> { + ) -> Fallible<(ShapeId, Option>)> { let future = if self.open_chunk.chunk_is_full() { - self.finish_open_chunk(window)? + Some(self.finish_open_chunk(window)?) } else { - window.now() + None }; let shape_id = self .open_chunk @@ -94,6 +90,10 @@ impl ShapeChunkManager { bail!("shape_id {:?} has not been uploaded", shape_id) } + pub fn get_chunk(&self, chunk_index: ChunkIndex) -> &ClosedChunk { + &self.closed_chunks[chunk_index.0] + } + pub fn at(&self, chunk_index: ChunkIndex) -> &ClosedChunk { &self.closed_chunks[chunk_index.0] } diff --git a/libs/render/buffer/shape_chunk/src/lib.rs b/libs/render/buffer/shape_chunk/src/lib.rs index 3f506c11..901a7240 100644 --- a/libs/render/buffer/shape_chunk/src/lib.rs +++ b/libs/render/buffer/shape_chunk/src/lib.rs @@ -18,7 +18,7 @@ mod draw_state; mod texture_atlas; mod upload; -pub use chunk::{Chunk, ClosedChunk, OpenChunk, ShapeId}; +pub use chunk::{Chunk, ChunkPart, ClosedChunk, OpenChunk, ShapeId}; pub use chunk_manager::{ChunkIndex, ShapeChunkManager}; pub use draw_state::DrawState; pub use upload::{DrawSelection, Vertex}; diff --git a/libs/render/draw/shape_instance/examples/demo-instances.rs b/libs/render/draw/shape_instance/examples/demo-instances.rs index 6ee862c6..e2e49ad1 100644 --- a/libs/render/draw/shape_instance/examples/demo-instances.rs +++ b/libs/render/draw/shape_instance/examples/demo-instances.rs @@ -27,7 +27,7 @@ use specs::{ use std::{sync::Arc, time::Instant}; use vulkano::{ buffer::{BufferUsage, CpuAccessibleBuffer}, - command_buffer::AutoCommandBufferBuilder, + command_buffer::{AutoCommandBufferBuilder, CommandBuffer}, descriptor::descriptor_set::PersistentDescriptorSet, framebuffer::Subpass, pipeline::{ @@ -161,6 +161,63 @@ mod fs { } } +impl crate::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]; + } +} + fn main() -> Fallible<()> { let mut window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; let bindings = InputBindings::new("base") @@ -171,27 +228,43 @@ fn main() -> Fallible<()> { let lib = omni.library("FA"); let world = Arc::new(World::new(lib)?); - let shape_renderer = Arc::new(ShapeRenderer::new(world.clone(), &window)?); - let (f8_id, _) = shape_renderer.upload_shape("F8.SH", DrawSelection::NormalModel, &window)?; - let (f18_id, _) = shape_renderer.upload_shape("F18.SH", DrawSelection::NormalModel, &window)?; + 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 future = shape_renderer.ensure_uploaded(&window)?; + assert!(fut1.is_none()); + assert!(fut2.is_none()); future.then_signal_fence_and_flush()?.wait(None)?; let f18_ent1 = world.create_flyer(f18_id, Point3::new(0f64, 0f64, 0f64))?; let f18_ent2 = world.create_flyer(f18_id, Point3::new(40f64, -10f64, 10f64))?; // Pump the renderer once to upload all of our buffers - let shape_render_system = ShapeRenderSystem::new(shape_renderer.clone()); - let mut shape_instance_updater = DispatcherBuilder::new() - .with(shape_render_system, "", &[]) - .build(); - world.run(&mut shape_instance_updater); + { + 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 chunks = shape_renderer.chunks(); + let chunk = chunks.at(shape_renderer.chunks().find_chunk_for_shape(f18_id)?); + let f18_part = chunk.part(f18_id).unwrap(); + let pipeline = shape_renderer.pipeline(); + 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)?; + let mut push_constants = crate::vs::ty::PushConstantData::new(); /* let chunk_index = chunk_man.find_chunk_for_shape(f18_id)?; let chunk = chunk_man.at(chunk_index); let f18_part = chunk.part(f18_id).unwrap(); + */ // Upload transforms let transforms = vec![0f32, 0f32, 0f32, 0f32, 0f32, 0f32]; @@ -252,11 +325,12 @@ fn main() -> Fallible<()> { BufferUsage::all(), [f18_part.draw_command(0, 1)].iter().cloned(), )?; - */ let mut camera = ArcBallCamera::new(window.aspect_ratio_f64()?, 0.1, 3.4e+38); - camera.set_distance(80.0); + camera.set_distance(120.0); camera.on_mousebutton_down(1); + push_constants.set_projection(&camera.projection_matrix()); + push_constants.set_view(&camera.view_matrix()); window.hide_cursor()?; loop { @@ -288,13 +362,44 @@ fn main() -> Fallible<()> { 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 = shape_renderer.render(cbb, &camera, &window, &f18_part)?; + /* + if false { + // foo + cbb = shape_renderer.blocks()[0].render( + cbb, + shape_renderer.pipeline(), + chunk, + //&push_constants, + &camera, + &window, + f18_part, + )?; + } else if false { + cbb = cbb.draw_indirect( + pipeline.clone(), + &window.dynamic_state, + vec![chunk.vertex_buffer()], + indirect_buffer.clone(), + ( + empty0.clone(), + empty1.clone(), + empty2.clone(), + shape_descriptor_set.clone(), + chunk.atlas_descriptor_set_ref(), + ), + push_constants, + )?; + } + */ cbb = cbb.end_render_pass()?; diff --git a/libs/render/draw/shape_instance/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs index fccf6288..f77690aa 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -19,7 +19,8 @@ use nalgebra::Matrix4; use nalgebra::Point3; use omnilib::OmniLib; use shape_chunk::{ - Chunk, ChunkIndex, ClosedChunk, DrawSelection, OpenChunk, ShapeChunkManager, ShapeId, Vertex, + Chunk, ChunkIndex, ChunkPart, ClosedChunk, DrawSelection, DrawState, OpenChunk, + ShapeChunkManager, ShapeId, Vertex, }; use specs::{ world::Index as EntityId, DispatcherBuilder, Entities, Join, ReadStorage, System, VecStorage, @@ -28,7 +29,10 @@ use std::{ collections::HashMap, mem, sync::{Arc, RwLock}, + time::Instant, }; +use vulkano::buffer::cpu_pool::CpuBufferPoolSubbuffer; +use vulkano::buffer::CpuAccessibleBuffer; use vulkano::command_buffer::AutoCommandBufferBuilder; use vulkano::{ buffer::{BufferAccess, BufferSlice, BufferUsage, CpuBufferPool, DeviceLocalBuffer}, @@ -243,9 +247,9 @@ pub struct DynamicInstanceBlock { //chunk_type: ChunkType, // Map from the entity to the stored offset and from the offset to the entity. - slot_reservations: RwLock<[Option; BLOCK_SIZE]>, - entity_to_slot_map: RwLock>, - command_buffer_dirty_bit: RwLock, + slot_reservations: [Option; BLOCK_SIZE], + entity_to_slot_map: HashMap, + mark_buffer: [bool; BLOCK_SIZE], // GC marked set descriptor_set: Arc, @@ -257,18 +261,21 @@ pub struct DynamicInstanceBlock { // 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_buffer_pool: CpuBufferPool, command_buffer: Arc>, - command_buffer_pool: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, // 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: Arc>, - transform_buffer_pool: CpuBufferPool<[[f32; 6]; BLOCK_SIZE]>, + 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: Arc>, - flag_buffer_pool: CpuBufferPool<[[u32; 2]; BLOCK_SIZE]>, + 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: Arc>, @@ -285,9 +292,9 @@ impl DynamicInstanceBlock { fn new( chunk_index: ChunkIndex, pipeline: Arc, - command_buffer_pool: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, - transform_buffer_pool: CpuBufferPool<[[f32; 6]; BLOCK_SIZE]>, - flag_buffer_pool: CpuBufferPool<[[u32; 2]; BLOCK_SIZE]>, + command_buffer_pool: CpuBufferPool, + transform_buffer_pool: CpuBufferPool<[f32; 6]>, + flag_buffer_pool: CpuBufferPool<[u32; 2]>, xform_index_buffer_pool: CpuBufferPool<[i32; BLOCK_SIZE]>, xform_buffer_pool: CpuBufferPool<[[f32; 6]; 14 * BLOCK_SIZE]>, device: Arc, @@ -295,16 +302,18 @@ impl DynamicInstanceBlock { let command_buffer = DeviceLocalBuffer::array( device.clone(), BLOCK_SIZE, - BufferUsage::indirect_buffer(), + BufferUsage::all(), device.active_queue_families(), )?; - let transform_buffer = DeviceLocalBuffer::new( + let transform_buffer = DeviceLocalBuffer::array( device.clone(), + BLOCK_SIZE, BufferUsage::all(), device.active_queue_families(), )?; - let flag_buffer = DeviceLocalBuffer::new( + let flag_buffer = DeviceLocalBuffer::array( device.clone(), + BLOCK_SIZE, BufferUsage::all(), device.active_queue_families(), )?; @@ -328,19 +337,27 @@ impl DynamicInstanceBlock { ); Ok(Self { chunk_index, - slot_reservations: RwLock::new([None; BLOCK_SIZE]), - entity_to_slot_map: RwLock::new(HashMap::new()), - command_buffer_dirty_bit: RwLock::new(false), + slot_reservations: [None; BLOCK_SIZE], + entity_to_slot_map: HashMap::new(), + mark_buffer: [false; BLOCK_SIZE], descriptor_set, pds0: GraphicsWindow::empty_descriptor_set(pipeline.clone(), 0)?, pds1: GraphicsWindow::empty_descriptor_set(pipeline.clone(), 1)?, pds2: GraphicsWindow::empty_descriptor_set(pipeline.clone(), 2)?, - command_buffer, + command_buffer_scratch: [DrawIndirectCommand { + vertex_count: 0u32, + instance_count: 0u32, + first_vertex: 0u32, + first_instance: 0u32, + }; BLOCK_SIZE], command_buffer_pool, - transform_buffer, + command_buffer, + transform_buffer_scratch: [[0f32; 6]; BLOCK_SIZE], transform_buffer_pool, - flag_buffer, + transform_buffer, + flag_buffer_scratch: [[0u32; 2]; BLOCK_SIZE], flag_buffer_pool, + flag_buffer, xform_index_buffer, xform_index_buffer_pool, xform_buffer, @@ -348,14 +365,18 @@ impl DynamicInstanceBlock { }) } - fn reserve_slot_for(&self, slot: SlotIndex, id: EntityId) { - self.slot_reservations.write().unwrap()[slot.0] = Some(id); - self.entity_to_slot_map.write().unwrap().insert(id, slot); - *self.command_buffer_dirty_bit.write().unwrap() = true; + fn reserve_slot_for(&mut self, slot: SlotIndex, id: EntityId) { + self.slot_reservations[slot.0] = Some(id); + self.entity_to_slot_map.insert(id, slot); + /* + let foo = &mut self.command_buffer_scratch[slot.0]; + foo.vertex_count = 10; + foo.instance_count = 1; + */ } fn find_free_slot(&self) -> Option { - for (slot_offset, entity_id) in self.slot_reservations.read().unwrap().iter().enumerate() { + for (slot_offset, entity_id) in self.slot_reservations.iter().enumerate() { if entity_id.is_none() { return Some(SlotIndex(slot_offset)); } @@ -363,7 +384,7 @@ impl DynamicInstanceBlock { None } - fn reserve_free_slot(&self, id: EntityId, chunk_index: ChunkIndex) -> Option { + fn reserve_free_slot(&mut self, id: EntityId, chunk_index: ChunkIndex) -> Option { if chunk_index != self.chunk_index { return None; } @@ -374,9 +395,29 @@ impl DynamicInstanceBlock { maybe_slot_index } - fn get_slot(&self, id: EntityId) -> SlotIndex { - *self.entity_to_slot_map.read().unwrap().get(&id).unwrap() + fn get_existing_slot(&mut self, id: EntityId) -> SlotIndex { + *self.entity_to_slot_map.get(&id).unwrap() + } + + fn get_command_buffer_slot(&mut self, slot_index: SlotIndex) -> &mut DrawIndirectCommand { + &mut self.command_buffer_scratch[slot_index.0] + } + + 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_upload_buffer(&mut self, slot_index: SlotIndex) -> Fallible<()> { + self.mark_buffer[slot_index.0] = true; + + Ok(()) } + */ fn update_buffers( &self, @@ -384,28 +425,41 @@ impl DynamicInstanceBlock { pipeline: Arc, chunk: &ClosedChunk, ) -> Fallible { - if *self.command_buffer_dirty_bit.read().unwrap() { - //let mut raw = [Default::default(); 128]; - //let buffer = self.command_buffer_pool.next(raw)?; - } - *self.command_buffer_dirty_bit.write().unwrap() = false; + 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())?; Ok(cbb) } - fn render( + pub fn render( &self, mut cbb: AutoCommandBufferBuilder, pipeline: Arc, chunk: &ClosedChunk, push_constants: &vs::ty::PushConstantData, + camera: &dyn CameraAbstract, window: &GraphicsWindow, + f18_part: &ChunkPart, ) -> Fallible { + let mut local_push_constants = vs::ty::PushConstantData::new(); + local_push_constants.set_projection(&camera.projection_matrix()); + local_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()], - self.command_buffer.clone(), + ib.into_buffer_slice().slice(0..1).unwrap(), ( self.pds0.clone(), self.pds1.clone(), @@ -425,13 +479,13 @@ pub struct ShapeRenderer { // 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: RwLock, + chunks: ShapeChunkManager, // All upload blocks. We will do one draw call per instance block each frame. - blocks: RwLock>, + blocks: Vec, // Map from the index to the block that it has a reserved upload slot in. - upload_block_map: RwLock>, + 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. @@ -440,9 +494,9 @@ pub struct ShapeRenderer { // pds2: Arc, // Buffer pools are shared by all blocks for maximum re-use. - command_buffer_pool: CpuBufferPool<[DrawIndirectCommand; BLOCK_SIZE]>, - transform_buffer_pool: CpuBufferPool<[[f32; 6]; BLOCK_SIZE]>, - flag_buffer_pool: CpuBufferPool<[[u32; 2]; BLOCK_SIZE]>, + command_buffer_pool: CpuBufferPool, + transform_buffer_pool: CpuBufferPool<[f32; 6]>, + flag_buffer_pool: CpuBufferPool<[u32; 2]>, xform_index_buffer_pool: CpuBufferPool<[i32; BLOCK_SIZE]>, xform_buffer_pool: CpuBufferPool<[[f32; 6]; 14 * BLOCK_SIZE]>, // FIXME: hunt down this max somewhere } @@ -450,18 +504,15 @@ pub struct ShapeRenderer { impl ShapeRenderer { pub fn new(world: Arc, window: &GraphicsWindow) -> Fallible { let pipeline = Self::build_pipeline(&window)?; - let mut chunks = RwLock::new(ShapeChunkManager::new(pipeline.clone(), &window)?); + let chunks = ShapeChunkManager::new(pipeline.clone(), &window)?; Ok(Self { device: window.device(), world, pipeline, chunks, - blocks: RwLock::new(Vec::new()), - upload_block_map: RwLock::new(HashMap::new()), - command_buffer_pool: CpuBufferPool::new( - window.device(), - BufferUsage::indirect_buffer(), - ), + blocks: Vec::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()), @@ -504,12 +555,12 @@ impl ShapeRenderer { } pub fn upload_shape( - &self, + &mut self, name: &str, selection: DrawSelection, window: &GraphicsWindow, - ) -> Fallible<(ShapeId, Box)> { - self.chunks.write().unwrap().upload_shape( + ) -> Fallible<(ShapeId, Option>)> { + self.chunks.upload_shape( name, selection, self.world.system_palette(), @@ -519,31 +570,31 @@ impl ShapeRenderer { } // Close any outstanding chunks and prepare to render. - pub fn ensure_uploaded(&self, window: &GraphicsWindow) -> Fallible> { - self.chunks.write().unwrap().finish(window) + pub fn ensure_uploaded(&mut self, window: &GraphicsWindow) -> Fallible> { + self.chunks.finish(window) } // First fit: find the first block with a free upload slot. fn reserve_free_slot( - &self, + &mut self, id: EntityId, shape_id: ShapeId, ) -> Fallible<(BlockIndex, SlotIndex)> { - let chunk_index = self.chunks.read().unwrap().find_chunk_for_shape(shape_id)?; + let chunk_index = self.chunks.find_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.read().unwrap().iter().enumerate() { + for (block_index, block) in self.blocks.iter_mut().enumerate() { if let Some(slot_index) = block.reserve_free_slot(id, chunk_index) { return Ok((BlockIndex(block_index), slot_index)); } } // No free slots in any blocks. Build a new one. - let next_block_index = BlockIndex(self.blocks.read().unwrap().len()); - let block = DynamicInstanceBlock::new( + let next_block_index = BlockIndex(self.blocks.len()); + let mut block = DynamicInstanceBlock::new( chunk_index, self.pipeline.clone(), self.command_buffer_pool.clone(), @@ -554,35 +605,63 @@ impl ShapeRenderer { self.device.clone(), )?; let slot_index = block.reserve_free_slot(id, chunk_index).unwrap(); - self.blocks.write().unwrap().push(block); - self.upload_block_map - .write() - .unwrap() - .insert(id, next_block_index); + self.blocks.push(block); + self.upload_block_map.insert(id, next_block_index); Ok((next_block_index, slot_index)) } - pub fn reserve_entity_slot( - &self, + pub fn ensure_entity_slot( + &mut self, id: EntityId, shape_id: ShapeId, ) -> Fallible<(BlockIndex, SlotIndex)> { // Fast path: in most cases we'll already have a block. - if let Some(&block_index) = self.upload_block_map.read().unwrap().get(&id) { - let slot_index = self.blocks.read().unwrap()[block_index.0].get_slot(id); + if let Some(&block_index) = self.upload_block_map.get(&id) { + let slot_index = self.blocks[block_index.0].get_existing_slot(id); return Ok((block_index, slot_index)); } self.reserve_free_slot(id, shape_id) } - fn update_buffers( + pub fn chunks(&self) -> &ShapeChunkManager { + &self.chunks + } + + pub fn blocks(&self) -> &Vec { + &self.blocks + } + + fn get_chunk_for_slot(&self, index: (BlockIndex, SlotIndex)) -> &ClosedChunk { + let (block_index, _) = index; + let block = &self.blocks[block_index.0]; + self.chunks.at(block.chunk_index) + } + + fn get_command_buffer_slot( + &mut self, + index: (BlockIndex, SlotIndex), + ) -> &mut DrawIndirectCommand { + let (block_index, slot_index) = index; + self.blocks[block_index.0].get_command_buffer_slot(slot_index) + } + + fn get_transform_buffer_slot(&mut self, index: (BlockIndex, SlotIndex)) -> &mut [f32; 6] { + let (block_index, slot_index) = index; + self.blocks[block_index.0].get_transform_buffer_slot(slot_index) + } + + fn get_flag_buffer_slot(&mut self, index: (BlockIndex, SlotIndex)) -> &mut [u32; 2] { + let (block_index, slot_index) = index; + self.blocks[block_index.0].get_flag_buffer_slot(slot_index) + } + + pub fn update_buffers( &self, mut cbb: AutoCommandBufferBuilder, ) -> Fallible { - let chunk_man = self.chunks.read().unwrap(); - for block in self.blocks.read().unwrap().iter() { - let chunk = chunk_man.get_chunk(block.chunk_index); + for block in self.blocks.iter() { + let chunk = self.chunks.get_chunk(block.chunk_index); cbb = block.update_buffers(cbb, self.pipeline(), &chunk)?; } Ok(cbb) @@ -591,32 +670,43 @@ impl ShapeRenderer { pub fn render( &self, mut cbb: AutoCommandBufferBuilder, - camera: &CameraAbstract, + camera: &dyn CameraAbstract, window: &GraphicsWindow, + f18_part: &ChunkPart, ) -> Fallible { let mut push_constants = vs::ty::PushConstantData::new(); push_constants.set_projection(&camera.projection_matrix()); push_constants.set_view(&camera.view_matrix()); - let chunk_man = self.chunks.read().unwrap(); - for block in self.blocks.read().unwrap().iter() { + + 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, &push_constants, window)?; + println!("at chunk: {:?}", block.chunk_index); + cbb = block.render( + cbb, + self.pipeline(), + &chunk, + &push_constants, + camera, + window, + f18_part, + )?; } Ok(cbb) } } -pub struct ShapeRenderSystem { - renderer: Arc, +pub struct ShapeRenderSystem<'b> { + renderer: &'b mut ShapeRenderer, } -impl ShapeRenderSystem { - pub fn new(renderer: Arc) -> Self { +impl<'b> ShapeRenderSystem<'b> { + pub fn new(renderer: &'b mut ShapeRenderer) -> Self { Self { renderer } } } -impl<'a> System<'a> for ShapeRenderSystem { +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. @@ -628,12 +718,27 @@ impl<'a> System<'a> for ShapeRenderSystem { fn run(&mut self, (entities, transform, shape_mesh): Self::SystemData) { for (entity, transform, shape_mesh) in (&entities, &transform, &shape_mesh).join() { - let (block_index, slot_index) = self + let index = self .renderer - .reserve_entity_slot(entity.id(), shape_mesh.shape_id()) + .ensure_entity_slot(entity.id(), shape_mesh.shape_id()) .expect("unable to reserve instance slot"); + + // Push all. + let chunk = self.renderer.get_chunk_for_slot(index); + let chunk_part = chunk.part(shape_mesh.shape_id()).unwrap(); + let errata = chunk_part.widgets().errata(); + *self.renderer.get_command_buffer_slot(index) = chunk_part.draw_command(0, 1); + *self.renderer.get_transform_buffer_slot(index) = [0f32; 6]; + let flag_slot = self.renderer.get_flag_buffer_slot(index); + + // FIXME: get time start somehow + shape_mesh + .draw_state() + .build_mask_into(&Instant::now(), errata, flag_slot); + + //let foo = self.acquire_upload_buffers(block_index, slot_index); //self.blocks[block_index]; - println!("{:?} => block_index: {:?}", entity.id(), block_index); + //println!("{:?} => block_index: {:?}", entity.id(), block_index); } } } @@ -652,28 +757,21 @@ mod tests { let window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; let lib = omni.library("FA"); - let mut world = World::new(lib)?; + let world = Arc::new(World::new(lib)?); - let pipeline = ShapeRenderSystem::build_pipeline(&window)?; - let mut shape_chunk_man = ShapeChunkManager::new(pipeline, &window)?; - let (t80_id, fut1) = shape_chunk_man.upload_shape( - "T80.SH", - DrawSelection::NormalModel, - world.system_palette(), - world.library(), - &window, - )?; - let future = shape_chunk_man.finish(&window)?; + 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( - ShapeRenderSystem::new(shape_chunk_man, window.device()), - "", - &[], - ) + .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); diff --git a/libs/world/src/component/shape_mesh.rs b/libs/world/src/component/shape_mesh.rs index 2634f80d..e26e5ae2 100644 --- a/libs/world/src/component/shape_mesh.rs +++ b/libs/world/src/component/shape_mesh.rs @@ -35,4 +35,8 @@ impl ShapeMesh { pub fn shape_id(&self) -> ShapeId { self.shape_id } + + pub fn draw_state(&self) -> &DrawState { + &self.draw_state + } } diff --git a/libs/world/src/lib.rs b/libs/world/src/lib.rs index 5faff24b..c9bde762 100644 --- a/libs/world/src/lib.rs +++ b/libs/world/src/lib.rs @@ -91,7 +91,7 @@ impl World { .build()) } - pub fn destroy_entity(&mut self, entity: Entity) -> Fallible<()> { + pub fn destroy_entity(&self, entity: Entity) -> Fallible<()> { Ok(self.ecs.write().unwrap().delete_entity(entity)?) } } From 888ae2acbc6e354e74e71a9ee24a2be48db6b0fc Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sat, 7 Sep 2019 08:04:24 -0700 Subject: [PATCH 11/16] Compress and simplify. --- .../shape_instance/examples/demo-instances.rs | 342 ++---------------- libs/render/draw/shape_instance/src/lib.rs | 24 +- 2 files changed, 28 insertions(+), 338 deletions(-) diff --git a/libs/render/draw/shape_instance/examples/demo-instances.rs b/libs/render/draw/shape_instance/examples/demo-instances.rs index e2e49ad1..2c094010 100644 --- a/libs/render/draw/shape_instance/examples/demo-instances.rs +++ b/libs/render/draw/shape_instance/examples/demo-instances.rs @@ -12,212 +12,19 @@ // // You should have received a copy of the GNU General Public License // along with OpenFA. If not, see . -use camera::{ArcBallCamera, CameraAbstract}; +use camera::ArcBallCamera; use failure::Fallible; -use global_layout::GlobalSets; use input::{InputBindings, InputSystem}; -use nalgebra::{Matrix4, Point3}; +use nalgebra::Point3; use omnilib::OmniLib; -use pal::Palette; -use shape_chunk::{DrawSelection, DrawState, ShapeChunkManager, Vertex}; +use shape_chunk::DrawSelection; use shape_instance::{ShapeRenderSystem, ShapeRenderer}; -use specs::{ - world::Index as EntityId, DispatcherBuilder, Entities, Join, ReadStorage, System, VecStorage, -}; -use std::{sync::Arc, time::Instant}; -use vulkano::{ - buffer::{BufferUsage, CpuAccessibleBuffer}, - command_buffer::{AutoCommandBufferBuilder, CommandBuffer}, - descriptor::descriptor_set::PersistentDescriptorSet, - framebuffer::Subpass, - pipeline::{ - depth_stencil::{Compare, DepthBounds, DepthStencil}, - GraphicsPipeline, GraphicsPipelineAbstract, - }, - sync::GpuFuture, -}; +use specs::DispatcherBuilder; +use std::sync::Arc; +use vulkano::{command_buffer::AutoCommandBufferBuilder, sync::GpuFuture}; use window::{GraphicsConfigBuilder, GraphicsWindow}; use world::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; - - 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; - 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 crate::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]; - } -} - fn main() -> Fallible<()> { let mut window = GraphicsWindow::new(&GraphicsConfigBuilder::new().build())?; let bindings = InputBindings::new("base") @@ -229,108 +36,27 @@ fn main() -> Fallible<()> { let world = Arc::new(World::new(lib)?); let mut shape_renderer = ShapeRenderer::new(world.clone(), &window)?; - let (f8_id, fut1) = + 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))?; - let f18_ent2 = world.create_flyer(f18_id, Point3::new(40f64, -10f64, 10f64))?; - - // Pump the renderer once to upload all of our buffers - { - 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 chunks = shape_renderer.chunks(); - let chunk = chunks.at(shape_renderer.chunks().find_chunk_for_shape(f18_id)?); - let f18_part = chunk.part(f18_id).unwrap(); - let pipeline = shape_renderer.pipeline(); - 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)?; - let mut push_constants = crate::vs::ty::PushConstantData::new(); - - /* - let chunk_index = chunk_man.find_chunk_for_shape(f18_id)?; - let chunk = chunk_man.at(chunk_index); - let f18_part = chunk.part(f18_id).unwrap(); - */ - - // 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(); - draw_state.toggle_gear(&Instant::now()); - let mut flags_arr = [0u32; 2]; - draw_state.build_mask_into( - draw_state.time_origin(), - f18_part.widgets().errata(), - &mut flags_arr[0..2], - )?; - let flags_buffer = CpuAccessibleBuffer::from_iter( - window.device(), - BufferUsage::all(), - flags_arr.iter().cloned(), - )?; - - // Upload xforms - let now = Instant::now(); - let xforms_len = f18_part.widgets().num_transformer_floats(); - let mut xforms = Vec::with_capacity(xforms_len); - xforms.resize(xforms_len, 0f32); - f18_part - .widgets() - .animate_into(&draw_state, draw_state.time_origin(), &now, &mut xforms)?; - let xforms_buffer = CpuAccessibleBuffer::from_iter( - window.device(), - BufferUsage::all(), - 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()?, - ); - - let indirect_buffer = CpuAccessibleBuffer::from_iter( - window.device(), - BufferUsage::all(), - [f18_part.draw_command(0, 1)].iter().cloned(), - )?; + let _f18_ent1 = world.create_flyer(f18_id, Point3::new(0f64, 0f64, 0f64))?; + let _f18_ent2 = world.create_flyer(f18_id, Point3::new(40f64, -10f64, 10f64))?; let mut camera = ArcBallCamera::new(window.aspect_ratio_f64()?, 0.1, 3.4e+38); camera.set_distance(120.0); camera.on_mousebutton_down(1); - push_constants.set_projection(&camera.projection_matrix()); - push_constants.set_view(&camera.view_matrix()); window.hide_cursor()?; loop { @@ -351,6 +77,15 @@ fn main() -> Fallible<()> { } 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() { @@ -370,36 +105,7 @@ fn main() -> Fallible<()> { vec![[0f32, 0f32, 1f32, 1f32].into(), 0f32.into()], )?; - cbb = shape_renderer.render(cbb, &camera, &window, &f18_part)?; - /* - if false { - // foo - cbb = shape_renderer.blocks()[0].render( - cbb, - shape_renderer.pipeline(), - chunk, - //&push_constants, - &camera, - &window, - f18_part, - )?; - } else if false { - cbb = cbb.draw_indirect( - pipeline.clone(), - &window.dynamic_state, - vec![chunk.vertex_buffer()], - indirect_buffer.clone(), - ( - empty0.clone(), - empty1.clone(), - empty2.clone(), - shape_descriptor_set.clone(), - chunk.atlas_descriptor_set_ref(), - ), - push_constants, - )?; - } - */ + cbb = shape_renderer.render(cbb, &camera, &window)?; cbb = cbb.end_render_pass()?; diff --git a/libs/render/draw/shape_instance/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs index f77690aa..2ebbd7dd 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -445,14 +445,12 @@ impl DynamicInstanceBlock { mut cbb: AutoCommandBufferBuilder, pipeline: Arc, chunk: &ClosedChunk, - push_constants: &vs::ty::PushConstantData, camera: &dyn CameraAbstract, window: &GraphicsWindow, - f18_part: &ChunkPart, ) -> Fallible { - let mut local_push_constants = vs::ty::PushConstantData::new(); - local_push_constants.set_projection(&camera.projection_matrix()); - local_push_constants.set_view(&camera.view_matrix()); + 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( @@ -672,25 +670,11 @@ impl ShapeRenderer { mut cbb: AutoCommandBufferBuilder, camera: &dyn CameraAbstract, window: &GraphicsWindow, - f18_part: &ChunkPart, ) -> Fallible { - let mut push_constants = vs::ty::PushConstantData::new(); - push_constants.set_projection(&camera.projection_matrix()); - push_constants.set_view(&camera.view_matrix()); - let chunk_man = &self.chunks; for block in self.blocks.iter() { let chunk = chunk_man.get_chunk(block.chunk_index); - println!("at chunk: {:?}", block.chunk_index); - cbb = block.render( - cbb, - self.pipeline(), - &chunk, - &push_constants, - camera, - window, - f18_part, - )?; + cbb = block.render(cbb, self.pipeline(), &chunk, camera, window)?; } Ok(cbb) } From d8e86b75225791f90b07ece10c1921afc052ae37 Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sat, 7 Sep 2019 10:05:09 -0700 Subject: [PATCH 12/16] Keep cleaning --- .../buffer/shape_chunk/src/chunk_manager.rs | 9 + .../shape_instance/examples/demo-instances.rs | 6 +- libs/render/draw/shape_instance/src/lib.rs | 333 ++++++++---------- libs/world/src/component/transform.rs | 12 + 4 files changed, 171 insertions(+), 189 deletions(-) diff --git a/libs/render/buffer/shape_chunk/src/chunk_manager.rs b/libs/render/buffer/shape_chunk/src/chunk_manager.rs index 7bffae52..c9390543 100644 --- a/libs/render/buffer/shape_chunk/src/chunk_manager.rs +++ b/libs/render/buffer/shape_chunk/src/chunk_manager.rs @@ -90,6 +90,15 @@ impl ShapeChunkManager { bail!("shape_id {:?} has not been uploaded", shape_id) } + pub fn get_chunk_for_shape(&self, shape_id: ShapeId) -> Fallible<&ClosedChunk> { + for chunk in self.closed_chunks.iter() { + if chunk.part(shape_id).is_some() { + return Ok(chunk); + } + } + bail!("shape_id {:?} has not been uploaded", shape_id) + } + pub fn get_chunk(&self, chunk_index: ChunkIndex) -> &ClosedChunk { &self.closed_chunks[chunk_index.0] } diff --git a/libs/render/draw/shape_instance/examples/demo-instances.rs b/libs/render/draw/shape_instance/examples/demo-instances.rs index 2c094010..b6a4c692 100644 --- a/libs/render/draw/shape_instance/examples/demo-instances.rs +++ b/libs/render/draw/shape_instance/examples/demo-instances.rs @@ -36,7 +36,7 @@ fn main() -> Fallible<()> { let world = Arc::new(World::new(lib)?); let mut shape_renderer = ShapeRenderer::new(world.clone(), &window)?; - let (_f8_id, fut1) = + 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)?; @@ -52,7 +52,7 @@ fn main() -> Fallible<()> { future.then_signal_fence_and_flush()?.wait(None)?; let _f18_ent1 = world.create_flyer(f18_id, Point3::new(0f64, 0f64, 0f64))?; - let _f18_ent2 = world.create_flyer(f18_id, Point3::new(40f64, -10f64, 10f64))?; + let _f18_ent2 = world.create_flyer(f8_id, Point3::new(80f64, 0f64, 120f64))?; let mut camera = ArcBallCamera::new(window.aspect_ratio_f64()?, 0.1, 3.4e+38); camera.set_distance(120.0); @@ -113,5 +113,7 @@ fn main() -> Fallible<()> { frame.submit(cb, &mut window)?; } + + shape_renderer.maintain(); } } diff --git a/libs/render/draw/shape_instance/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs index 2ebbd7dd..368a4809 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -16,31 +16,15 @@ use camera::CameraAbstract; use failure::{bail, ensure, Fallible}; use global_layout::GlobalSets; use nalgebra::Matrix4; -use nalgebra::Point3; -use omnilib::OmniLib; -use shape_chunk::{ - Chunk, ChunkIndex, ChunkPart, ClosedChunk, DrawSelection, DrawState, OpenChunk, - ShapeChunkManager, ShapeId, Vertex, -}; -use specs::{ - world::Index as EntityId, DispatcherBuilder, Entities, Join, ReadStorage, System, VecStorage, -}; -use std::{ - collections::HashMap, - mem, - sync::{Arc, RwLock}, - time::Instant, -}; -use vulkano::buffer::cpu_pool::CpuBufferPoolSubbuffer; -use vulkano::buffer::CpuAccessibleBuffer; -use vulkano::command_buffer::AutoCommandBufferBuilder; +use shape_chunk::{ChunkIndex, ClosedChunk, DrawSelection, ShapeChunkManager, ShapeId, Vertex}; +use specs::{world::Index as EntityId, Entities, Join, ReadStorage, System}; +use std::{collections::HashMap, sync::Arc, time::Instant}; use vulkano::{ buffer::{BufferAccess, BufferSlice, BufferUsage, CpuBufferPool, DeviceLocalBuffer}, - command_buffer::DrawIndirectCommand, + command_buffer::{AutoCommandBufferBuilder, DrawIndirectCommand}, descriptor::descriptor_set::{DescriptorSet, PersistentDescriptorSet}, device::Device, framebuffer::Subpass, - instance::QueueFamily, pipeline::{ depth_stencil::{Compare, DepthBounds, DepthStencil}, GraphicsPipeline, GraphicsPipelineAbstract, @@ -50,7 +34,7 @@ use vulkano::{ use window::GraphicsWindow; use world::{ component::{ShapeMesh, Transform}, - Entity, World, + World, }; mod vs { @@ -121,7 +105,11 @@ mod vs { 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); + 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; @@ -249,6 +237,7 @@ pub struct DynamicInstanceBlock { // 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, @@ -262,6 +251,7 @@ pub struct DynamicInstanceBlock { // 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>, @@ -278,8 +268,9 @@ pub struct DynamicInstanceBlock { flag_buffer: Arc>, // 4 bytes per entity; can infer position from index - xform_index_buffer: Arc>, - xform_index_buffer_pool: CpuBufferPool<[i32; BLOCK_SIZE]>, + xform_index_buffer_scratch: [u32; BLOCK_SIZE], + xform_index_buffer_pool: CpuBufferPool<[u32; BLOCK_SIZE]>, + 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 @@ -295,7 +286,7 @@ impl DynamicInstanceBlock { command_buffer_pool: CpuBufferPool, transform_buffer_pool: CpuBufferPool<[f32; 6]>, flag_buffer_pool: CpuBufferPool<[u32; 2]>, - xform_index_buffer_pool: CpuBufferPool<[i32; BLOCK_SIZE]>, + xform_index_buffer_pool: CpuBufferPool<[u32; BLOCK_SIZE]>, xform_buffer_pool: CpuBufferPool<[[f32; 6]; 14 * BLOCK_SIZE]>, device: Arc, ) -> Fallible { @@ -339,6 +330,7 @@ impl DynamicInstanceBlock { chunk_index, slot_reservations: [None; BLOCK_SIZE], entity_to_slot_map: HashMap::new(), + reservation_offset: 0, mark_buffer: [false; BLOCK_SIZE], descriptor_set, pds0: GraphicsWindow::empty_descriptor_set(pipeline.clone(), 0)?, @@ -351,6 +343,7 @@ impl DynamicInstanceBlock { 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, @@ -358,49 +351,81 @@ impl DynamicInstanceBlock { flag_buffer_scratch: [[0u32; 2]; BLOCK_SIZE], flag_buffer_pool, flag_buffer, - xform_index_buffer, + xform_index_buffer_scratch: [0u32; BLOCK_SIZE], xform_index_buffer_pool, + xform_index_buffer, xform_buffer, xform_buffer_pool, }) } - fn reserve_slot_for(&mut self, slot: SlotIndex, id: EntityId) { + // 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, + 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); - /* - let foo = &mut self.command_buffer_scratch[slot.0]; - foo.vertex_count = 10; - foo.instance_count = 1; - */ - } - fn find_free_slot(&self) -> Option { - for (slot_offset, entity_id) in self.slot_reservations.iter().enumerate() { - if entity_id.is_none() { - return Some(SlotIndex(slot_offset)); - } - } - None - } + // Update the draw commands and add an upload slot for the new one. + self.command_buffer_scratch[slot.0] = draw_command; + self.command_upload_set.push(slot.0); - fn reserve_free_slot(&mut self, id: EntityId, chunk_index: ChunkIndex) -> Option { - if chunk_index != self.chunk_index { - return None; - } - let maybe_slot_index = self.find_free_slot(); - if let Some(slot_index) = maybe_slot_index { - self.reserve_slot_for(slot_index, id); - } - maybe_slot_index + 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 { - *self.entity_to_slot_map.get(&id).unwrap() - } + assert!(self.entity_to_slot_map.contains_key(&id)); + let slot = *self.entity_to_slot_map.get(&id).unwrap(); + 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) { + fn swap(arr: &mut [T], a: usize, b: usize) + where + T: Copy, + { + let tmp = arr[a]; + arr[a] = arr[b]; + arr[b] = tmp; + } - fn get_command_buffer_slot(&mut self, slot_index: SlotIndex) -> &mut DrawIndirectCommand { - &mut self.command_buffer_scratch[slot_index.0] + let mut head = 0; + let mut tail = self.reservation_offset - 1; + + // 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] { + 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 we need to re-check head in case tail was itself unmarked. + head += 1; + } + } + self.reservation_offset = tail + 1; } fn get_transform_buffer_slot(&mut self, slot_index: SlotIndex) -> &mut [f32; 6] { @@ -411,19 +436,9 @@ impl DynamicInstanceBlock { &mut self.flag_buffer_scratch[slot_index.0] } - /* - fn get_upload_buffer(&mut self, slot_index: SlotIndex) -> Fallible<()> { - self.mark_buffer[slot_index.0] = true; - - Ok(()) - } - */ - fn update_buffers( &self, mut cbb: AutoCommandBufferBuilder, - pipeline: Arc, - chunk: &ClosedChunk, ) -> Fallible { let dic = self.command_buffer_scratch.to_vec(); let command_buffer_upload = self.command_buffer_pool.chunk(dic)?; @@ -442,7 +457,7 @@ impl DynamicInstanceBlock { pub fn render( &self, - mut cbb: AutoCommandBufferBuilder, + cbb: AutoCommandBufferBuilder, pipeline: Arc, chunk: &ClosedChunk, camera: &dyn CameraAbstract, @@ -457,7 +472,9 @@ impl DynamicInstanceBlock { pipeline.clone(), &window.dynamic_state, vec![chunk.vertex_buffer()], - ib.into_buffer_slice().slice(0..1).unwrap(), + ib.into_buffer_slice() + .slice(0..self.reservation_offset) + .unwrap(), ( self.pds0.clone(), self.pds1.clone(), @@ -474,6 +491,7 @@ pub struct ShapeRenderer { device: Arc, world: Arc, pipeline: Arc, + start_time: Instant, // 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. @@ -495,7 +513,7 @@ pub struct ShapeRenderer { command_buffer_pool: CpuBufferPool, transform_buffer_pool: CpuBufferPool<[f32; 6]>, flag_buffer_pool: CpuBufferPool<[u32; 2]>, - xform_index_buffer_pool: CpuBufferPool<[i32; BLOCK_SIZE]>, + xform_index_buffer_pool: CpuBufferPool<[u32; BLOCK_SIZE]>, xform_buffer_pool: CpuBufferPool<[[f32; 6]; 14 * BLOCK_SIZE]>, // FIXME: hunt down this max somewhere } @@ -507,6 +525,7 @@ impl ShapeRenderer { device: window.device(), world, pipeline, + start_time: Instant::now(), chunks, blocks: Vec::new(), upload_block_map: HashMap::new(), @@ -572,12 +591,18 @@ impl ShapeRenderer { self.chunks.finish(window) } - // First fit: find the first block with a free upload slot. - fn reserve_free_slot( + 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, - ) -> Fallible<(BlockIndex, SlotIndex)> { + draw_command: DrawIndirectCommand, + ) -> Fallible<()> { + assert!(!self.knows_entity(id)); + let chunk_index = self.chunks.find_chunk_for_shape(shape_id)?; // Note that we do not bother sorting blocks by chunk because we only have to care about @@ -585,8 +610,9 @@ impl ShapeRenderer { // 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().enumerate() { - if let Some(slot_index) = block.reserve_free_slot(id, chunk_index) { - return Ok((BlockIndex(block_index), slot_index)); + if let Some(_) = block.allocate_entity_slot(id, chunk_index, draw_command) { + self.upload_block_map.insert(id, BlockIndex(block_index)); + return Ok(()); } } @@ -602,24 +628,22 @@ impl ShapeRenderer { self.xform_buffer_pool.clone(), self.device.clone(), )?; - let slot_index = block.reserve_free_slot(id, chunk_index).unwrap(); + block + .allocate_entity_slot(id, chunk_index, draw_command) + .unwrap(); self.blocks.push(block); self.upload_block_map.insert(id, next_block_index); - Ok((next_block_index, slot_index)) + Ok(()) } - pub fn ensure_entity_slot( - &mut self, - id: EntityId, - shape_id: ShapeId, - ) -> Fallible<(BlockIndex, SlotIndex)> { - // Fast path: in most cases we'll already have a block. - if let Some(&block_index) = self.upload_block_map.get(&id) { - let slot_index = self.blocks[block_index.0].get_existing_slot(id); - return Ok((block_index, slot_index)); - } + fn get_entity_block_mut(&mut self, id: EntityId) -> &mut DynamicInstanceBlock { + let block_index = self.upload_block_map[&id]; + &mut self.blocks[block_index.0] + } - self.reserve_free_slot(id, shape_id) + fn get_entity_block(&self, id: EntityId) -> &DynamicInstanceBlock { + let block_index = self.upload_block_map[&id]; + &self.blocks[block_index.0] } pub fn chunks(&self) -> &ShapeChunkManager { @@ -630,37 +654,16 @@ impl ShapeRenderer { &self.blocks } - fn get_chunk_for_slot(&self, index: (BlockIndex, SlotIndex)) -> &ClosedChunk { - let (block_index, _) = index; - let block = &self.blocks[block_index.0]; + fn get_chunk_for_block(&self, block: &DynamicInstanceBlock) -> &ClosedChunk { self.chunks.at(block.chunk_index) } - fn get_command_buffer_slot( - &mut self, - index: (BlockIndex, SlotIndex), - ) -> &mut DrawIndirectCommand { - let (block_index, slot_index) = index; - self.blocks[block_index.0].get_command_buffer_slot(slot_index) - } - - fn get_transform_buffer_slot(&mut self, index: (BlockIndex, SlotIndex)) -> &mut [f32; 6] { - let (block_index, slot_index) = index; - self.blocks[block_index.0].get_transform_buffer_slot(slot_index) - } - - fn get_flag_buffer_slot(&mut self, index: (BlockIndex, SlotIndex)) -> &mut [u32; 2] { - let (block_index, slot_index) = index; - self.blocks[block_index.0].get_flag_buffer_slot(slot_index) - } - pub fn update_buffers( &self, mut cbb: AutoCommandBufferBuilder, ) -> Fallible { for block in self.blocks.iter() { - let chunk = self.chunks.get_chunk(block.chunk_index); - cbb = block.update_buffers(cbb, self.pipeline(), &chunk)?; + cbb = block.update_buffers(cbb)?; } Ok(cbb) } @@ -678,6 +681,12 @@ impl ShapeRenderer { } Ok(cbb) } + + pub fn maintain(&mut self) { + for block in self.blocks.iter_mut() { + block.maintain(); + } + } } pub struct ShapeRenderSystem<'b> { @@ -701,28 +710,38 @@ impl<'a, 'b> System<'a> for ShapeRenderSystem<'b> { ); fn run(&mut self, (entities, transform, shape_mesh): Self::SystemData) { + let start_time = self.renderer.start_time; for (entity, transform, shape_mesh) in (&entities, &transform, &shape_mesh).join() { - let index = self - .renderer - .ensure_entity_slot(entity.id(), shape_mesh.shape_id()) - .expect("unable to reserve instance slot"); + if !self.renderer.knows_entity(entity.id()) { + let chunk = self + .renderer + .chunks() + .get_chunk_for_shape(shape_mesh.shape_id()) + .expect("adding an instance for a non-existing shape"); + let chunk_part = chunk.part(shape_mesh.shape_id()).unwrap(); + let draw_command = chunk_part.draw_command(0, 1); + self.renderer + .allocate_entity_slot(entity.id(), shape_mesh.shape_id(), draw_command) + .expect("unable to reserve instance slot"); + } + let errata = { + let block = self.renderer.get_entity_block(entity.id()); + let chunk = self.renderer.get_chunk_for_block(block); + let chunk_part = chunk.part(shape_mesh.shape_id()).unwrap(); + chunk_part.widgets().errata() + }; + let block = self.renderer.get_entity_block_mut(entity.id()); + let slot = block.get_existing_slot(entity.id()); // Push all. - let chunk = self.renderer.get_chunk_for_slot(index); - let chunk_part = chunk.part(shape_mesh.shape_id()).unwrap(); - let errata = chunk_part.widgets().errata(); - *self.renderer.get_command_buffer_slot(index) = chunk_part.draw_command(0, 1); - *self.renderer.get_transform_buffer_slot(index) = [0f32; 6]; - let flag_slot = self.renderer.get_flag_buffer_slot(index); + *block.get_transform_buffer_slot(slot) = transform.compact(); + let flag_slot = block.get_flag_buffer_slot(slot); // FIXME: get time start somehow shape_mesh .draw_state() - .build_mask_into(&Instant::now(), errata, flag_slot); - - //let foo = self.acquire_upload_buffers(block_index, slot_index); - //self.blocks[block_index]; - //println!("{:?} => block_index: {:?}", entity.id(), block_index); + .build_mask_into(&start_time, errata, flag_slot) + .expect("failed to build flags for shape"); } } } @@ -730,6 +749,9 @@ impl<'a, 'b> System<'a> for ShapeRenderSystem<'b> { #[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; @@ -759,7 +781,7 @@ mod tests { 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))?; + let _t80_ent3 = world.create_ground_mover(t80_id, Point3::new(0f64, 0f64, 0f64))?; world.run(&mut dispatcher); world.destroy_entity(t80_ent2)?; @@ -770,66 +792,3 @@ mod tests { Ok(()) } } - -/* -// Types of data we want to be able to deal with. -// -// Static Immortal: -// CommandBuf: [ Name1(0...N), Name2(0...M), ...] -// BaseBuffer: [A, A, A, ... A{N}, B, B, B, ... B{M}]; A/B: [f32; 6] -// FlagsBuffer: [] -// XFormBuffer: [] -// -// We need to accumulate before uploading the command buffer, which means we need to be -// careful with the order in BaseBuffer. Assert that there are no xforms or flags on any of these. -// How much can we simplify the renderer if we know there are no xforms? -// -// Xforms vs no xforms -- most shapes have no xforms, even if they can be destroyed, or -// move around and be destroyed. How much can we simplify the renderer if we don't have -// xforms? Probably quite a bit. Is it worth having two pipelines? Benchmark to figure out -// how many fully dynamic shapes we can have. -// -// Fully dynamic: -// CommandBuf: [ E0, E1, E2, E3, ... EN ] <- updated on add/remove entity (as are all) -// BaseBuffer: [ B0, B1, B2, B3, ... BN ] <- updated every frame for movers, never for static -// FlagsBuffer: [ F0, F1, F2, F3, ... FN ] <- updated occasionally -// XformBuffer: [ X0..M, X0...L, X0...H ... X0...I ] <- updated every frame for some things -// -// Implement fullest feature set first. If we can render a million SOLDIER.SH, we can easily -// render a million TREE.SH. - -pub struct OpenChunkInstance { - open_chunk: OpenChunk, - command_buf: Vec, - base_buffer: Vec>, - flags_buffer: Vec<[u32; 2]>, -} - -pub struct InstanceSet { - // Offset of the chunk these instances draw from. - chunk_reference: usize, - - // Buffers for all instances stored in this instance set. One command per unique entity. - // 16 bytes per entity; index unnecessary for draw - command_buf: CpuAccessibleBuffer<[DrawIndirectCommand]>, - - // Base position and orientation in xyz+euler angles stored as 6 adjacent floats. - // 24 bytes per entity; buffer index inferable from drawing index - base_buffer: CpuAccessibleBuffer<[f32]>, // Flags buffers - - // 2 32bit flags words for each entity. - // 8 bytes per entity; buffer index inferable from drawing index - flags_buffer: CpuAccessibleBuffer<[u32]>, - - // 0 to 14 position/orientation [f32; 6], depending on the shape. - // assume 240 bytes per entity if we're talking about planes - // cannot infer position, so needs an index buffer - xform_buffer: CpuAccessibleBuffer<[f32]>, - - // 4 bytes per entity; can infer position from index - xform_index_buffer: CpuAccessibleBuffer<[i32]>, - // - // Total cost per entity is: 16 + 24 + 8 + 240 + 4 ~ 300 bytes per entity - // We cannot really upload more than 1MiB per frame, so... ~3000 planes -} -*/ diff --git a/libs/world/src/component/transform.rs b/libs/world/src/component/transform.rs index 66dc78ec..b0d90649 100644 --- a/libs/world/src/component/transform.rs +++ b/libs/world/src/component/transform.rs @@ -32,4 +32,16 @@ impl Transform { rotation: UnitQuaternion::identity(), } } + + // Convert to dense pack for upload. + pub fn compact(&self) -> [f32; 6] { + [ + self.position.x as f32, + self.position.y as f32, + self.position.z as f32, + 0f32, + 0f32, + 0f32, + ] + } } From 42a4eb695f2b2dc0393695eaa4c62b79dd2b762b Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sat, 7 Sep 2019 10:18:34 -0700 Subject: [PATCH 13/16] We need to manually set the indices. --- libs/render/draw/shape_instance/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/render/draw/shape_instance/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs index 368a4809..13ff73c0 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -365,7 +365,7 @@ impl DynamicInstanceBlock { &mut self, id: EntityId, chunk_index: ChunkIndex, - draw_command: DrawIndirectCommand, + mut draw_command: DrawIndirectCommand, ) -> Option { if chunk_index != self.chunk_index { return None; // block not for this chunk @@ -381,6 +381,7 @@ impl DynamicInstanceBlock { 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); From 3ff3b585eb778a4853e7d73775024cd665100d2f Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Sat, 7 Sep 2019 15:22:13 -0700 Subject: [PATCH 14/16] Add a dummy set to get things working. --- libs/render/buffer/global_layout/src/lib.rs | 1 + .../draw/shape_instance/examples/demo-life.rs | 123 ++++++++++++++++++ libs/render/draw/shape_instance/src/lib.rs | 28 +++- 3 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 libs/render/draw/shape_instance/examples/demo-life.rs diff --git a/libs/render/buffer/global_layout/src/lib.rs b/libs/render/buffer/global_layout/src/lib.rs index 2acd3167..01b43698 100644 --- a/libs/render/buffer/global_layout/src/lib.rs +++ b/libs/render/buffer/global_layout/src/lib.rs @@ -14,6 +14,7 @@ // along with OpenFA. If not, see . pub enum GlobalSets { + Global = 0, Atmosphere = 1, Stars = 2, ShapeBuffers = 3, 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..e2426b13 --- /dev/null +++ b/libs/render/draw/shape_instance/examples/demo-life.rs @@ -0,0 +1,123 @@ +// 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 (f18_id, fut1) = + shape_renderer.upload_shape("F18.SH", DrawSelection::NormalModel, &window)?; + let future = shape_renderer.ensure_uploaded(&window)?; + + assert!(fut1.is_none()); + future.then_signal_fence_and_flush()?.wait(None)?; + + const WIDTH: usize = 100; + const HEIGHT: usize = 100; + for i in 0..WIDTH { + for j in 0..HEIGHT { + const D: f64 = 180f64; + let _ent = world.create_flyer( + f18_id, + Point3::new(-50f64 * D + D * i as f64, 0f64, -50f64 * D + D * j as f64), + )?; + } + } + + 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-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()?; + + // 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/src/lib.rs b/libs/render/draw/shape_instance/src/lib.rs index 13ff73c0..93e5c51d 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -19,6 +19,8 @@ use nalgebra::Matrix4; use shape_chunk::{ChunkIndex, ClosedChunk, DrawSelection, ShapeChunkManager, ShapeId, Vertex}; use specs::{world::Index as EntityId, Entities, Join, ReadStorage, System}; use std::{collections::HashMap, sync::Arc, time::Instant}; +use vulkano::buffer::CpuAccessibleBuffer; +use vulkano::descriptor::descriptor_set::PersistentDescriptorSetBuilderArray; use vulkano::{ buffer::{BufferAccess, BufferSlice, BufferUsage, CpuBufferPool, DeviceLocalBuffer}, command_buffer::{AutoCommandBufferBuilder, DrawIndirectCommand}, @@ -54,6 +56,10 @@ mod vs { 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 { @@ -283,6 +289,7 @@ 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]>, @@ -333,9 +340,9 @@ impl DynamicInstanceBlock { reservation_offset: 0, mark_buffer: [false; BLOCK_SIZE], descriptor_set, - pds0: GraphicsWindow::empty_descriptor_set(pipeline.clone(), 0)?, - pds1: GraphicsWindow::empty_descriptor_set(pipeline.clone(), 1)?, - pds2: GraphicsWindow::empty_descriptor_set(pipeline.clone(), 2)?, + 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, @@ -493,6 +500,7 @@ pub struct ShapeRenderer { 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. @@ -522,11 +530,24 @@ 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, blocks: Vec::new(), upload_block_map: HashMap::new(), @@ -622,6 +643,7 @@ impl ShapeRenderer { 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(), From 0c72566b71d999258250ad91da13a88b44f9f188 Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Tue, 10 Sep 2019 22:04:25 -0700 Subject: [PATCH 15/16] Break everything again. --- .../shape_chunk/examples/demo-chunks.rs | 5 +- libs/render/buffer/shape_chunk/src/chunk.rs | 16 +- .../buffer/shape_chunk/src/chunk_manager.rs | 58 ++--- libs/render/buffer/shape_chunk/src/lib.rs | 18 +- libs/render/draw/shape_instance/Cargo.toml | 2 + .../shape_instance/examples/demo-instances.rs | 12 +- .../draw/shape_instance/examples/demo-life.rs | 113 ++++++++- libs/render/draw/shape_instance/src/lib.rs | 239 ++++++++++++------ libs/world/src/component/mod.rs | 2 +- libs/world/src/component/shape_mesh.rs | 30 ++- libs/world/src/lib.rs | 16 +- 11 files changed, 375 insertions(+), 136 deletions(-) diff --git a/libs/render/buffer/shape_chunk/examples/demo-chunks.rs b/libs/render/buffer/shape_chunk/examples/demo-chunks.rs index 1e11bc76..30ceeb7a 100644 --- a/libs/render/buffer/shape_chunk/examples/demo-chunks.rs +++ b/libs/render/buffer/shape_chunk/examples/demo-chunks.rs @@ -263,9 +263,8 @@ fn main() -> Fallible<()> { let future = chunk_man.finish(&window)?; future.then_signal_fence_and_flush()?.wait(None)?; - let chunk_index = chunk_man.find_chunk_for_shape(f18_id)?; - let chunk = chunk_man.at(chunk_index); - let f18_part = chunk.part(f18_id).unwrap(); + let chunk = chunk_man.get_chunk_for_shape(f18_id); + let f18_part = chunk.part(f18_id); // Upload transforms let transforms = vec![0f32, 0f32, 0f32, 0f32, 0f32, 0f32]; diff --git a/libs/render/buffer/shape_chunk/src/chunk.rs b/libs/render/buffer/shape_chunk/src/chunk.rs index 5c184d27..ebee21cd 100644 --- a/libs/render/buffer/shape_chunk/src/chunk.rs +++ b/libs/render/buffer/shape_chunk/src/chunk.rs @@ -15,6 +15,7 @@ use crate::{ texture_atlas::MegaAtlas, upload::{DrawSelection, ShapeUploader, ShapeWidgets, Vertex}, + ChunkIndex, }; use failure::{ensure, err_msg, Fallible}; use global_layout::GlobalSets; @@ -198,6 +199,7 @@ impl OpenChunk { } pub struct ClosedChunk { + chunk_index: ChunkIndex, vertex_buffer: Arc>, shape_ids: HashMap, chunk_parts: HashMap, @@ -207,6 +209,7 @@ pub struct ClosedChunk { impl ClosedChunk { pub fn new( chunk: OpenChunk, + chunk_index: ChunkIndex, pipeline: Arc, window: &GraphicsWindow, ) -> Fallible<(Self, Box)> { @@ -244,6 +247,7 @@ impl ClosedChunk { Ok(( ClosedChunk { + chunk_index, vertex_buffer, shape_ids: chunk.shape_ids, chunk_parts: chunk.chunk_parts, @@ -253,6 +257,10 @@ impl ClosedChunk { )) } + pub fn index(&self) -> ChunkIndex { + self.chunk_index + } + pub fn atlas_descriptor_set_ref(&self) -> Arc { self.atlas_descriptor_set.clone() } @@ -261,8 +269,8 @@ impl ClosedChunk { self.vertex_buffer.clone() } - pub fn part(&self, id: ShapeId) -> Option<&ChunkPart> { - self.chunk_parts.get(&id) + pub fn part(&self, id: ShapeId) -> &ChunkPart { + &self.chunk_parts[&id] } pub fn part_for(&self, name: &str) -> Fallible<&ChunkPart> { @@ -272,4 +280,8 @@ impl ClosedChunk { .ok_or_else(|| err_msg("shape not found"))?; Ok(&self.chunk_parts[id]) } + + pub fn all_shapes(&self) -> Vec { + self.chunk_parts.keys().cloned().collect::>() + } } diff --git a/libs/render/buffer/shape_chunk/src/chunk_manager.rs b/libs/render/buffer/shape_chunk/src/chunk_manager.rs index c9390543..e1b4b85b 100644 --- a/libs/render/buffer/shape_chunk/src/chunk_manager.rs +++ b/libs/render/buffer/shape_chunk/src/chunk_manager.rs @@ -13,24 +13,26 @@ // You should have received a copy of the GNU General Public License // along with OpenFA. If not, see . use crate::{ - chunk::{ClosedChunk, OpenChunk, ShapeId}, + chunk::{ChunkPart, ClosedChunk, OpenChunk, ShapeId}, upload::DrawSelection, }; -use failure::{bail, Fallible}; +use failure::Fallible; use lib::Library; use pal::Palette; -use std::{mem, sync::Arc}; +use std::{collections::HashMap, mem, sync::Arc}; use vulkano::{pipeline::GraphicsPipelineAbstract, sync::GpuFuture}; use window::GraphicsWindow; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct ChunkIndex(usize); +pub struct ChunkIndex(pub usize); pub struct ShapeChunkManager { pipeline: Arc, open_chunk: OpenChunk, - closed_chunks: Vec, + next_chunk_index: usize, + closed_chunks: HashMap, + shape_map: HashMap, } impl ShapeChunkManager { @@ -41,14 +43,12 @@ impl ShapeChunkManager { Ok(Self { pipeline, open_chunk: OpenChunk::new(window)?, - closed_chunks: Vec::new(), + next_chunk_index: 0, + closed_chunks: HashMap::new(), + shape_map: HashMap::new(), }) } - // pub fn create_building - - // pub fn create_airplane -- need to hook into shape state? - pub fn finish(&mut self, window: &GraphicsWindow) -> Fallible> { self.finish_open_chunk(window) } @@ -56,8 +56,16 @@ impl ShapeChunkManager { pub fn finish_open_chunk(&mut self, window: &GraphicsWindow) -> Fallible> { let mut open_chunk = OpenChunk::new(window)?; mem::swap(&mut open_chunk, &mut self.open_chunk); - let (chunk, future) = ClosedChunk::new(open_chunk, self.pipeline.clone(), window)?; - self.closed_chunks.push(chunk); + + let chunk_index = ChunkIndex(self.next_chunk_index); + self.next_chunk_index += 1; + let (chunk, future) = + ClosedChunk::new(open_chunk, chunk_index, self.pipeline.clone(), window)?; + for shape_id in chunk.all_shapes() { + self.shape_map.insert(shape_id, chunk_index); + } + self.closed_chunks.insert(chunk_index, chunk); + Ok(future) } @@ -80,30 +88,16 @@ impl ShapeChunkManager { Ok((shape_id, future)) } - // TODO: we should maybe speed this up with a hash from shape_id to chunk_index - pub fn find_chunk_for_shape(&self, shape_id: ShapeId) -> Fallible { - for (chunk_offset, chunk) in self.closed_chunks.iter().enumerate() { - if chunk.part(shape_id).is_some() { - return Ok(ChunkIndex(chunk_offset)); - } - } - bail!("shape_id {:?} has not been uploaded", shape_id) + pub fn part(&self, shape_id: ShapeId) -> &ChunkPart { + self.get_chunk_for_shape(shape_id).part(shape_id) } - pub fn get_chunk_for_shape(&self, shape_id: ShapeId) -> Fallible<&ClosedChunk> { - for chunk in self.closed_chunks.iter() { - if chunk.part(shape_id).is_some() { - return Ok(chunk); - } - } - bail!("shape_id {:?} has not been uploaded", shape_id) + pub fn get_chunk_for_shape(&self, shape_id: ShapeId) -> &ClosedChunk { + let chunk_id = self.shape_map[&shape_id]; + &self.closed_chunks[&chunk_id] } pub fn get_chunk(&self, chunk_index: ChunkIndex) -> &ClosedChunk { - &self.closed_chunks[chunk_index.0] - } - - pub fn at(&self, chunk_index: ChunkIndex) -> &ClosedChunk { - &self.closed_chunks[chunk_index.0] + &self.closed_chunks[&chunk_index] } } diff --git a/libs/render/buffer/shape_chunk/src/lib.rs b/libs/render/buffer/shape_chunk/src/lib.rs index 901a7240..75bbab21 100644 --- a/libs/render/buffer/shape_chunk/src/lib.rs +++ b/libs/render/buffer/shape_chunk/src/lib.rs @@ -21,7 +21,7 @@ mod upload; pub use chunk::{Chunk, ChunkPart, ClosedChunk, OpenChunk, ShapeId}; pub use chunk_manager::{ChunkIndex, ShapeChunkManager}; pub use draw_state::DrawState; -pub use upload::{DrawSelection, Vertex}; +pub use upload::{DrawSelection, ShapeErrata, Vertex}; mod vs { use vulkano_shaders::shader; @@ -129,19 +129,31 @@ mod test { "WAVE2.SH", ]; let mut chunks = Vec::new(); + let mut chunk_index = 0; for name in shapes { if skipped.contains(&name.as_str()) { continue; } if open_chunk.chunk_is_full() { - let (chunk, future) = ClosedChunk::new(open_chunk, pipeline.clone(), &window)?; + let (chunk, future) = ClosedChunk::new( + open_chunk, + ChunkIndex(chunk_index), + pipeline.clone(), + &window, + )?; + chunk_index += 1; future.then_signal_fence_and_flush()?.wait(None)?; chunks.push(chunk); open_chunk = OpenChunk::new(&window)?; } open_chunk.upload_shape(&name, DrawSelection::NormalModel, &palette, &lib, &window)?; } - let (chunk, future) = ClosedChunk::new(open_chunk, pipeline.clone(), &window)?; + let (chunk, future) = ClosedChunk::new( + open_chunk, + ChunkIndex(chunk_index), + pipeline.clone(), + &window, + )?; future.then_signal_fence_and_flush()?.wait(None)?; chunks.push(chunk); println!("CHUNK COUNT: {}", chunks.len()); diff --git a/libs/render/draw/shape_instance/Cargo.toml b/libs/render/draw/shape_instance/Cargo.toml index 35232b6a..ae654982 100644 --- a/libs/render/draw/shape_instance/Cargo.toml +++ b/libs/render/draw/shape_instance/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [dependencies] failure = "^ 0.1.2" nalgebra = "^ 0.18" +rayon = "^ 1.2" specs = "^ 0.15" vulkano = "^ 0.14" vulkano-shaders = "^ 0.14" @@ -23,5 +24,6 @@ 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 index b6a4c692..0253c020 100644 --- a/libs/render/draw/shape_instance/examples/demo-instances.rs +++ b/libs/render/draw/shape_instance/examples/demo-instances.rs @@ -51,8 +51,16 @@ fn main() -> Fallible<()> { 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))?; - let _f18_ent2 = world.create_flyer(f8_id, Point3::new(80f64, 0f64, 120f64))?; + 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); diff --git a/libs/render/draw/shape_instance/examples/demo-life.rs b/libs/render/draw/shape_instance/examples/demo-life.rs index e2426b13..48a4657a 100644 --- a/libs/render/draw/shape_instance/examples/demo-life.rs +++ b/libs/render/draw/shape_instance/examples/demo-life.rs @@ -17,10 +17,13 @@ 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}; -use specs::DispatcherBuilder; -use std::sync::Arc; +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; @@ -36,31 +39,45 @@ fn main() -> Fallible<()> { let world = Arc::new(World::new(lib)?); let mut shape_renderer = ShapeRenderer::new(world.clone(), &window)?; - let (f18_id, fut1) = + 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 { - const D: f64 = 180f64; - let _ent = world.create_flyer( - f18_id, - Point3::new(-50f64 * D + D * i as f64, 0f64, -50f64 * D + D * j as f64), - )?; + 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(120.0); + 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" => { @@ -81,14 +98,76 @@ fn main() -> Fallible<()> { } 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, "", &[]) + //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 shape_instance_updater); + world.run(&mut disp); } + println!("DUP: {:?}", dup.elapsed()); { let frame = window.begin_frame()?; @@ -101,7 +180,9 @@ fn main() -> Fallible<()> { 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), @@ -118,6 +199,10 @@ fn main() -> Fallible<()> { 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 index 93e5c51d..31ab3062 100644 --- a/libs/render/draw/shape_instance/src/lib.rs +++ b/libs/render/draw/shape_instance/src/lib.rs @@ -13,16 +13,16 @@ // You should have received a copy of the GNU General Public License // along with OpenFA. If not, see . use camera::CameraAbstract; -use failure::{bail, ensure, Fallible}; +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, ReadStorage, System}; +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::descriptor::descriptor_set::PersistentDescriptorSetBuilderArray; use vulkano::{ - buffer::{BufferAccess, BufferSlice, BufferUsage, CpuBufferPool, DeviceLocalBuffer}, + buffer::{BufferAccess, BufferUsage, CpuBufferPool, DeviceLocalBuffer}, command_buffer::{AutoCommandBufferBuilder, DrawIndirectCommand}, descriptor::descriptor_set::{DescriptorSet, PersistentDescriptorSet}, device::Device, @@ -35,7 +35,7 @@ use vulkano::{ }; use window::GraphicsWindow; use world::{ - component::{ShapeMesh, Transform}, + component::{ShapeMesh, ShapeMeshFlagBuffer, ShapeMeshTransformBuffer, Transform}, World, }; @@ -275,14 +275,15 @@ pub struct DynamicInstanceBlock { // 4 bytes per entity; can infer position from index xform_index_buffer_scratch: [u32; BLOCK_SIZE], - xform_index_buffer_pool: CpuBufferPool<[u32; BLOCK_SIZE]>, - xform_index_buffer: Arc>, + 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: Arc>, - xform_buffer_pool: CpuBufferPool<[[f32; 6]; 14 * BLOCK_SIZE]>, + xform_buffer_scratch: [[f32; 6]; 14 * BLOCK_SIZE], + xform_buffer_pool: CpuBufferPool<[f32; 6]>, + xform_buffer: Arc>, } impl DynamicInstanceBlock { @@ -293,8 +294,8 @@ impl DynamicInstanceBlock { command_buffer_pool: CpuBufferPool, transform_buffer_pool: CpuBufferPool<[f32; 6]>, flag_buffer_pool: CpuBufferPool<[u32; 2]>, - xform_index_buffer_pool: CpuBufferPool<[u32; BLOCK_SIZE]>, - xform_buffer_pool: CpuBufferPool<[[f32; 6]; 14 * BLOCK_SIZE]>, + xform_index_buffer_pool: CpuBufferPool, + xform_buffer_pool: CpuBufferPool<[f32; 6]>, device: Arc, ) -> Fallible { let command_buffer = DeviceLocalBuffer::array( @@ -315,13 +316,15 @@ impl DynamicInstanceBlock { BufferUsage::all(), device.active_queue_families(), )?; - let xform_index_buffer = DeviceLocalBuffer::new( + let xform_index_buffer = DeviceLocalBuffer::array( device.clone(), + BLOCK_SIZE, BufferUsage::all(), device.active_queue_families(), )?; - let xform_buffer = DeviceLocalBuffer::new( + let xform_buffer = DeviceLocalBuffer::array( device.clone(), + 14 * BLOCK_SIZE, BufferUsage::all(), device.active_queue_families(), )?; @@ -361,8 +364,9 @@ impl DynamicInstanceBlock { xform_index_buffer_scratch: [0u32; BLOCK_SIZE], xform_index_buffer_pool, xform_index_buffer, - xform_buffer, + xform_buffer_scratch: [[0f32; 6]; 14 * BLOCK_SIZE], xform_buffer_pool, + xform_buffer, }) } @@ -398,14 +402,13 @@ impl DynamicInstanceBlock { // 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 { - assert!(self.entity_to_slot_map.contains_key(&id)); - let slot = *self.entity_to_slot_map.get(&id).unwrap(); + 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) { + fn maintain(&mut self, removed: &mut Vec) -> bool { fn swap(arr: &mut [T], a: usize, b: usize) where T: Copy, @@ -418,10 +421,28 @@ impl DynamicInstanceBlock { 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); @@ -429,11 +450,15 @@ impl DynamicInstanceBlock { swap(&mut self.xform_index_buffer_scratch, head, tail); tail -= 1; } else { - // Note that we need to re-check head in case tail was itself unmarked. + // 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] { @@ -444,6 +469,10 @@ impl DynamicInstanceBlock { &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, @@ -460,6 +489,10 @@ impl DynamicInstanceBlock { 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) } @@ -507,7 +540,8 @@ pub struct ShapeRenderer { chunks: ShapeChunkManager, // All upload blocks. We will do one draw call per instance block each frame. - blocks: Vec, + 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, @@ -522,8 +556,8 @@ pub struct ShapeRenderer { command_buffer_pool: CpuBufferPool, transform_buffer_pool: CpuBufferPool<[f32; 6]>, flag_buffer_pool: CpuBufferPool<[u32; 2]>, - xform_index_buffer_pool: CpuBufferPool<[u32; BLOCK_SIZE]>, - xform_buffer_pool: CpuBufferPool<[[f32; 6]; 14 * BLOCK_SIZE]>, // FIXME: hunt down this max somewhere + xform_index_buffer_pool: CpuBufferPool, + xform_buffer_pool: CpuBufferPool<[f32; 6]>, // FIXME: hunt down this max somewhere } impl ShapeRenderer { @@ -549,7 +583,8 @@ impl ShapeRenderer { start_time: Instant::now(), base_descriptors: [global0, empty1, empty2], chunks, - blocks: Vec::new(), + 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()), @@ -625,23 +660,24 @@ impl ShapeRenderer { ) -> Fallible<()> { assert!(!self.knows_entity(id)); - let chunk_index = self.chunks.find_chunk_for_shape(shape_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().enumerate() { - if let Some(_) = block.allocate_entity_slot(id, chunk_index, draw_command) { - self.upload_block_map.insert(id, BlockIndex(block_index)); + 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.blocks.len()); + let next_block_index = BlockIndex(self.next_block_index); + self.next_block_index += 1; let mut block = DynamicInstanceBlock::new( - chunk_index, + chunk.index(), self.pipeline.clone(), &self.base_descriptors, self.command_buffer_pool.clone(), @@ -652,40 +688,35 @@ impl ShapeRenderer { self.device.clone(), )?; block - .allocate_entity_slot(id, chunk_index, draw_command) + .allocate_entity_slot(id, chunk.index(), draw_command) .unwrap(); - self.blocks.push(block); + 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) -> &mut DynamicInstanceBlock { - let block_index = self.upload_block_map[&id]; - &mut self.blocks[block_index.0] + 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 { - let block_index = self.upload_block_map[&id]; - &self.blocks[block_index.0] + &self.blocks[&self.upload_block_map[&id]] } pub fn chunks(&self) -> &ShapeChunkManager { &self.chunks } - pub fn blocks(&self) -> &Vec { - &self.blocks - } - fn get_chunk_for_block(&self, block: &DynamicInstanceBlock) -> &ClosedChunk { - self.chunks.at(block.chunk_index) + self.chunks.get_chunk(block.chunk_index) } pub fn update_buffers( &self, mut cbb: AutoCommandBufferBuilder, ) -> Fallible { - for block in self.blocks.iter() { + for (_, block) in self.blocks.iter() { cbb = block.update_buffers(cbb)?; } Ok(cbb) @@ -698,7 +729,7 @@ impl ShapeRenderer { window: &GraphicsWindow, ) -> Fallible { let chunk_man = &self.chunks; - for block in self.blocks.iter() { + for (_, block) in self.blocks.iter() { let chunk = chunk_man.get_chunk(block.chunk_index); cbb = block.render(cbb, self.pipeline(), &chunk, camera, window)?; } @@ -706,10 +737,61 @@ impl ShapeRenderer { } pub fn maintain(&mut self) { - for block in self.blocks.iter_mut() { - block.maintain(); + 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> { @@ -733,42 +815,49 @@ impl<'a, 'b> System<'a> for ShapeRenderSystem<'b> { ); fn run(&mut self, (entities, transform, shape_mesh): Self::SystemData) { - let start_time = self.renderer.start_time; + let now = Instant::now(); for (entity, transform, shape_mesh) in (&entities, &transform, &shape_mesh).join() { - if !self.renderer.knows_entity(entity.id()) { - let chunk = self - .renderer - .chunks() - .get_chunk_for_shape(shape_mesh.shape_id()) - .expect("adding an instance for a non-existing shape"); - let chunk_part = chunk.part(shape_mesh.shape_id()).unwrap(); - let draw_command = chunk_part.draw_command(0, 1); - self.renderer - .allocate_entity_slot(entity.id(), shape_mesh.shape_id(), draw_command) - .expect("unable to reserve instance slot"); - } - let errata = { - let block = self.renderer.get_entity_block(entity.id()); - let chunk = self.renderer.get_chunk_for_block(block); - let chunk_part = chunk.part(shape_mesh.shape_id()).unwrap(); - chunk_part.widgets().errata() - }; - let block = self.renderer.get_entity_block_mut(entity.id()); - let slot = block.get_existing_slot(entity.id()); - - // Push all. - *block.get_transform_buffer_slot(slot) = transform.compact(); - let flag_slot = block.get_flag_buffer_slot(slot); - - // FIXME: get time start somehow - shape_mesh - .draw_state() - .build_mask_into(&start_time, errata, flag_slot) - .expect("failed to build flags for shape"); + 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::*; diff --git a/libs/world/src/component/mod.rs b/libs/world/src/component/mod.rs index a4c500de..7cc9c140 100644 --- a/libs/world/src/component/mod.rs +++ b/libs/world/src/component/mod.rs @@ -18,6 +18,6 @@ pub mod transform; pub mod wheeled_dynamics; pub use flight_dynamics::FlightDynamics; -pub use shape_mesh::ShapeMesh; +pub use shape_mesh::{ShapeMesh, ShapeMeshFlagBuffer, ShapeMeshTransformBuffer}; pub use transform::Transform; pub use wheeled_dynamics::WheeledDynamics; diff --git a/libs/world/src/component/shape_mesh.rs b/libs/world/src/component/shape_mesh.rs index e26e5ae2..87c21ce1 100644 --- a/libs/world/src/component/shape_mesh.rs +++ b/libs/world/src/component/shape_mesh.rs @@ -12,7 +12,7 @@ // // You should have received a copy of the GNU General Public License // along with OpenFA. If not, see . -use shape_chunk::{DrawState, ShapeId}; +use shape_chunk::{DrawState, ShapeErrata, ShapeId}; use specs::{Component, VecStorage}; pub struct ShapeMesh { @@ -40,3 +40,31 @@ impl ShapeMesh { &self.draw_state } } + +pub struct ShapeMeshTransformBuffer { + pub buffer: [f32; 6], +} +impl Component for ShapeMeshTransformBuffer { + type Storage = VecStorage; +} +impl ShapeMeshTransformBuffer { + pub fn new() -> Self { + Self { buffer: [0f32; 6] } + } +} + +pub struct ShapeMeshFlagBuffer { + pub buffer: [u32; 2], + pub errata: ShapeErrata, +} +impl Component for ShapeMeshFlagBuffer { + type Storage = VecStorage; +} +impl ShapeMeshFlagBuffer { + pub fn new(errata: &ShapeErrata) -> Self { + Self { + buffer: [0u32; 2], + errata: errata.clone(), + } + } +} diff --git a/libs/world/src/lib.rs b/libs/world/src/lib.rs index c9bde762..f8d988a5 100644 --- a/libs/world/src/lib.rs +++ b/libs/world/src/lib.rs @@ -19,7 +19,7 @@ use failure::Fallible; use lib::Library; use nalgebra::Point3; use pal::Palette; -use shape_chunk::{DrawSelection, ShapeId}; +use shape_chunk::{ChunkPart, ShapeId}; use specs::{Builder, Dispatcher, World as SpecsWorld, WorldExt}; use std::sync::{Arc, RwLock}; @@ -39,6 +39,8 @@ impl World { ecs.register::(); ecs.register::(); ecs.register::(); + ecs.register::(); + ecs.register::(); ecs.register::(); Ok(Self { @@ -78,7 +80,13 @@ impl World { .build()) } - pub fn create_flyer(&self, shape_id: ShapeId, position: Point3) -> Fallible { + pub fn create_flyer( + &self, + shape_id: ShapeId, + position: Point3, + part: &ChunkPart, + ) -> Fallible { + let errata = part.widgets().errata(); Ok(self .ecs .write() @@ -88,6 +96,8 @@ impl World { .with(WheeledDynamics::new()) .with(FlightDynamics::new()) .with(ShapeMesh::new(shape_id)) + .with(ShapeMeshTransformBuffer::new()) + .with(ShapeMeshFlagBuffer::new(&errata)) .build()) } @@ -100,7 +110,7 @@ impl World { mod test { use super::*; use omnilib::OmniLib; - use shape_chunk::{ClosedChunk, OpenChunk}; + use shape_chunk::{ClosedChunk, DrawSelection, OpenChunk}; use window::{GraphicsConfigBuilder, GraphicsWindow}; #[test] From bba157087dcb289472e817b420820dd2784d805f Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Fri, 13 Sep 2019 19:01:42 -0700 Subject: [PATCH 16/16] Dead end at add_read_port -- cannot clone. --- apps/dump-sh/src/main.rs | 4 +-- libs/fnt/src/lib.rs | 12 +++----- libs/i386/src/disassembler.rs | 9 ++---- libs/i386/src/interpreter.rs | 29 +++++++++---------- .../shape_chunk/examples/demo-chunks.rs | 10 +++++-- libs/render/buffer/shape_chunk/src/chunk.rs | 4 +++ libs/render/buffer/shape_chunk/src/upload.rs | 25 +++++++++------- libs/render/draw/shape/src/upload.rs | 4 +-- libs/sh/src/instr/code.rs | 17 +++-------- 9 files changed, 54 insertions(+), 60 deletions(-) diff --git a/apps/dump-sh/src/main.rs b/apps/dump-sh/src/main.rs index ceb04038..dc95c694 100644 --- a/apps/dump-sh/src/main.rs +++ b/apps/dump-sh/src/main.rs @@ -104,7 +104,7 @@ fn main() -> Fallible<()> { for sh_instr in shape.instrs.iter() { if let sh::Instr::X86Code(x86) = sh_instr { let mut pos = 0; - for instr in &x86.bytecode.read().unwrap().instrs { + for instr in &x86.bytecode.borrow().instrs { for operand in &instr.operands { if let i386::Operand::Memory(memref) = operand { if let Ok(tramp) = shape.lookup_trampoline_by_offset( @@ -146,7 +146,7 @@ fn main() -> Fallible<()> { let mut dedup = HashMap::new(); for vinstr in shape.instrs { if let sh::Instr::X86Code(x86) = vinstr { - for instr in &x86.bytecode.read().unwrap().instrs { + for instr in &x86.bytecode.borrow().instrs { for operand in &instr.operands { if let i386::Operand::Memory(memref) = operand { let key = format!("{}", memref); diff --git a/libs/fnt/src/lib.rs b/libs/fnt/src/lib.rs index b60d6851..6eaf75a3 100644 --- a/libs/fnt/src/lib.rs +++ b/libs/fnt/src/lib.rs @@ -19,11 +19,7 @@ use failure::{bail, ensure, Fallible}; use i386::{ByteCode, Interpreter, Reg}; use image::{ImageBuffer, LumaA}; use peff::PE; -use std::{ - collections::HashMap, - mem, - sync::{Arc, RwLock}, -}; +use std::{cell::RefCell, collections::HashMap, mem, rc::Rc}; // Save chars to png when testing. const DUMP_CHARS: bool = false; @@ -32,7 +28,7 @@ pub struct GlyphInfo { pub glyph_index: u8, pub glyph_char: String, pub width: i32, - pub bytecode: Arc>, + pub bytecode: Rc>, } pub struct Fnt { @@ -96,7 +92,7 @@ impl Fnt { glyph_index, glyph_char, width, - bytecode: bytecode.into_arc(), + bytecode: bytecode.into_rc(), }, ); } @@ -113,7 +109,7 @@ impl Fnt { } let glyph = &self.glyphs[&glyph_index]; println!("{:<2} - {:04X}:", glyph.glyph_char, glyph.glyph_index); - println!("{}", glyph.bytecode.read().unwrap().show_relative(0)); + println!("{}", glyph.bytecode.borrow().show_relative(0)); { let mut interp = Interpreter::new(); diff --git a/libs/i386/src/disassembler.rs b/libs/i386/src/disassembler.rs index 30a7e65c..ec140a14 100644 --- a/libs/i386/src/disassembler.rs +++ b/libs/i386/src/disassembler.rs @@ -19,10 +19,7 @@ use ansi::ansi; use failure::{bail, ensure, Error, Fail, Fallible}; use log::trace; use reverse::bs2s; -use std::{ - fmt, mem, - sync::{Arc, RwLock}, -}; +use std::{cell::RefCell, fmt, mem, rc::Rc}; pub use crate::lut::{Memonic, HAS_INLINE_REG, OPCODES, PREFIX_CODES, USE_REG_OPCODES}; @@ -1055,8 +1052,8 @@ impl ByteCode { sz } - pub fn into_arc(self) -> Arc> { - Arc::new(RwLock::new(self)) + pub fn into_rc(self) -> Rc> { + Rc::new(RefCell::new(self)) } pub fn show_relative(&self, base: usize) -> String { diff --git a/libs/i386/src/interpreter.rs b/libs/i386/src/interpreter.rs index 9d12d04d..58e64652 100644 --- a/libs/i386/src/interpreter.rs +++ b/libs/i386/src/interpreter.rs @@ -20,11 +20,7 @@ use crate::{ }; use failure::{bail, ensure, Fallible}; use log::trace; -use std::{ - collections::HashMap, - mem, - sync::{Arc, RwLock}, -}; +use std::{cell::RefCell, collections::HashMap, mem, rc::Rc}; #[derive(Debug)] pub enum ExitInfo { @@ -41,7 +37,7 @@ impl ExitInfo { } } -#[derive(Eq, Ord, PartialOrd, PartialEq)] +#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)] struct MemMapR { start: u32, end: u32, @@ -55,7 +51,7 @@ impl MemMapR { } } -#[derive(Debug, Eq, Ord, PartialOrd, PartialEq)] +#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)] struct MemMapW { start: u32, end: u32, @@ -69,6 +65,7 @@ impl MemMapW { } } +#[derive(Clone)] pub struct Interpreter { registers: Vec, cf: bool, @@ -78,8 +75,8 @@ pub struct Interpreter { stack: Vec, memmap_w: Vec, memmap_r: Vec, - bytecode: Vec>>, - ports_r: HashMap u32 + Send + Sync>>, + bytecode: Vec>>, + ports_r: HashMap u32>>, trampolines: HashMap, } @@ -116,7 +113,7 @@ impl Interpreter { self.trampolines.insert(addr, (name.to_owned(), arg_count)); } - pub fn add_read_port(&mut self, addr: u32, func: Box u32 + Send + Sync>) { + pub fn add_read_port(&mut self, addr: u32, func: Box u32>) { self.ports_r.insert(addr, func); } @@ -166,10 +163,10 @@ impl Interpreter { bail!( "the address {:08X} is not mapped to a writable block", start - ); + ) } - pub fn add_code(&mut self, bc: Arc>) { + pub fn add_code(&mut self, bc: Rc>) { self.bytecode.push(bc); } @@ -177,10 +174,10 @@ impl Interpreter { self.bytecode.clear(); } - fn find_instr(&self) -> Fallible<(Arc>, usize)> { + fn find_instr(&self) -> Fallible<(Rc>, usize)> { trace!("searching for instr at ip: {:08X}", self.eip()); for bc_ref in self.bytecode.iter() { - let bc = bc_ref.read().unwrap(); + let bc = bc_ref.borrow(); if self.eip() >= bc.start_addr && self.eip() < bc.start_addr + bc.size { trace!("in bc at {:08X}", bc.start_addr); let mut pos = bc.start_addr; @@ -201,13 +198,13 @@ impl Interpreter { bail!( "attempted to jump to {:08X}, which is not in any code section", self.eip() - ); + ) } pub fn interpret(&mut self, at: u32) -> Fallible { *self.eip_mut() = at; let (bc_ref, mut offset) = self.find_instr()?; - let bc = bc_ref.read().unwrap(); + let bc = bc_ref.borrow(); while offset < bc.instrs.len() { let instr = &bc.instrs[offset]; trace!("{:3}:{:04X}: {}", offset, self.eip(), instr); diff --git a/libs/render/buffer/shape_chunk/examples/demo-chunks.rs b/libs/render/buffer/shape_chunk/examples/demo-chunks.rs index 30ceeb7a..13ac9d79 100644 --- a/libs/render/buffer/shape_chunk/examples/demo-chunks.rs +++ b/libs/render/buffer/shape_chunk/examples/demo-chunks.rs @@ -265,6 +265,7 @@ fn main() -> Fallible<()> { let chunk = chunk_man.get_chunk_for_shape(f18_id); let f18_part = chunk.part(f18_id); + let f18_widgets = *f18_part.widgets().clone(); // Upload transforms let transforms = vec![0f32, 0f32, 0f32, 0f32, 0f32, 0f32]; @@ -294,9 +295,12 @@ fn main() -> Fallible<()> { let xforms_len = f18_part.widgets().num_transformer_floats(); let mut xforms = Vec::with_capacity(xforms_len); xforms.resize(xforms_len, 0f32); - f18_part - .widgets() - .animate_into(&draw_state, draw_state.time_origin(), &now, &mut xforms)?; + f18_part.widgets_mut().animate_into( + &draw_state, + draw_state.time_origin(), + &now, + &mut xforms, + )?; let xforms_buffer = CpuAccessibleBuffer::from_iter( window.device(), BufferUsage::all(), diff --git a/libs/render/buffer/shape_chunk/src/chunk.rs b/libs/render/buffer/shape_chunk/src/chunk.rs index ebee21cd..367bec87 100644 --- a/libs/render/buffer/shape_chunk/src/chunk.rs +++ b/libs/render/buffer/shape_chunk/src/chunk.rs @@ -109,6 +109,10 @@ impl ChunkPart { pub fn widgets(&self) -> &ShapeWidgets { &self.shape_widgets } + + pub fn widgets_mut(&mut self) -> &mut ShapeWidgets { + &mut self.shape_widgets + } } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] diff --git a/libs/render/buffer/shape_chunk/src/upload.rs b/libs/render/buffer/shape_chunk/src/upload.rs index 521d6364..71205a88 100644 --- a/libs/render/buffer/shape_chunk/src/upload.rs +++ b/libs/render/buffer/shape_chunk/src/upload.rs @@ -24,6 +24,7 @@ use pal::Palette; use pic::Pic; use sh::{Facet, FacetFlags, Instr, RawShape, VertexBuf, X86Code, X86Trampoline, SHAPE_LOAD_BASE}; use std::{ + cell::RefCell, collections::{HashMap, HashSet}, sync::{Arc, RwLock}, time::Instant, @@ -423,12 +424,13 @@ impl BufferPropsManager { // the virtual machine interpreter, already set up, the xform_id it maps to // for upload, the code and data offsets, and all inputs that need to be // configured and where to put them. +#[derive(Clone)] pub struct Transformer { xform_id: u32, // 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: Arc>, + vm: Interpreter, code_offset: u32, data_offset: u32, inputs: Vec, @@ -441,7 +443,7 @@ impl Transformer { } pub fn transform( - &self, + &mut self, draw_state: &DrawState, start: &Instant, now: &Instant, @@ -454,7 +456,7 @@ impl Transformer { let bay_position = draw_state.bay_position() as u32; let thrust_vectoring = draw_state.thrust_vector_position() as i32 as u32; let wing_sweep = i32::from(draw_state.wing_sweep_angle()) as u32; - let mut vm = self.vm.write().unwrap(); + let mut vm = &mut self.vm; //.borrow_mut(); let t = (((*now - *start).as_millis() as u32) >> 4) & 0x0FFF; for input in &self.inputs { match input { @@ -525,9 +527,11 @@ impl Transformer { // Contains information about what parts of the shape can be mutated by // standard actions. e.g. Gears, flaps, etc. +#[derive(Clone)] 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. @@ -537,13 +541,14 @@ pub struct ShapeWidgets { impl ShapeWidgets { pub fn new(transformers: Vec, errata: ShapeErrata) -> Self { Self { - transformers, + num_transformer_floats: transformers.len() * 6, + transformers: transformers, errata, } } pub fn animate_into( - &self, + &mut self, draw_state: &DrawState, start: &Instant, now: &Instant, @@ -551,7 +556,7 @@ impl ShapeWidgets { ) -> Fallible<()> { assert!(buffer.len() >= self.num_transformer_floats()); let mut offset = 0; - for transformer in self.transformers.iter() { + for transformer in self.transformers.iter_mut() { let xform = transformer.transform(draw_state, start, now)?; buffer[offset..offset + 6].copy_from_slice(&xform); offset += 6; @@ -564,7 +569,7 @@ impl ShapeWidgets { } pub fn num_transformer_floats(&self) -> usize { - self.transformers.len() * 6 + self.num_transformer_floats } } @@ -683,7 +688,7 @@ impl ShapeUploader { sh: &'a RawShape, ) -> HashMap<&'a str, &'a X86Trampoline> { let mut out = HashMap::new(); - for instr in &x86.bytecode.read().unwrap().instrs { + for instr in &x86.bytecode.borrow().instrs { for operand in &instr.operands { if let i386::Operand::Memory(memref) = operand { if let Ok(tramp) = sh.lookup_trampoline_by_offset( @@ -703,7 +708,7 @@ impl ShapeUploader { ) -> Fallible> { let mut out = HashMap::new(); let mut push_value = 0; - for instr in &x86.bytecode.read().unwrap().instrs { + for instr in &x86.bytecode.borrow().instrs { if instr.memonic == i386::Memonic::Push { if let i386::Operand::Imm32s(v) = instr.operands[0] { push_value = (v as u32).wrapping_sub(SHAPE_LOAD_BASE); @@ -979,7 +984,7 @@ impl ShapeUploader { transformers.push(Transformer { xform_id, - vm: Arc::new(RwLock::new(interp)), + vm: 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/src/upload.rs b/libs/render/draw/shape/src/upload.rs index 49961815..ce2e625c 100644 --- a/libs/render/draw/shape/src/upload.rs +++ b/libs/render/draw/shape/src/upload.rs @@ -712,7 +712,7 @@ impl ShapeUploader { sh: &'a RawShape, ) -> HashMap<&'a str, &'a X86Trampoline> { let mut out = HashMap::new(); - for instr in &x86.bytecode.read().unwrap().instrs { + for instr in &x86.bytecode.borrow().instrs { for operand in &instr.operands { if let i386::Operand::Memory(memref) = operand { if let Ok(tramp) = sh.lookup_trampoline_by_offset( @@ -732,7 +732,7 @@ impl ShapeUploader { ) -> Fallible> { let mut out = HashMap::new(); let mut push_value = 0; - for instr in &x86.bytecode.read().unwrap().instrs { + for instr in &x86.bytecode.borrow().instrs { if instr.memonic == i386::Memonic::Push { if let i386::Operand::Imm32s(v) = instr.operands[0] { push_value = (v as u32).wrapping_sub(SHAPE_LOAD_BASE); diff --git a/libs/sh/src/instr/code.rs b/libs/sh/src/instr/code.rs index 52350cc4..d86201e7 100644 --- a/libs/sh/src/instr/code.rs +++ b/libs/sh/src/instr/code.rs @@ -20,12 +20,7 @@ use lazy_static::lazy_static; use log::trace; use peff::{Thunk, PE}; use reverse::bs2s; -use std::{ - cmp, - collections::HashSet, - mem, - sync::{Arc, RwLock}, -}; +use std::{cell::RefCell, cmp, collections::HashSet, mem, rc::Rc}; lazy_static! { pub static ref DATA_RELOCATIONS: HashSet = { @@ -236,7 +231,7 @@ pub struct X86Code { pub offset: usize, pub length: usize, pub data: *const u8, - pub bytecode: Arc>, + pub bytecode: Rc>, pub have_header: bool, } @@ -415,7 +410,7 @@ impl X86Code { offset: section_offset, length: section_length, data: pe.code[section_offset..].as_ptr(), - bytecode: bc.into_arc(), + bytecode: bc.into_rc(), have_header, }) } @@ -604,11 +599,7 @@ impl X86Code { ansi().green().bold(), hdr, ansi(), - self.bytecode - .read() - .unwrap() - .show_relative(show_offset) - .trim() + self.bytecode.borrow().show_relative(show_offset).trim() ) } }