added obj loader
This commit is contained in:
parent
a99a15dded
commit
dce8b57b0e
|
@ -69,6 +69,15 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.7"
|
||||
|
@ -181,6 +190,16 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "cgmath"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.50"
|
||||
|
@ -290,6 +309,8 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
|||
name = "eruption"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cgmath",
|
||||
"tobj",
|
||||
"vulkano",
|
||||
"vulkano-shaders",
|
||||
"vulkano-win",
|
||||
|
@ -571,6 +592,15 @@ dependencies = [
|
|||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.11"
|
||||
|
@ -979,6 +1009,15 @@ dependencies = [
|
|||
"strict-num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tobj"
|
||||
version = "3.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57381207291289bad19de63acd3fbf5948ff99b2868116c367b7224c37d55f90"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.1"
|
||||
|
|
|
@ -10,4 +10,8 @@ authors = ["Sven Vogel"]
|
|||
vulkano = "0.33.0"
|
||||
vulkano-win = "0.33.0"
|
||||
vulkano-shaders = "0.33.0"
|
||||
winit = "0.28.3"
|
||||
winit = "0.28.3"
|
||||
|
||||
tobj = "3.2.5"
|
||||
|
||||
cgmath = "0.18.0"
|
|
@ -0,0 +1,52 @@
|
|||
# Blender 3.4.1 MTL File: 'example-scene.blend'
|
||||
# www.blender.org
|
||||
|
||||
newmtl glass
|
||||
Ns 1000.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
|
||||
newmtl green
|
||||
Ns 250.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.006232 0.800000 0.014503
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
|
||||
newmtl light
|
||||
Ns 360.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.000000
|
||||
d 1.000000
|
||||
illum 2
|
||||
|
||||
newmtl red
|
||||
Ns 250.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.006232 0.009300
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
|
||||
newmtl white
|
||||
Ns 250.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
File diff suppressed because it is too large
Load Diff
|
@ -3,4 +3,4 @@ mod shader;
|
|||
|
||||
fn main() {
|
||||
vulkan::init();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
pub(crate) mod vs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "vertex",
|
||||
path: "src/shader/src/composite.vert"
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod fs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "fragment",
|
||||
include: ["src/shader/src"],
|
||||
path: "src/shader/src/composite.frag"
|
||||
}
|
||||
}
|
|
@ -1,16 +1,20 @@
|
|||
#version 450
|
||||
|
||||
#include <pathtracing/pathtracer.glsl>
|
||||
|
||||
layout(location = 0) in vec2 texture_coordinate;
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D image;
|
||||
|
||||
layout(location = 0) out vec4 frag_color;
|
||||
|
||||
void main() {
|
||||
|
||||
vec2 uv = texture_coordinate;
|
||||
|
||||
vec3 color = trace_path(uv);
|
||||
vec3 color = texture(image, uv).rgb;
|
||||
|
||||
// TODO: tonemapping
|
||||
// TODO: denoising
|
||||
// TODO: bloom
|
||||
|
||||
frag_color = vec4(color, 1.0);
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
|
||||
use vulkano::device::Queue;
|
||||
use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint};
|
||||
use vulkano::render_pass::{Framebuffer, RenderPass};
|
||||
use std::sync::Arc;
|
||||
use vulkano::buffer::{Subbuffer};
|
||||
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, RenderPassBeginInfo, SubpassContents};
|
||||
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
|
||||
use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet};
|
||||
use vulkano::image::{ImageViewAbstract, StorageImage};
|
||||
use vulkano::image::view::ImageView;
|
||||
use vulkano::pipeline::graphics::viewport::Viewport;
|
||||
use vulkano::sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode};
|
||||
use crate::shader::create_final_composite;
|
||||
use crate::shader::pathtracing::PathtracerPipeline;
|
||||
use crate::vulkan::Renderer;
|
||||
use crate::vulkan::textured_quad::{create_quad_buffer, TexturedVertex};
|
||||
|
||||
pub(crate) mod vs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "vertex",
|
||||
path: "src/shader/composite/final.vert"
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod fs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "fragment",
|
||||
include: ["src/shader/src"],
|
||||
path: "src/shader/composite/final.frag"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextureDrawPipeline {
|
||||
gfx_queue: Arc<Queue>,
|
||||
pub(crate) pipeline: Arc<GraphicsPipeline>,
|
||||
command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
|
||||
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
|
||||
vertices: Subbuffer<[TexturedVertex]>,
|
||||
indices: Subbuffer<[u32]>,
|
||||
}
|
||||
|
||||
impl TextureDrawPipeline {
|
||||
pub fn new(renderer: &Renderer, gfx_queue: &Arc<Queue>, render_pass: &Arc<RenderPass>) -> Self {
|
||||
let (vertices, indices) = create_quad_buffer(&renderer.memory_allocator);
|
||||
|
||||
let composite = create_final_composite(render_pass, &gfx_queue.device().clone());
|
||||
|
||||
Self {
|
||||
gfx_queue: gfx_queue.clone(),
|
||||
pipeline: composite.pipeline,
|
||||
command_buffer_allocator: renderer.command_buffer_allocator.clone(),
|
||||
descriptor_set_allocator: renderer.descriptor_set_allocator.clone(),
|
||||
vertices,
|
||||
indices
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, frame_buffer: Arc<Framebuffer>, viewport: &Viewport, pathtracer: &PathtracerPipeline) -> PrimaryAutoCommandBuffer {
|
||||
let set = self.create_image_sampler_nearest(pathtracer.image());
|
||||
|
||||
// In order to draw, we have to build a *command buffer*. The command buffer object
|
||||
// holds the list of commands that are going to be executed.
|
||||
//
|
||||
// Building a command buffer is an expensive operation (usually a few hundred
|
||||
// microseconds), but it is known to be a hot path in the driver and is expected to
|
||||
// be optimized.
|
||||
//
|
||||
// Note that we have to pass a queue family when we create the command buffer. The
|
||||
// command buffer will only be executable on that given queue family.
|
||||
let mut builder = AutoCommandBufferBuilder::primary(
|
||||
&self.command_buffer_allocator,
|
||||
self.gfx_queue.queue_family_index(),
|
||||
CommandBufferUsage::OneTimeSubmit,
|
||||
).unwrap();
|
||||
|
||||
builder
|
||||
// Before we can draw, we have to *enter a render pass*.
|
||||
.begin_render_pass(
|
||||
RenderPassBeginInfo {
|
||||
// A list of values to clear the attachments with. This list contains
|
||||
// one item for each attachment in the render pass. In this case, there
|
||||
// is only one attachment, and we clear it with black.
|
||||
//
|
||||
// Only attachments that have `LoadOp::Clear` are provided with clear
|
||||
// values, any others should use `ClearValue::None` as the clear value.
|
||||
clear_values: vec![Some([0.0, 0.0, 0.0, 1.0].into())],
|
||||
|
||||
..RenderPassBeginInfo::framebuffer(frame_buffer)
|
||||
},
|
||||
// The contents of the first (and only) subpass. This can be either
|
||||
// `Inline` or `SecondaryCommandBuffers`. The latter is a bit more advanced
|
||||
// and is not covered here.
|
||||
SubpassContents::Inline,
|
||||
)
|
||||
.unwrap()
|
||||
// We are now inside the first subpass of the render pass.
|
||||
.set_viewport(0, [viewport.clone()])
|
||||
.bind_pipeline_graphics(self.pipeline.clone())
|
||||
.bind_vertex_buffers(0, self.vertices.clone())
|
||||
.bind_index_buffer(self.indices.clone())
|
||||
.bind_descriptor_sets(PipelineBindPoint::Graphics, self.pipeline.layout().clone(), 0, set)
|
||||
// We add a draw command.
|
||||
.draw_indexed(self.indices.len() as u32, 1, 0, 0, 0)
|
||||
.unwrap()
|
||||
// We leave the render pass. Note that if we had multiple subpasses we could
|
||||
// have called `next_subpass` to jump to the next subpass.
|
||||
.end_render_pass()
|
||||
.unwrap();
|
||||
|
||||
builder.build().unwrap()
|
||||
}
|
||||
|
||||
fn create_image_sampler_nearest(
|
||||
&self,
|
||||
image: &Arc<ImageView<StorageImage>>,
|
||||
) -> Arc<PersistentDescriptorSet> {
|
||||
let layout = self.pipeline.layout().set_layouts().get(0).unwrap();
|
||||
let sampler = Sampler::new(
|
||||
self.gfx_queue.device().clone(),
|
||||
SamplerCreateInfo {
|
||||
mag_filter: Filter::Linear,
|
||||
min_filter: Filter::Linear,
|
||||
address_mode: [SamplerAddressMode::Repeat; 3],
|
||||
mipmap_mode: SamplerMipmapMode::Nearest,
|
||||
..Default::default()
|
||||
},
|
||||
).unwrap();
|
||||
|
||||
PersistentDescriptorSet::new(
|
||||
&self.descriptor_set_allocator,
|
||||
layout.clone(),
|
||||
[WriteDescriptorSet::image_view_sampler(
|
||||
0,
|
||||
image.clone(),
|
||||
sampler,
|
||||
)],
|
||||
).unwrap()
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod composite;
|
||||
pub mod composite;
|
||||
pub mod pathtracing;
|
||||
|
||||
use std::sync::Arc;
|
||||
use vulkano::device::Device;
|
||||
|
@ -7,21 +8,27 @@ use vulkano::pipeline::graphics::vertex_input::Vertex;
|
|||
use vulkano::pipeline::graphics::viewport::ViewportState;
|
||||
use vulkano::pipeline::GraphicsPipeline;
|
||||
use vulkano::render_pass::{RenderPass, Subpass};
|
||||
use crate::vulkan::Vertex2d;
|
||||
use vulkano::shader::ShaderModule;
|
||||
|
||||
pub fn create_program(render_pass: &Arc<RenderPass>, device: &Arc<Device>) -> Arc<GraphicsPipeline> {
|
||||
pub struct Program {
|
||||
pub pipeline: Arc<GraphicsPipeline>,
|
||||
pub vertex: Arc<ShaderModule>,
|
||||
pub fragement: Arc<ShaderModule>
|
||||
}
|
||||
|
||||
pub fn create_final_composite(render_pass: &Arc<RenderPass>, device: &Arc<Device>) -> Program {
|
||||
|
||||
let vs = composite::vs::load(device.clone()).unwrap();
|
||||
let fs = composite::fs::load(device.clone()).unwrap();
|
||||
|
||||
// Before we draw we have to create what is called a pipeline. This is similar to an OpenGL
|
||||
// program, but much more specific.
|
||||
GraphicsPipeline::start()
|
||||
let pipeline = GraphicsPipeline::start()
|
||||
// We have to indicate which subpass of which render pass this pipeline is going to be used
|
||||
// in. The pipeline will only be usable from this particular subpass.
|
||||
.render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
|
||||
// We need to indicate the layout of the vertices.
|
||||
.vertex_input_state(Vertex2d::per_vertex())
|
||||
.vertex_input_state(crate::vulkan::textured_quad::TexturedVertex::per_vertex())
|
||||
// The content of the vertex buffer describes a list of triangles.
|
||||
.input_assembly_state(InputAssemblyState::new())
|
||||
// A Vulkan shader can in theory contain multiple entry points, so we have to specify
|
||||
|
@ -33,5 +40,11 @@ pub fn create_program(render_pass: &Arc<RenderPass>, device: &Arc<Device>) -> Ar
|
|||
.fragment_shader(fs.entry_point("main").unwrap(), ())
|
||||
// Now that our builder is filled, we call `build()` to obtain an actual pipeline.
|
||||
.build(device.clone())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
return Program {
|
||||
pipeline,
|
||||
vertex: vs,
|
||||
fragement: fs,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
use cgmath::prelude::*;
|
||||
use cgmath::{Rad, Vector2, Vector3};
|
||||
|
||||
pub struct Camera {
|
||||
front: Vector3<f32>,
|
||||
left: Vector3<f32>,
|
||||
up: Vector3<f32>,
|
||||
pos: Vector3<f32>,
|
||||
fov: f32,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
front: Vector3::new(0.0, 0.0, 1.0),
|
||||
left: Vector3::new(1.0, 0.0, 0.0),
|
||||
up: Vector3::new(0.0, 1.0, 0.0),
|
||||
pos: Vector3::new(0.0, 0.0, -4.0),
|
||||
fov: 90.0f32
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate(&mut self, rot_y: f32, rot_x: f32) {
|
||||
let rot_mat_y = cgmath::Matrix4::from_angle_y(Rad(rot_y));
|
||||
let rot_mat_x = cgmath::Matrix4::from_angle_x(Rad(rot_x));
|
||||
|
||||
let mat = rot_mat_x * rot_mat_y;
|
||||
|
||||
self.front = mat.transform_vector(self.front);
|
||||
self.left = mat.transform_vector(self.left);
|
||||
self.up = mat.transform_vector(self.up);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
mod camera;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
|
||||
use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo};
|
||||
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
|
||||
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract};
|
||||
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
|
||||
use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet};
|
||||
use vulkano::device::{Queue};
|
||||
use vulkano::format::Format;
|
||||
use vulkano::image::{ImageAccess, ImageUsage, StorageImage};
|
||||
use vulkano::image::view::ImageView;
|
||||
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator};
|
||||
use vulkano::padded::Padded;
|
||||
use vulkano::pipeline::{ComputePipeline, Pipeline, PipelineBindPoint};
|
||||
use vulkano::sync::GpuFuture;
|
||||
use crate::vulkan::Renderer;
|
||||
|
||||
pub(crate) mod cs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "compute",
|
||||
include: ["src/shader/pathtracing/"],
|
||||
path: "src/shader/pathtracing/pathtracer.comp"
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PathtracerPipeline {
|
||||
compute_queue: Arc<Queue>,
|
||||
compute_pipeline: Arc<ComputePipeline>,
|
||||
command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
|
||||
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
|
||||
memory_allocator: Arc<StandardMemoryAllocator>,
|
||||
image: Arc<ImageView<StorageImage>>,
|
||||
raw_image: Subbuffer<[[f32; 4]]>,
|
||||
seconds: Instant,
|
||||
uniform_buffer: Arc<SubbufferAllocator>,
|
||||
vertex_buffer: Subbuffer<[[f32; 3]]>,
|
||||
index_buffer: Subbuffer<[u32]>,
|
||||
}
|
||||
|
||||
impl PathtracerPipeline {
|
||||
pub fn new(renderer: &Renderer, compute_queue: &Arc<Queue>, size: [u32; 2]) -> Self {
|
||||
let compute_pipeline = {
|
||||
let shader = cs::load(compute_queue.device().clone()).unwrap();
|
||||
|
||||
ComputePipeline::new(
|
||||
compute_queue.device().clone(),
|
||||
shader.entry_point("main").unwrap(),
|
||||
&(),
|
||||
None,
|
||||
|_| {},
|
||||
).unwrap()
|
||||
};
|
||||
|
||||
let (raw_image_buffer, image) = create_image(&renderer.memory_allocator, compute_queue, size);
|
||||
|
||||
let uniform_buffer = SubbufferAllocator::new(
|
||||
renderer.memory_allocator.clone(),
|
||||
SubbufferAllocatorCreateInfo {
|
||||
buffer_usage: BufferUsage::UNIFORM_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let (vertices, indices) = load_example_scene();
|
||||
|
||||
let (vertex_buffer, index_buffer) = create_gpu_buffer(&vertices, &indices, &renderer.memory_allocator);
|
||||
|
||||
return PathtracerPipeline {
|
||||
compute_queue: compute_queue.clone(),
|
||||
compute_pipeline,
|
||||
command_buffer_allocator: renderer.command_buffer_allocator.clone(),
|
||||
descriptor_set_allocator: renderer.descriptor_set_allocator.clone(),
|
||||
memory_allocator: renderer.memory_allocator.clone(),
|
||||
image,
|
||||
raw_image: raw_image_buffer,
|
||||
uniform_buffer: Arc::new(uniform_buffer),
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
seconds: Instant::now()
|
||||
};
|
||||
}
|
||||
|
||||
pub fn compute(&mut self) -> Box<dyn GpuFuture> {
|
||||
let mut builder = AutoCommandBufferBuilder::primary(
|
||||
&self.command_buffer_allocator,
|
||||
self.compute_queue.queue_family_index(),
|
||||
CommandBufferUsage::OneTimeSubmit,
|
||||
).unwrap();
|
||||
|
||||
// Dispatch will mutate the builder adding commands which won't be sent before we build the
|
||||
// command buffer after dispatches. This will minimize the commands we send to the GPU. For
|
||||
// example, we could be doing tens of dispatches here depending on our needs. Maybe we
|
||||
// wanted to simulate 10 steps at a time...
|
||||
|
||||
// First compute the next state.
|
||||
self.dispatch(&mut builder);
|
||||
|
||||
let command_buffer = builder.build().unwrap();
|
||||
let finished = command_buffer.execute(self.compute_queue.clone()).unwrap();
|
||||
let after_pipeline = finished.then_signal_fence_and_flush().unwrap().boxed();
|
||||
|
||||
after_pipeline
|
||||
}
|
||||
|
||||
pub fn resize_image(&mut self, size: [u32; 2]) {
|
||||
let (raw_image_buffer, image) = create_image(&self.memory_allocator, &self.compute_queue, size);
|
||||
|
||||
self.image = image;
|
||||
self.raw_image = raw_image_buffer;
|
||||
}
|
||||
|
||||
/// Builds the command for a dispatch.
|
||||
fn dispatch(
|
||||
&self,
|
||||
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer,Arc<StandardCommandBufferAllocator>>
|
||||
) {
|
||||
let camera = cs::Camera {
|
||||
front: Padded::from([0f32, 0f32, 1f32]),
|
||||
left: Padded::from([1f32, 0f32, 0f32]),
|
||||
up: Padded::from([0f32, 1f32, 0f32]),
|
||||
|
||||
position: [0f32, 0f32, -12f32],
|
||||
|
||||
fov: 90f32
|
||||
};
|
||||
|
||||
let subbuffer = self.uniform_buffer.allocate_sized().unwrap();
|
||||
*subbuffer.write().unwrap() = camera;
|
||||
|
||||
let size = self.image.image().dimensions().width_height();
|
||||
let pipeline_layout = self.compute_pipeline.layout();
|
||||
let desc_layout = pipeline_layout.set_layouts().get(0).unwrap();
|
||||
let set = PersistentDescriptorSet::new(
|
||||
&self.descriptor_set_allocator,
|
||||
desc_layout.clone(),
|
||||
[
|
||||
WriteDescriptorSet::buffer(0, self.raw_image.clone()),
|
||||
WriteDescriptorSet::image_view(1, self.image.clone()),
|
||||
WriteDescriptorSet::buffer(2, subbuffer),
|
||||
WriteDescriptorSet::buffer(3, self.vertex_buffer.clone()),
|
||||
WriteDescriptorSet::buffer(4, self.index_buffer.clone())
|
||||
],
|
||||
).unwrap();
|
||||
|
||||
let push_constants = cs::PushConstants {
|
||||
resolution: [size[0] as f32, size[1] as f32],
|
||||
seconds: (Instant::now() - self.seconds).as_secs_f32()
|
||||
};
|
||||
|
||||
builder
|
||||
.bind_pipeline_compute(self.compute_pipeline.clone())
|
||||
.bind_descriptor_sets(PipelineBindPoint::Compute, pipeline_layout.clone(), 0, set)
|
||||
.push_constants(pipeline_layout.clone(), 0, push_constants)
|
||||
.dispatch([size[0] / 8, size[1] / 8, 1])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn image(&self) -> &Arc<ImageView<StorageImage>> {
|
||||
&self.image
|
||||
}
|
||||
}
|
||||
|
||||
fn create_image(memory_allocator: &StandardMemoryAllocator, queue: &Arc<Queue>, size: [u32; 2]) -> (Subbuffer<[[f32; 4]]>, Arc<ImageView<StorageImage>>) {
|
||||
let raw_image = Buffer::from_iter(
|
||||
memory_allocator,
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::STORAGE_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
usage: MemoryUsage::Upload,
|
||||
..Default::default()
|
||||
},
|
||||
vec![[0f32; 4]; (size[0] * size[1]) as usize],
|
||||
).unwrap();
|
||||
|
||||
let image = StorageImage::general_purpose_image_view(
|
||||
memory_allocator,
|
||||
queue.clone(),
|
||||
size,
|
||||
Format::R8G8B8A8_UNORM,
|
||||
ImageUsage::SAMPLED | ImageUsage::STORAGE | ImageUsage::TRANSFER_DST,
|
||||
).unwrap();
|
||||
|
||||
(raw_image, image)
|
||||
}
|
||||
|
||||
fn load_example_scene() -> (Vec<[f32; 3]>, Vec<u32>) {
|
||||
let (mut models, materials) = tobj::load_obj("res/example-scene.obj", &tobj::GPU_LOAD_OPTIONS).expect("unable to load scene from obj");
|
||||
|
||||
let mut vertices:Vec<[f32; 3]> = Vec::new();
|
||||
let mut indices:Vec<u32> = Vec::new();
|
||||
|
||||
for model in models.iter_mut() {
|
||||
let num_triangles = model.mesh.positions.len() / 3;
|
||||
|
||||
for triangle_index in 0..num_triangles {
|
||||
let vertex_index = triangle_index * 3;
|
||||
vertices.push([
|
||||
model.mesh.positions[vertex_index],
|
||||
model.mesh.positions[vertex_index + 1],
|
||||
model.mesh.positions[vertex_index + 2]
|
||||
]);
|
||||
}
|
||||
|
||||
for index in model.mesh.indices.iter() {
|
||||
indices.push(*index);
|
||||
}
|
||||
|
||||
// model.mesh.indices.iter_mut().for_each(|i| indices.push(*i));
|
||||
}
|
||||
|
||||
(vertices, indices)
|
||||
}
|
||||
|
||||
fn create_gpu_buffer(vertices: &Vec<[f32; 3]>, indices: &Vec<u32>, memory_allocator: &StandardMemoryAllocator) -> (Subbuffer<[[f32; 3]]>, Subbuffer<[u32]>) {
|
||||
let vertex_buffer = Buffer::from_iter(
|
||||
memory_allocator,
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::STORAGE_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
usage: MemoryUsage::Upload,
|
||||
..Default::default()
|
||||
},
|
||||
vertices.clone(),
|
||||
).unwrap();
|
||||
|
||||
let index_buffer = Buffer::from_iter(
|
||||
memory_allocator,
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::STORAGE_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
usage: MemoryUsage::Upload,
|
||||
..Default::default()
|
||||
},
|
||||
indices.clone(),
|
||||
).unwrap();
|
||||
|
||||
(vertex_buffer, index_buffer)
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
#version 450
|
||||
|
||||
#include "rand/random.glsl"
|
||||
#include "raytracing/raytracing.glsl"
|
||||
#include "raytracing/camera.glsl"
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
||||
|
||||
layout(set = 0, binding = 0) buffer LinearImage { vec4 pixels[]; };
|
||||
|
||||
layout(set = 0, binding = 1, rgba8) uniform writeonly image2D image;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec2 resolution;
|
||||
float seconds;
|
||||
} program_metadata;
|
||||
|
||||
vec2 get_viewport_coordinate() {
|
||||
vec2 texture_uv = (vec2(gl_GlobalInvocationID.xy + vec2(random(), random())) - 0.5 * program_metadata.resolution) / program_metadata.resolution.y;
|
||||
|
||||
return texture_uv;
|
||||
}
|
||||
|
||||
uint get_pixel_index() {
|
||||
return gl_GlobalInvocationID.y * uint(program_metadata.resolution.x) + gl_GlobalInvocationID.x;
|
||||
}
|
||||
|
||||
void main() {
|
||||
init_random_state(floatBitsToInt(program_metadata.seconds));
|
||||
|
||||
vec2 uv = get_viewport_coordinate();
|
||||
|
||||
Ray camera_ray = construct_camera_ray_pinhole(uv);
|
||||
|
||||
vec3 color = vec3(0);
|
||||
|
||||
vec4 result = intersect_scene(camera_ray);
|
||||
|
||||
if (result.a == 1.0) {
|
||||
color = vec3(result.y, result.z, 0);
|
||||
}
|
||||
|
||||
// index of the current pixel as array index
|
||||
uint pixel_index = get_pixel_index();
|
||||
|
||||
vec4 buffer_data = pixels[pixel_index] + vec4(color, 1);
|
||||
// contribute to the raw pixel buffer
|
||||
pixels[pixel_index] = buffer_data;
|
||||
|
||||
imageStore(image, ivec2(gl_GlobalInvocationID.xy), vec4(buffer_data.rgb / buffer_data.a, 1));
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
|
||||
uint state;
|
||||
|
||||
void init_random_state(uint seed) {
|
||||
state = (gl_GlobalInvocationID.x << 16) | gl_GlobalInvocationID.y;
|
||||
state += seed;
|
||||
}
|
||||
|
||||
// A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm.
|
||||
uint hash(in uint x) {
|
||||
x += ( x << 10u );
|
||||
x ^= ( x >> 6u );
|
||||
x += ( x << 3u );
|
||||
x ^= ( x >> 11u );
|
||||
x += ( x << 15u );
|
||||
return x;
|
||||
}
|
||||
|
||||
// Construct a float with half-open range [0:1] using low 23 bits.
|
||||
// All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0.
|
||||
float floatConstruct( uint m ) {
|
||||
const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask
|
||||
const uint ieeeOne = 0x3F800000u; // 1.0 in IEEE binary32
|
||||
|
||||
m &= ieeeMantissa; // Keep only mantissa bits (fractional part)
|
||||
m |= ieeeOne; // Add fractional part to 1.0
|
||||
|
||||
float f = uintBitsToFloat(m); // Range [1:2]
|
||||
return f - 1.0; // Range [0:1]
|
||||
}
|
||||
|
||||
float random() {
|
||||
state = hash(state);
|
||||
|
||||
return floatConstruct(state);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
layout(set = 0, binding = 2) uniform Camera {
|
||||
vec3 front;
|
||||
vec3 left;
|
||||
vec3 up;
|
||||
|
||||
vec3 position;
|
||||
|
||||
// camera fov in degrees
|
||||
float fov;
|
||||
} camera;
|
||||
|
||||
Ray construct_camera_ray_pinhole(in vec2 uv) {
|
||||
Ray camera_ray;
|
||||
|
||||
float fov_fac = tan(camera.fov / 180.0 * 3.1412535 * 0.5);
|
||||
|
||||
camera_ray.origin = camera.position;
|
||||
camera_ray.direction = normalize(camera.front / fov_fac + camera.left * uv.x + camera.up * uv.y);
|
||||
|
||||
camera_ray.near = 1e-3;
|
||||
camera_ray.far = 1e3;
|
||||
|
||||
return camera_ray;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
struct Ray {
|
||||
vec3 direction;
|
||||
vec3 origin;
|
||||
|
||||
float near;
|
||||
float far;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 3) buffer VertexBuffer {
|
||||
vec3 vertices[];
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 4) buffer IndexBuffer {
|
||||
uint indices[];
|
||||
};
|
||||
|
||||
vec3 intersect_triangle(in Ray ray, in vec3 v0, in vec3 v1, in vec3 v2) {
|
||||
vec3 v1v0 = v1 - v0;
|
||||
vec3 v2v0 = v2 - v0;
|
||||
vec3 rov0 = ray.origin - v0;
|
||||
vec3 n = cross(v1v0, v2v0);
|
||||
vec3 q = cross(rov0, ray.direction);
|
||||
float d = 1.0 / dot(ray.direction, n);
|
||||
float u = d * dot( -q, v2v0);
|
||||
float v = d * dot( q, v1v0);
|
||||
float t = d * dot( -n, rov0);
|
||||
|
||||
t = min(u, min(v, min(1.0 - (u + v), t)));
|
||||
|
||||
return vec3( t, u, v );
|
||||
}
|
||||
|
||||
vec4 intersect_scene(in Ray ray) {
|
||||
vec4 hit = vec4(ray.far, -1, -1, 0);
|
||||
|
||||
for (int i = 0; i < indices.length(); i += 3) {
|
||||
vec3 v0 = vertices[indices[i]];
|
||||
vec3 v1 = vertices[indices[i + 1]];
|
||||
vec3 v2 = vertices[indices[i + 2]];
|
||||
|
||||
vec3 result = intersect_triangle(ray, v0, v1, v2);
|
||||
|
||||
if (result.x > ray.near && result.x < hit.x) {
|
||||
hit = vec4(result, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
layout(push_constant) uniform Camera {
|
||||
vec3 position;
|
||||
vec3 front;
|
||||
vec3 up;
|
||||
vec3 left;
|
||||
float fov;
|
||||
} camera;
|
||||
|
||||
vec3 generate_view_ray_direction(in vec2 uv) {
|
||||
// convert camera fov from degrees to relative factor for scaling normalized front vector
|
||||
float fov = 1.0 / tan(DegreeToRadians(camera.fov) * 0.5);
|
||||
|
||||
return normalize(camera.front * fov + camera.up * uv.x + camera.left * uv.y);
|
||||
}
|
||||
|
||||
Ray generate_view_ray(in vec2 uv) {
|
||||
Ray view_ray;
|
||||
view_ray.origin = camera.position;
|
||||
view_ray.direction = generate_view_ray_direction(uv);
|
||||
return view_ray;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
#define DegreeToRadians(x) (x * 0.01745329251994330)
|
||||
|
||||
struct Ray {
|
||||
vec3 origin;
|
||||
vec3 direction;
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
#include <pathtracing/common.glsl>
|
||||
#include <pathtracing/camera.glsl>
|
||||
|
||||
vec3 trace_path(in vec2 uv) {
|
||||
Ray view_ray = generate_view_ray(uv);
|
||||
|
||||
return view_ray.direction;
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
use std::sync::Arc;
|
||||
use vulkano::device::{Device, DeviceCreateInfo, Queue, QueueCreateInfo, QueueFlags, Properties, DeviceExtensions};
|
||||
use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType};
|
||||
use vulkano::instance::Instance;
|
||||
use vulkano::swapchain::Surface;
|
||||
|
||||
/// Choose the device to render on.
|
||||
/// This function will favor certain devices over others in the following descending order:
|
||||
/// * discrete GPU
|
||||
/// * integrated GPU
|
||||
/// * virtual GPU
|
||||
/// * CPU
|
||||
/// * others...
|
||||
pub(crate) fn get_device(instance: &Arc<Instance>, surface: &Arc<Surface>) -> (Arc<Device>, impl ExactSizeIterator<Item=Arc<Queue>> + Sized) {
|
||||
// Choose device extensions that we're going to use. In order to present images to a surface,
|
||||
// we need a `Swapchain`, which is provided by the `khr_swapchain` extension.
|
||||
let device_extensions = DeviceExtensions {
|
||||
khr_swapchain: true,
|
||||
..DeviceExtensions::empty()
|
||||
};
|
||||
|
||||
let (physical_device, queue_family_index) = choose_physical_device(&instance, &surface, device_extensions);
|
||||
|
||||
print_physical_device_info(physical_device.properties());
|
||||
|
||||
// Now initializing the device. This is probably the most important object of Vulkan.
|
||||
//
|
||||
// An iterator of created queues is returned by the function alongside the device.
|
||||
Device::new(
|
||||
// Which physical device to connect to.
|
||||
physical_device,
|
||||
DeviceCreateInfo {
|
||||
// A list of optional features and extensions that our program needs to work correctly.
|
||||
// Some parts of the Vulkan specs are optional and must be enabled manually at device
|
||||
// creation. In this example the only thing we are going to need is the `khr_swapchain`
|
||||
// extension that allows us to draw to a window.
|
||||
enabled_extensions: device_extensions,
|
||||
|
||||
// The list of queues that we are going to use. Here we only use one queue, from the
|
||||
// previously chosen queue family.
|
||||
queue_create_infos: vec![QueueCreateInfo {
|
||||
queue_family_index,
|
||||
..Default::default()
|
||||
}],
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
).unwrap()
|
||||
}
|
||||
|
||||
/// Choose the actual physical device to render on.
|
||||
/// This function will favor certain devices over others in the following descending order:
|
||||
/// * discrete GPU
|
||||
/// * integrated GPU
|
||||
/// * virtual GPU
|
||||
/// * CPU
|
||||
/// * others...
|
||||
fn choose_physical_device(instance: &Arc<Instance>, surface: &Arc<Surface>, device_extensions: DeviceExtensions) -> (Arc<PhysicalDevice>, u32) {
|
||||
// We then choose which physical device to use. First, we enumerate all the available physical
|
||||
// devices, then apply filters to narrow them down to those that can support our needs.
|
||||
instance
|
||||
.enumerate_physical_devices()
|
||||
.unwrap()
|
||||
.filter(|p| {
|
||||
// Some devices may not support the extensions or features that your application, or
|
||||
// report properties and limits that are not sufficient for your application. These
|
||||
// should be filtered out here.
|
||||
p.supported_extensions().contains(&device_extensions)
|
||||
})
|
||||
.filter_map(|p| {
|
||||
// For each physical device, we try to find a suitable queue family that will execute
|
||||
// our draw commands.
|
||||
//
|
||||
// Devices can provide multiple queues to run commands in parallel (for example a draw
|
||||
// queue and a compute queue), similar to CPU threads. This is something you have to
|
||||
// have to manage manually in Vulkan. Queues of the same type belong to the same queue
|
||||
// family.
|
||||
//
|
||||
// Here, we look for a single queue family that is suitable for our purposes. In a
|
||||
// real-world application, you may want to use a separate dedicated transfer queue to
|
||||
// handle data transfers in parallel with graphics operations. You may also need a
|
||||
// separate queue for compute operations, if your application uses those.
|
||||
p.queue_family_properties()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.position(|(i, q)| {
|
||||
// We select a queue family that supports graphics operations. When drawing to
|
||||
// a window surface, as we do in this example, we also need to check that
|
||||
// queues in this queue family are capable of presenting images to the surface.
|
||||
q.queue_flags.intersects(QueueFlags::GRAPHICS)
|
||||
&& p.surface_support(i as u32, &surface).unwrap_or(false)
|
||||
})
|
||||
// The code here searches for the first queue family that is suitable. If none is
|
||||
// found, `None` is returned to `filter_map`, which disqualifies this physical
|
||||
// device.
|
||||
.map(|i| (p, i as u32))
|
||||
})
|
||||
// All the physical devices that pass the filters above are suitable for the application.
|
||||
// However, not every device is equal, some are preferred over others. Now, we assign each
|
||||
// physical device a score, and pick the device with the lowest ("best") score.
|
||||
//
|
||||
// In this example, we simply select the best-scoring device to use in the application.
|
||||
// In a real-world setting, you may want to use the best-scoring device only as a "default"
|
||||
// or "recommended" device, and let the user choose the device themself.
|
||||
.min_by_key(|(p, _)| {
|
||||
// We assign a lower score to device types that are likely to be faster/better.
|
||||
match p.properties().device_type {
|
||||
PhysicalDeviceType::DiscreteGpu => 0,
|
||||
PhysicalDeviceType::IntegratedGpu => 1,
|
||||
PhysicalDeviceType::VirtualGpu => 2,
|
||||
PhysicalDeviceType::Cpu => 3,
|
||||
PhysicalDeviceType::Other => 4,
|
||||
_ => 5,
|
||||
}
|
||||
})
|
||||
.expect("no suitable physical device found")
|
||||
}
|
||||
|
||||
fn print_physical_device_info(device_properties: &Properties) {
|
||||
println!("name: {}", device_properties.device_name);
|
||||
|
||||
print_device_driver_info(device_properties);
|
||||
}
|
||||
|
||||
fn print_device_driver_info(device_properties: &Properties) {
|
||||
let default_name = String::from("<none>");
|
||||
let name = device_properties.driver_name.as_ref().unwrap_or(&default_name);
|
||||
|
||||
let default_info = String::from("<none>");
|
||||
let info = device_properties.driver_info.as_ref().unwrap_or(&default_info);
|
||||
|
||||
println!("driver:\n\tname: {}\n\tversion: {}\n\tinfo: {}\n", name, device_properties.driver_version, info);
|
||||
}
|
|
@ -1,62 +1,30 @@
|
|||
mod device;
|
||||
pub(crate) mod textured_quad;
|
||||
|
||||
use std::sync::Arc;
|
||||
use vulkano::device::{Device, DeviceCreateInfo, DeviceExtensions, Properties, Queue, QueueCreateInfo, QueueFlags};
|
||||
use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType};
|
||||
use vulkano::device::{Device};
|
||||
use vulkano::image::{ImageAccess, ImageUsage, SwapchainImage};
|
||||
use vulkano::instance::{Instance, InstanceCreateInfo};
|
||||
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator};
|
||||
use vulkano::memory::allocator::{StandardMemoryAllocator};
|
||||
use vulkano::swapchain::{acquire_next_image, AcquireError, Surface, Swapchain, SwapchainCreateInfo, SwapchainCreationError, SwapchainPresentInfo};
|
||||
use vulkano::{sync, VulkanLibrary};
|
||||
use vulkano_win::VkSurfaceBuild;
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::{Window, WindowBuilder};
|
||||
use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer};
|
||||
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
|
||||
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassContents};
|
||||
use vulkano::descriptor_set::allocator::{StandardDescriptorSetAllocator};
|
||||
use vulkano::image::view::ImageView;
|
||||
use vulkano::pipeline::graphics::vertex_input::Vertex;
|
||||
use vulkano::pipeline::graphics::viewport::Viewport;
|
||||
use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass};
|
||||
use vulkano::sync::{FlushError, GpuFuture};
|
||||
use winit::event::{Event, WindowEvent};
|
||||
use crate::shader::composite::TextureDrawPipeline;
|
||||
use crate::shader::pathtracing::PathtracerPipeline;
|
||||
|
||||
// We now create a buffer that will store the shape of our triangle. We use `#[repr(C)]` here
|
||||
// to force rustc to use a defined layout for our data, as the default representation has *no
|
||||
// guarantees*.
|
||||
#[derive(BufferContents, Vertex)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Vertex2d {
|
||||
#[format(R32G32_SFLOAT)]
|
||||
position: [f32; 2],
|
||||
#[format(R32G32_SFLOAT)]
|
||||
texture: [f32; 2]
|
||||
}
|
||||
|
||||
fn textured_quad() -> (Vec<Vertex2d>, Vec<u32>) {
|
||||
(
|
||||
vec![
|
||||
Vertex2d {
|
||||
position: [-1.0, -1.0],
|
||||
texture: [0.0, 0.0]
|
||||
},
|
||||
Vertex2d {
|
||||
position: [1.0, -1.0],
|
||||
texture: [1.0, 0.0]
|
||||
},
|
||||
Vertex2d {
|
||||
position: [1.0, 1.0],
|
||||
texture: [1.0, 1.0]
|
||||
},
|
||||
Vertex2d {
|
||||
position: [-1.0, 1.0],
|
||||
texture: [0.0, 1.0]
|
||||
}
|
||||
],
|
||||
vec![
|
||||
0, 1, 2,
|
||||
0, 2, 3
|
||||
]
|
||||
)
|
||||
pub struct Renderer {
|
||||
pub(crate) memory_allocator: Arc<StandardMemoryAllocator>,
|
||||
pub(crate) command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
|
||||
pub(crate) descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
|
@ -91,7 +59,7 @@ pub fn init() {
|
|||
.build_vk_surface(&event_loop, instance.clone())
|
||||
.unwrap();
|
||||
|
||||
let (device, mut queues) = get_device(&instance, &surface);
|
||||
let (device, mut queues) = device::get_device(&instance, &surface);
|
||||
|
||||
// Since we can request multiple queues, the `queues` variable is in fact an iterator. We only
|
||||
// use one queue in this example, so we just retrieve the first and only element of the
|
||||
|
@ -100,9 +68,7 @@ pub fn init() {
|
|||
|
||||
let (mut swapchain, images) = create_swapchain(&device, &surface);
|
||||
|
||||
let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
|
||||
|
||||
let (vertex_buffer, index_buffer) = create_quad_buffer(&memory_allocator);
|
||||
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));
|
||||
|
||||
// At this point, OpenGL initialization would be finished. However in Vulkan it is not. OpenGL
|
||||
// implicitly does a lot of computation whenever you draw. In Vulkan, you have to do all this
|
||||
|
@ -129,7 +95,17 @@ pub fn init() {
|
|||
// them. Vulkano provides a command buffer allocator, which manages raw Vulkan command pools
|
||||
// underneath and provides a safe interface for them.
|
||||
let command_buffer_allocator =
|
||||
StandardCommandBufferAllocator::new(device.clone(), Default::default());
|
||||
Arc::new(StandardCommandBufferAllocator::new(device.clone(), Default::default()));
|
||||
|
||||
let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
|
||||
device.clone(),
|
||||
));
|
||||
|
||||
let renderer = Renderer {
|
||||
memory_allocator,
|
||||
command_buffer_allocator,
|
||||
descriptor_set_allocator
|
||||
};
|
||||
|
||||
// Initialization is finally finished!
|
||||
|
||||
|
@ -152,7 +128,10 @@ pub fn init() {
|
|||
// that, we store the submission of the previous frame here.
|
||||
let mut previous_frame_end = Some(sync::now(device.clone()).boxed());
|
||||
|
||||
let pipeline = crate::shader::create_program(&render_pass, &device);
|
||||
let texture_drawer = TextureDrawPipeline::new(&renderer, &queue, &render_pass);
|
||||
let mut pathtracer = PathtracerPipeline::new(&renderer, &queue, [512, 512]);
|
||||
|
||||
let mut now_keys = [false; 255];
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
match event {
|
||||
|
@ -167,7 +146,31 @@ pub fn init() {
|
|||
..
|
||||
} => {
|
||||
recreate_swapchain = true;
|
||||
}
|
||||
},
|
||||
Event::WindowEvent {
|
||||
// Note this deeply nested pattern match
|
||||
event: WindowEvent::KeyboardInput {
|
||||
input:winit::event::KeyboardInput {
|
||||
// Which serves to filter out only events we actually want
|
||||
virtual_keycode:Some(keycode),
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
// It also binds these handy variable names!
|
||||
match state {
|
||||
winit::event::ElementState::Pressed => {
|
||||
// VirtualKeycode is an enum with a defined representation
|
||||
now_keys[keycode as usize] = true;
|
||||
},
|
||||
winit::event::ElementState::Released => {
|
||||
now_keys[keycode as usize] = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
Event::RedrawEventsCleared => {
|
||||
// Do not draw the frame when the screen dimensions are zero. On Windows, this can
|
||||
// occur when minimizing the application.
|
||||
|
@ -240,59 +243,9 @@ pub fn init() {
|
|||
recreate_swapchain = true;
|
||||
}
|
||||
|
||||
// In order to draw, we have to build a *command buffer*. The command buffer object
|
||||
// holds the list of commands that are going to be executed.
|
||||
//
|
||||
// Building a command buffer is an expensive operation (usually a few hundred
|
||||
// microseconds), but it is known to be a hot path in the driver and is expected to
|
||||
// be optimized.
|
||||
//
|
||||
// Note that we have to pass a queue family when we create the command buffer. The
|
||||
// command buffer will only be executable on that given queue family.
|
||||
let mut builder = AutoCommandBufferBuilder::primary(
|
||||
&command_buffer_allocator,
|
||||
queue.queue_family_index(),
|
||||
CommandBufferUsage::OneTimeSubmit,
|
||||
)
|
||||
.unwrap();
|
||||
pathtracer.compute();
|
||||
|
||||
builder
|
||||
// Before we can draw, we have to *enter a render pass*.
|
||||
.begin_render_pass(
|
||||
RenderPassBeginInfo {
|
||||
// A list of values to clear the attachments with. This list contains
|
||||
// one item for each attachment in the render pass. In this case, there
|
||||
// is only one attachment, and we clear it with a blue color.
|
||||
//
|
||||
// Only attachments that have `LoadOp::Clear` are provided with clear
|
||||
// values, any others should use `ClearValue::None` as the clear value.
|
||||
clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())],
|
||||
|
||||
..RenderPassBeginInfo::framebuffer(
|
||||
framebuffers[image_index as usize].clone(),
|
||||
)
|
||||
},
|
||||
// The contents of the first (and only) subpass. This can be either
|
||||
// `Inline` or `SecondaryCommandBuffers`. The latter is a bit more advanced
|
||||
// and is not covered here.
|
||||
SubpassContents::Inline,
|
||||
)
|
||||
.unwrap()
|
||||
// We are now inside the first subpass of the render pass.
|
||||
.set_viewport(0, [viewport.clone()])
|
||||
.bind_pipeline_graphics(pipeline.clone())
|
||||
.bind_vertex_buffers(0, vertex_buffer.clone())
|
||||
.bind_index_buffer(index_buffer.clone())
|
||||
// We add a draw command.
|
||||
.draw_indexed(index_buffer.len() as u32, 1, 0, 0, 0)
|
||||
.unwrap()
|
||||
// We leave the render pass. Note that if we had multiple subpasses we could
|
||||
// have called `next_subpass` to jump to the next subpass.
|
||||
.end_render_pass()
|
||||
.unwrap();
|
||||
|
||||
// Finish building the command buffer by calling `build`.
|
||||
let command_buffer = builder.build().unwrap();
|
||||
let command_buffer = texture_drawer.draw(framebuffers[image_index as usize].clone(), &viewport, &pathtracer);
|
||||
|
||||
let future = previous_frame_end
|
||||
.take()
|
||||
|
@ -333,38 +286,6 @@ pub fn init() {
|
|||
});
|
||||
}
|
||||
|
||||
fn create_quad_buffer(memory_allocator: &StandardMemoryAllocator) -> (Subbuffer<[Vertex2d]>, Subbuffer<[u32]>) {
|
||||
let (vertices, indices) = textured_quad();
|
||||
|
||||
let vertex_buffer = Buffer::from_iter(
|
||||
memory_allocator,
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::VERTEX_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
usage: MemoryUsage::Upload,
|
||||
..Default::default()
|
||||
},
|
||||
vertices,
|
||||
).unwrap();
|
||||
|
||||
let index_buffer = Buffer::from_iter(
|
||||
memory_allocator,
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::INDEX_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
usage: MemoryUsage::Upload,
|
||||
..Default::default()
|
||||
},
|
||||
indices,
|
||||
).unwrap();
|
||||
|
||||
(vertex_buffer, index_buffer)
|
||||
}
|
||||
|
||||
/// This function is called once during initialization, then again whenever the window is resized.
|
||||
fn window_size_dependent_setup(
|
||||
images: &[Arc<SwapchainImage>],
|
||||
|
@ -386,45 +307,7 @@ fn window_size_dependent_setup(
|
|||
},
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn get_device(instance: &Arc<Instance>, surface: &Arc<Surface>) -> (Arc<Device>, impl ExactSizeIterator<Item=Arc<Queue>> + Sized) {
|
||||
// Choose device extensions that we're going to use. In order to present images to a surface,
|
||||
// we need a `Swapchain`, which is provided by the `khr_swapchain` extension.
|
||||
let device_extensions = DeviceExtensions {
|
||||
khr_swapchain: true,
|
||||
..DeviceExtensions::empty()
|
||||
};
|
||||
|
||||
let (physical_device, queue_family_index) = choose_physical_device(&instance, &surface, device_extensions);
|
||||
|
||||
print_physical_device_info(physical_device.properties());
|
||||
|
||||
// Now initializing the device. This is probably the most important object of Vulkan.
|
||||
//
|
||||
// An iterator of created queues is returned by the function alongside the device.
|
||||
Device::new(
|
||||
// Which physical device to connect to.
|
||||
physical_device,
|
||||
DeviceCreateInfo {
|
||||
// A list of optional features and extensions that our program needs to work correctly.
|
||||
// Some parts of the Vulkan specs are optional and must be enabled manually at device
|
||||
// creation. In this example the only thing we are going to need is the `khr_swapchain`
|
||||
// extension that allows us to draw to a window.
|
||||
enabled_extensions: device_extensions,
|
||||
|
||||
// The list of queues that we are going to use. Here we only use one queue, from the
|
||||
// previously chosen queue family.
|
||||
queue_create_infos: vec![QueueCreateInfo {
|
||||
queue_family_index,
|
||||
..Default::default()
|
||||
}],
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
).unwrap()
|
||||
}).collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn create_render_pass(device: &Arc<Device>, swapchain: &Arc<Swapchain>) -> Arc<RenderPass> {
|
||||
|
@ -463,67 +346,6 @@ fn create_render_pass(device: &Arc<Device>, swapchain: &Arc<Swapchain>) -> Arc<R
|
|||
).unwrap()
|
||||
}
|
||||
|
||||
fn choose_physical_device(instance: &Arc<Instance>, surface: &Arc<Surface>, device_extensions: DeviceExtensions) -> (Arc<PhysicalDevice>, u32) {
|
||||
// We then choose which physical device to use. First, we enumerate all the available physical
|
||||
// devices, then apply filters to narrow them down to those that can support our needs.
|
||||
instance
|
||||
.enumerate_physical_devices()
|
||||
.unwrap()
|
||||
.filter(|p| {
|
||||
// Some devices may not support the extensions or features that your application, or
|
||||
// report properties and limits that are not sufficient for your application. These
|
||||
// should be filtered out here.
|
||||
p.supported_extensions().contains(&device_extensions)
|
||||
})
|
||||
.filter_map(|p| {
|
||||
// For each physical device, we try to find a suitable queue family that will execute
|
||||
// our draw commands.
|
||||
//
|
||||
// Devices can provide multiple queues to run commands in parallel (for example a draw
|
||||
// queue and a compute queue), similar to CPU threads. This is something you have to
|
||||
// have to manage manually in Vulkan. Queues of the same type belong to the same queue
|
||||
// family.
|
||||
//
|
||||
// Here, we look for a single queue family that is suitable for our purposes. In a
|
||||
// real-world application, you may want to use a separate dedicated transfer queue to
|
||||
// handle data transfers in parallel with graphics operations. You may also need a
|
||||
// separate queue for compute operations, if your application uses those.
|
||||
p.queue_family_properties()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.position(|(i, q)| {
|
||||
// We select a queue family that supports graphics operations. When drawing to
|
||||
// a window surface, as we do in this example, we also need to check that
|
||||
// queues in this queue family are capable of presenting images to the surface.
|
||||
q.queue_flags.intersects(QueueFlags::GRAPHICS)
|
||||
&& p.surface_support(i as u32, &surface).unwrap_or(false)
|
||||
})
|
||||
// The code here searches for the first queue family that is suitable. If none is
|
||||
// found, `None` is returned to `filter_map`, which disqualifies this physical
|
||||
// device.
|
||||
.map(|i| (p, i as u32))
|
||||
})
|
||||
// All the physical devices that pass the filters above are suitable for the application.
|
||||
// However, not every device is equal, some are preferred over others. Now, we assign each
|
||||
// physical device a score, and pick the device with the lowest ("best") score.
|
||||
//
|
||||
// In this example, we simply select the best-scoring device to use in the application.
|
||||
// In a real-world setting, you may want to use the best-scoring device only as a "default"
|
||||
// or "recommended" device, and let the user choose the device themself.
|
||||
.min_by_key(|(p, _)| {
|
||||
// We assign a lower score to device types that are likely to be faster/better.
|
||||
match p.properties().device_type {
|
||||
PhysicalDeviceType::DiscreteGpu => 0,
|
||||
PhysicalDeviceType::IntegratedGpu => 1,
|
||||
PhysicalDeviceType::VirtualGpu => 2,
|
||||
PhysicalDeviceType::Cpu => 3,
|
||||
PhysicalDeviceType::Other => 4,
|
||||
_ => 5,
|
||||
}
|
||||
})
|
||||
.expect("no suitable physical device found")
|
||||
}
|
||||
|
||||
fn create_swapchain(device: &Arc<Device>, surface: &Arc<Surface>) -> (Arc<Swapchain>, Vec<Arc<SwapchainImage>>) {
|
||||
// Before we can draw on the surface, we have to create what is called a swapchain. Creating a
|
||||
// swapchain allocates the color buffers that will contain the image that will ultimately be
|
||||
|
@ -583,19 +405,3 @@ fn create_swapchain(device: &Arc<Device>, surface: &Arc<Surface>) -> (Arc<Swapch
|
|||
},
|
||||
).unwrap()
|
||||
}
|
||||
|
||||
fn print_physical_device_info(device_properties: &Properties) {
|
||||
println!("name: {}", device_properties.device_name);
|
||||
|
||||
print_device_driver_info(device_properties);
|
||||
}
|
||||
|
||||
fn print_device_driver_info(device_properties: &Properties) {
|
||||
let default_name = String::from("<none>");
|
||||
let name = device_properties.driver_name.as_ref().unwrap_or(&default_name);
|
||||
|
||||
let default_info = String::from("<none>");
|
||||
let info = device_properties.driver_info.as_ref().unwrap_or(&default_info);
|
||||
|
||||
println!("driver:\n\tname: {}\n\tversion: {}\n\tinfo: {}\n", name, device_properties.driver_version, info);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
|
||||
/// This crate will utilize a textured quad to draw a texture onto a surface
|
||||
|
||||
use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer};
|
||||
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator};
|
||||
use vulkano::pipeline::graphics::vertex_input::Vertex;
|
||||
|
||||
// We now create a buffer that will store the shape of our triangle. We use `#[repr(C)]` here
|
||||
// to force rustc to use a defined layout for our data, as the default representation has *no
|
||||
// guarantees*.
|
||||
#[derive(BufferContents, Vertex)]
|
||||
#[repr(C)]
|
||||
pub struct TexturedVertex {
|
||||
// vertex coordinates in object space
|
||||
// in this case also equals to view space
|
||||
#[format(R32G32_SFLOAT)]
|
||||
position: [f32; 2],
|
||||
// uv coordinates per vertex
|
||||
#[format(R32G32_SFLOAT)]
|
||||
texture: [f32; 2]
|
||||
}
|
||||
|
||||
fn textured_quad() -> (Vec<TexturedVertex>, Vec<u32>) {
|
||||
(
|
||||
// vertex list
|
||||
vec![
|
||||
TexturedVertex {
|
||||
position: [-1.0, -1.0],
|
||||
texture: [0.0, 0.0]
|
||||
},
|
||||
TexturedVertex {
|
||||
position: [1.0, -1.0],
|
||||
texture: [1.0, 0.0]
|
||||
},
|
||||
TexturedVertex {
|
||||
position: [1.0, 1.0],
|
||||
texture: [1.0, 1.0]
|
||||
},
|
||||
TexturedVertex {
|
||||
position: [-1.0, 1.0],
|
||||
texture: [0.0, 1.0]
|
||||
}
|
||||
],
|
||||
// indices list
|
||||
vec![
|
||||
0, 1, 2,
|
||||
0, 2, 3
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_quad_buffer(memory_allocator: &StandardMemoryAllocator) -> (Subbuffer<[TexturedVertex]>, Subbuffer<[u32]>) {
|
||||
let (vertices, indices) = textured_quad();
|
||||
|
||||
let vertex_buffer = Buffer::from_iter(
|
||||
memory_allocator,
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::VERTEX_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
usage: MemoryUsage::Upload,
|
||||
..Default::default()
|
||||
},
|
||||
vertices,
|
||||
).unwrap();
|
||||
|
||||
let index_buffer = Buffer::from_iter(
|
||||
memory_allocator,
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::INDEX_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
usage: MemoryUsage::Upload,
|
||||
..Default::default()
|
||||
},
|
||||
indices,
|
||||
).unwrap();
|
||||
|
||||
(vertex_buffer, index_buffer)
|
||||
}
|
Reference in New Issue