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