instanced rendering, nuke multithreaded rendering
parent
9e38c48452
commit
91018922cb
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue