instanced rendering, nuke multithreaded rendering

ecs
q3k 2020-03-15 23:48:07 +01:00
parent 9e38c48452
commit 91018922cb
8 changed files with 142 additions and 151 deletions

View File

@ -2,11 +2,12 @@
#version 450
layout(push_constant) uniform UniformBufferObject {
mat4 model;
mat4 view;
} ubo;
layout(location = 0) in vec3 pos;
layout(location = 1) in vec3 color;
layout(location = 2) in mat4 model;
layout(location = 0) out vec3 fragColor;
@ -15,7 +16,7 @@ out gl_PerVertex {
};
void main() {
gl_Position = ubo.model * vec4(pos, 1.0);
gl_Position = ubo.view * model * vec4(pos, 1.0);
fragColor = color;
// Vulkanize

View File

@ -14,11 +14,7 @@ fn main() {
env_logger::init();
log::info!("Starting...");
let mut renderables = Vec::new();
for i in 1..1000 {
let elapsed = 0.0;
let transform = cgm::Matrix4::from_angle_z(cgm::Rad::from(cgm::Deg(elapsed as f32 * 0.180))) *
cgm::Matrix4::from_translation(cgm::Vector3::new(0.0, 0.0, (i as f32)/1000.0));
let mesh_cube = {
let vertices = Arc::new(vec![
data::Vertex::new([-0.5, -0.5, 0.0], [1.0, 0.0, 0.0]),
data::Vertex::new([0.5, -0.5, 0.0], [0.0, 1.0, 0.0]),
@ -28,13 +24,20 @@ fn main() {
let indices = Arc::new(vec![
0, 1, 2, 2, 3, 0,
]);
let demo = render::renderable::Mesh {
transform, vertices, indices
Arc::new(render::renderable::Mesh::new(vertices, indices))
};
let mut renderables: Vec<Arc<dyn Renderable>> = Vec::new();
for i in 1..100000 {
let transform = cgm::Matrix4::from_translation(cgm::Vector3::new(0.0, 0.0, (i as f32)/1000.0));
let cube = render::renderable::Object {
mesh: mesh_cube.clone(),
transform
};
renderables.push(Arc::new(demo.data().unwrap()));
renderables.push(Arc::new(cube));
}
let mut renderer = render::Renderer::initialize();
renderer.set_render_data(renderables);
renderer.set_renderables(renderables);
renderer.main_loop();
}

View File

@ -22,7 +22,7 @@ pub struct Renderer {
instance: vulkan::Instance<winit::Window>,
events_loop: EventsLoop,
render_data: Vec<Arc<renderable::Data>>,
renderables: Vec<Arc<dyn renderable::Renderable>>,
}
impl Renderer {
@ -34,12 +34,12 @@ impl Renderer {
Self {
instance,
events_loop,
render_data: vec![],
renderables: vec![],
}
}
pub fn set_render_data(&mut self, render_data: Vec<Arc<renderable::Data>>) {
self.render_data = render_data;
pub fn set_renderables(&mut self, renderables: Vec<Arc<dyn renderable::Renderable>>) {
self.renderables = renderables;
}
fn init_window(instance: Arc<vi::Instance>) -> (EventsLoop, Arc<vs::Surface<Window>>) {
@ -53,7 +53,7 @@ impl Renderer {
}
fn draw_frame(&mut self) {
self.instance.flip(self.render_data.clone());
self.instance.flip(self.renderables.clone());
}
pub fn main_loop(&mut self) {

View File

@ -1,5 +1,7 @@
use std::hash;
use std::sync::Arc;
use std::sync::Mutex;
use std::time;
use cgmath as cgm;
use vulkano::device as vd;
@ -9,7 +11,7 @@ use vulkano::sync::GpuFuture;
use crate::render::vulkan::data;
pub trait Renderable {
fn data(&self) -> Option<Data> {
fn render_data(&self) -> Option<(Arc<Mesh>, cgm::Matrix4<f32>)> {
None
}
}
@ -19,26 +21,30 @@ struct VulkanData {
ibuffer: Arc<vb::ImmutableBuffer<[u16]>>,
}
pub struct Data {
pub struct Mesh {
vertices: Arc<Vec<data::Vertex>>,
indices: Arc<Vec<u16>>,
transform: cgm::Matrix4<f32>,
id: u64,
// vulkan buffers cache
vulkan: Mutex<Option<VulkanData>>,
}
impl Data {
impl Mesh {
pub fn new(
vertices: Arc<Vec<data::Vertex>>,
indices: Arc<Vec<u16>>,
transform: cgm::Matrix4<f32>,
) -> Data {
Data {
vertices, indices, transform,
) -> Self {
Self {
vertices, indices,
// TODO: use a better method
id: time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos() as u64,
vulkan: Mutex::new(None),
}
}
pub fn get_id(&self) -> u64 { self.id }
pub fn vulkan_buffers(
&self,
graphics_queue: Arc<vd::Queue>,
@ -72,20 +78,29 @@ impl Data {
},
}
}
}
pub fn get_transform(&self) -> cgm::Matrix4<f32> {
self.transform.clone()
impl hash::Hash for Mesh {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
pub struct Mesh {
pub vertices: Arc<Vec<data::Vertex>>,
pub indices: Arc<Vec<u16>>,
impl PartialEq for Mesh {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Mesh {}
pub struct Object {
pub mesh: Arc<Mesh>,
pub transform: cgm::Matrix4<f32>,
}
impl Renderable for Mesh {
fn data(&self) -> Option<Data> {
Some(Data::new(self.vertices.clone(), self.indices.clone(), self.transform.clone()))
impl Renderable for Object {
fn render_data(&self) -> Option<(Arc<Mesh>, cgm::Matrix4<f32>)> {
Some((self.mesh.clone(), self.transform.clone()))
}
}

View File

@ -8,12 +8,28 @@ pub struct Vertex {
impl Vertex {
pub fn new(pos: [f32; 3], color: [f32; 3]) -> Self {
Self { pos, color }
Self {
pos, color,
}
}
}
vulkano::impl_vertex!(Vertex, pos, color);
pub struct Instance {
model: [f32; 16],
}
impl Instance {
pub fn new(model: &cgm::Matrix4<f32>) -> Self {
let slice: &[f32; 16] = model.as_ref();
Self {
model: slice.clone(),
}
}
}
vulkano::impl_vertex!(Instance, model);
#[derive(Copy, Clone)]
pub struct UniformBufferObject {
pub model: cgm::Matrix4<f32>,
pub view: cgm::Matrix4<f32>,
}

View File

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::sync::Arc;
use std::time;
use log;
@ -142,12 +143,9 @@ impl<WT: 'static + Send + Sync> Instance<WT> {
fn make_graphics_commands(
&mut self,
render_data: &Vec<Arc<renderable::Data>>,
renderables: &Vec<Arc<dyn renderable::Renderable>>,
) -> Vec<Box<vc::AutoCommandBuffer>> {
let device = self.surface_binding().device.clone();
let rp = self.swapchain_binding().render_pass.clone();
let queue = self.surface_binding().graphics_queue.clone();
let pipeline = self.pipeline.as_ref().unwrap().get_pipeline().clone();
let dimensions = self.dimensions();
let view = cgm::Matrix4::look_at(
@ -162,27 +160,78 @@ impl<WT: 'static + Send + Sync> Instance<WT> {
10.0
);
// Split work into N vectors (one per worker)
// This is not fair to workers, but good enough for now.
let nworkers = self.workers.len();
let nwork = render_data.len();
let nperworker = (nwork / nworkers) + 1; // last worker will be underloaded
let mut buffers: Vec<Box<vc::AutoCommandBuffer>> = vec![];
let work = render_data.chunks(nperworker);
let futures = self.workers.iter().zip(work).map(|(w, c)| {
w.render(device.clone(), queue.clone(), rp.clone(), pipeline.clone(), view.clone(), proj.clone(), c.to_vec())
});
// Sort renderables by mesh.
let mut meshes: HashMap<Arc<renderable::Mesh>, Vec<cgm::Matrix4<f32>>> = HashMap::new();
for r in renderables {
if let Some((mesh, transform)) = r.render_data() {
let entry = meshes.entry(mesh.clone()).or_insert(vec![]);
entry.push(transform);
}
}
futures.map(|r| { r.recv().unwrap() }).collect()
let device = self.surface_binding().device.clone();
let queue = self.surface_binding().graphics_queue.clone();
let rp = self.swapchain_binding().render_pass.clone();
let pipeline = self.pipeline.as_ref().unwrap().get_pipeline().clone();
for (mesh, transforms) in meshes {
let mut builder = vc::AutoCommandBufferBuilder::secondary_graphics_one_time_submit(
device.clone(), queue.family(), vf::Subpass::from(rp.clone(), 0).unwrap()).unwrap();
let ubo = data::UniformBufferObject {
view: proj * view,
};
let (instancebuffer, future) = vb::immutable::ImmutableBuffer::from_iter(
transforms.iter().map(|t| { data::Instance::new(t) }),
vb::BufferUsage::vertex_buffer(),
queue.clone(),
).unwrap();
future.flush().unwrap();
let (vbuffer, ibuffer) = mesh.vulkan_buffers(queue.clone());
builder = builder.draw_indexed(pipeline.clone(), &vc::DynamicState::none(),
vec![vbuffer.clone(), instancebuffer], ibuffer.clone(), (), ubo).unwrap();
buffers.push(Box::new(builder.build().unwrap()));
}
return buffers;
}
fn make_command_buffer(
&mut self,
framebuffer: Arc<dyn vf::FramebufferAbstract + Send + Sync>,
batches: Vec<Box<vc::AutoCommandBuffer>>,
) -> Arc<vc::AutoCommandBuffer> {
let device = self.surface_binding().device.clone();
let qf = self.surface_binding().graphics_queue.family();
let mut primary = vc::AutoCommandBufferBuilder::primary_one_time_submit(device.clone(), qf)
.unwrap()
.begin_render_pass(framebuffer.clone(), false, vec![[0.0, 0.0, 0.0, 1.0].into()])
.unwrap();
for batch in batches {
unsafe {
primary = primary.execute_commands(batch).unwrap();
}
}
Arc::new(primary.end_render_pass().unwrap().build().unwrap())
}
// (╯°□°)╯︵ ┻━┻
pub fn flip(
&mut self,
render_data: Vec<Arc<renderable::Data>>,
renderables: Vec<Arc<dyn renderable::Renderable>>,
) {
// Build batch command buffer as early as possible.
let mut batches = self.make_graphics_commands(&render_data);
let mut batches = self.make_graphics_commands(&renderables);
match &self.previous_frame_end {
None => (),
@ -192,7 +241,7 @@ impl<WT: 'static + Send + Sync> Instance<WT> {
if !self.armed {
self.arm();
// Rearming means the batch is invalid - rebuild it.
batches = self.make_graphics_commands(&render_data);
batches = self.make_graphics_commands(&renderables);
}
let chain = self.swapchain_binding().chain.clone();
@ -239,30 +288,6 @@ impl<WT: 'static + Send + Sync> Instance<WT> {
[dimensions_u32[0] as f32, dimensions_u32[1] as f32]
}
fn make_command_buffer(
&mut self,
framebuffer: Arc<dyn vf::FramebufferAbstract + Send + Sync>,
batches: Vec<Box<vc::AutoCommandBuffer>>,
) -> Arc<vc::AutoCommandBuffer> {
let device = self.surface_binding().device.clone();
let qf = self.surface_binding().graphics_queue.family();
let mut primary = vc::AutoCommandBufferBuilder::primary_one_time_submit(device.clone(), qf)
.unwrap()
.begin_render_pass(framebuffer.clone(), false, vec![[0.0, 0.0, 0.0, 1.0].into()])
.unwrap();
for batch in batches {
unsafe {
primary = primary.execute_commands(batch).unwrap();
}
}
Arc::new(primary.end_render_pass().unwrap().build().unwrap())
}
fn init_debug_callback(instance: &Arc<vi::Instance>) -> vi::debug::DebugCallback {
let mt = vi::debug::MessageTypes {
error: true,

View File

@ -10,6 +10,7 @@ use vulkano::format::Format;
use vulkano::framebuffer as vf;
use vulkano::pipeline as vp;
use vulkano::pipeline::shader as vps;
use vulkano::pipeline::vertex as vpv;
use crate::render::vulkan::data;
use crate::render::vulkan::shaders;
@ -45,6 +46,10 @@ impl Forward {
location: 1..2, format: Format::R32G32B32Sfloat,
name: Some(Cow::Borrowed("color")),
},
vps::ShaderInterfaceDefEntry {
location: 2..6, format: Format::R32G32B32A32Sfloat,
name: Some(Cow::Borrowed("model")),
},
],
outputs: vec![
vps::ShaderInterfaceDefEntry {
@ -99,7 +104,7 @@ impl Forward {
// against most existing software and practices. This might bite us in the ass at some
// point in the future.
let pipeline = Arc::new(vp::GraphicsPipeline::start()
.vertex_input_single_buffer::<data::Vertex>()
.vertex_input(vpv::OneVertexOneInstanceDefinition::<data::Vertex, data::Instance>::new())
.vertex_shader(vertex_shader.entry_point(), ())
.triangle_list()
.primitive_restart(false)

View File

@ -1,36 +1,12 @@
use log;
use std::sync::Arc;
use std::sync::mpsc;
use std::thread;
use cgmath as cgm;
use vulkano::command_buffer as vc;
use vulkano::device as vd;
use vulkano::framebuffer as vf;
use crate::render::renderable;
use crate::render::vulkan::data;
use crate::render::vulkan::pipeline;
enum Command {
Render(CommandRender),
Exit,
}
struct CommandRender {
device: Arc<vd::Device>,
queue: Arc<vd::Queue>,
render_pass: Arc<dyn vf::RenderPassAbstract + Send + Sync>,
pipeline: Arc<pipeline::VulkanoPipeline>,
matrix_v: cgm::Matrix4<f32>,
matrix_p: cgm::Matrix4<f32>,
data: Vec<Arc<renderable::Data>>,
result: mpsc::Sender<Box<vc::AutoCommandBuffer>>,
}
pub struct Worker {
handle: Option<thread::JoinHandle<()>>,
control: mpsc::Sender<Command>,
@ -56,9 +32,6 @@ impl Worker {
log::info!("Worker {} exiting", id);
done = true;
}
Command::Render(r) => {
Worker::work_render(r);
}
}
},
}
@ -74,53 +47,6 @@ impl Worker {
control
}
}
fn work_render(r: CommandRender) {
let qf = r.queue.family();
let mut builder = vc::AutoCommandBufferBuilder::secondary_graphics_one_time_submit(
r.device, qf, vf::Subpass::from(r.render_pass, 0).unwrap()).unwrap();
for d in r.data {
let ubo = data::UniformBufferObject {
model: r.matrix_p.clone() * r.matrix_v.clone() * d.get_transform(),
};
let (vbuffer, ibuffer) = d.vulkan_buffers(r.queue.clone());
builder = builder.draw_indexed(r.pipeline.clone(), &vc::DynamicState::none(),
vec![vbuffer.clone()],
ibuffer.clone(),
(),
ubo).unwrap();
}
let buffer = builder.build().unwrap();
r.result.send(Box::new(buffer)).unwrap();
}
pub fn render(
&self,
device: Arc<vd::Device>,
queue: Arc<vd::Queue>,
render_pass: Arc<dyn vf::RenderPassAbstract + Send + Sync>,
pipeline: Arc<pipeline::VulkanoPipeline>,
matrix_v: cgm::Matrix4<f32>,
matrix_p: cgm::Matrix4<f32>,
data: Vec<Arc<renderable::Data>>,
) -> mpsc::Receiver<Box<vc::AutoCommandBuffer>> {
let (result, rx) = mpsc::channel();
let req = Command::Render(CommandRender {
device, queue, render_pass, pipeline,
matrix_v, matrix_p, data,
result
});
self.control.send(req).unwrap();
return rx;
}
}
impl Drop for Worker {