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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
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]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
|
@ -181,6 +190,16 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
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]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
version = "0.1.50"
|
version = "0.1.50"
|
||||||
|
@ -290,6 +309,8 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||||
name = "eruption"
|
name = "eruption"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cgmath",
|
||||||
|
"tobj",
|
||||||
"vulkano",
|
"vulkano",
|
||||||
"vulkano-shaders",
|
"vulkano-shaders",
|
||||||
"vulkano-win",
|
"vulkano-win",
|
||||||
|
@ -571,6 +592,15 @@ dependencies = [
|
||||||
"minimal-lexical",
|
"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]]
|
[[package]]
|
||||||
name = "num_enum"
|
name = "num_enum"
|
||||||
version = "0.5.11"
|
version = "0.5.11"
|
||||||
|
@ -979,6 +1009,15 @@ dependencies = [
|
||||||
"strict-num",
|
"strict-num",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tobj"
|
||||||
|
version = "3.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57381207291289bad19de63acd3fbf5948ff99b2868116c367b7224c37d55f90"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
|
|
@ -10,4 +10,8 @@ authors = ["Sven Vogel"]
|
||||||
vulkano = "0.33.0"
|
vulkano = "0.33.0"
|
||||||
vulkano-win = "0.33.0"
|
vulkano-win = "0.33.0"
|
||||||
vulkano-shaders = "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() {
|
fn main() {
|
||||||
vulkan::init();
|
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
|
#version 450
|
||||||
|
|
||||||
#include <pathtracing/pathtracer.glsl>
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 texture_coordinate;
|
layout(location = 0) in vec2 texture_coordinate;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2D image;
|
||||||
|
|
||||||
layout(location = 0) out vec4 frag_color;
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
||||||
vec2 uv = texture_coordinate;
|
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);
|
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 std::sync::Arc;
|
||||||
use vulkano::device::Device;
|
use vulkano::device::Device;
|
||||||
|
@ -7,21 +8,27 @@ use vulkano::pipeline::graphics::vertex_input::Vertex;
|
||||||
use vulkano::pipeline::graphics::viewport::ViewportState;
|
use vulkano::pipeline::graphics::viewport::ViewportState;
|
||||||
use vulkano::pipeline::GraphicsPipeline;
|
use vulkano::pipeline::GraphicsPipeline;
|
||||||
use vulkano::render_pass::{RenderPass, Subpass};
|
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 vs = composite::vs::load(device.clone()).unwrap();
|
||||||
let fs = composite::fs::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
|
// Before we draw we have to create what is called a pipeline. This is similar to an OpenGL
|
||||||
// program, but much more specific.
|
// 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
|
// 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.
|
// in. The pipeline will only be usable from this particular subpass.
|
||||||
.render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
|
.render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
|
||||||
// We need to indicate the layout of the vertices.
|
// 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.
|
// The content of the vertex buffer describes a list of triangles.
|
||||||
.input_assembly_state(InputAssemblyState::new())
|
.input_assembly_state(InputAssemblyState::new())
|
||||||
// A Vulkan shader can in theory contain multiple entry points, so we have to specify
|
// 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(), ())
|
.fragment_shader(fs.entry_point("main").unwrap(), ())
|
||||||
// Now that our builder is filled, we call `build()` to obtain an actual pipeline.
|
// Now that our builder is filled, we call `build()` to obtain an actual pipeline.
|
||||||
.build(device.clone())
|
.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 std::sync::Arc;
|
||||||
use vulkano::device::{Device, DeviceCreateInfo, DeviceExtensions, Properties, Queue, QueueCreateInfo, QueueFlags};
|
use vulkano::device::{Device};
|
||||||
use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType};
|
|
||||||
use vulkano::image::{ImageAccess, ImageUsage, SwapchainImage};
|
use vulkano::image::{ImageAccess, ImageUsage, SwapchainImage};
|
||||||
use vulkano::instance::{Instance, InstanceCreateInfo};
|
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::swapchain::{acquire_next_image, AcquireError, Surface, Swapchain, SwapchainCreateInfo, SwapchainCreationError, SwapchainPresentInfo};
|
||||||
use vulkano::{sync, VulkanLibrary};
|
use vulkano::{sync, VulkanLibrary};
|
||||||
use vulkano_win::VkSurfaceBuild;
|
use vulkano_win::VkSurfaceBuild;
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
use winit::window::{Window, WindowBuilder};
|
use winit::window::{Window, WindowBuilder};
|
||||||
use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer};
|
|
||||||
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
|
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::image::view::ImageView;
|
||||||
use vulkano::pipeline::graphics::vertex_input::Vertex;
|
|
||||||
use vulkano::pipeline::graphics::viewport::Viewport;
|
use vulkano::pipeline::graphics::viewport::Viewport;
|
||||||
use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass};
|
use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass};
|
||||||
use vulkano::sync::{FlushError, GpuFuture};
|
use vulkano::sync::{FlushError, GpuFuture};
|
||||||
use winit::event::{Event, WindowEvent};
|
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
|
pub struct Renderer {
|
||||||
// to force rustc to use a defined layout for our data, as the default representation has *no
|
pub(crate) memory_allocator: Arc<StandardMemoryAllocator>,
|
||||||
// guarantees*.
|
pub(crate) command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
|
||||||
#[derive(BufferContents, Vertex)]
|
pub(crate) descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>
|
||||||
#[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 fn init() {
|
pub fn init() {
|
||||||
|
@ -91,7 +59,7 @@ pub fn init() {
|
||||||
.build_vk_surface(&event_loop, instance.clone())
|
.build_vk_surface(&event_loop, instance.clone())
|
||||||
.unwrap();
|
.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
|
// 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
|
// 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 (mut swapchain, images) = create_swapchain(&device, &surface);
|
||||||
|
|
||||||
let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
|
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));
|
||||||
|
|
||||||
let (vertex_buffer, index_buffer) = create_quad_buffer(&memory_allocator);
|
|
||||||
|
|
||||||
// At this point, OpenGL initialization would be finished. However in Vulkan it is not. OpenGL
|
// 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
|
// 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
|
// them. Vulkano provides a command buffer allocator, which manages raw Vulkan command pools
|
||||||
// underneath and provides a safe interface for them.
|
// underneath and provides a safe interface for them.
|
||||||
let command_buffer_allocator =
|
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!
|
// Initialization is finally finished!
|
||||||
|
|
||||||
|
@ -152,7 +128,10 @@ pub fn init() {
|
||||||
// that, we store the submission of the previous frame here.
|
// that, we store the submission of the previous frame here.
|
||||||
let mut previous_frame_end = Some(sync::now(device.clone()).boxed());
|
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| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
match event {
|
match event {
|
||||||
|
@ -167,7 +146,31 @@ pub fn init() {
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
recreate_swapchain = true;
|
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 => {
|
Event::RedrawEventsCleared => {
|
||||||
// Do not draw the frame when the screen dimensions are zero. On Windows, this can
|
// Do not draw the frame when the screen dimensions are zero. On Windows, this can
|
||||||
// occur when minimizing the application.
|
// occur when minimizing the application.
|
||||||
|
@ -240,59 +243,9 @@ pub fn init() {
|
||||||
recreate_swapchain = true;
|
recreate_swapchain = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In order to draw, we have to build a *command buffer*. The command buffer object
|
pathtracer.compute();
|
||||||
// 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();
|
|
||||||
|
|
||||||
builder
|
let command_buffer = texture_drawer.draw(framebuffers[image_index as usize].clone(), &viewport, &pathtracer);
|
||||||
// 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 future = previous_frame_end
|
let future = previous_frame_end
|
||||||
.take()
|
.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.
|
/// This function is called once during initialization, then again whenever the window is resized.
|
||||||
fn window_size_dependent_setup(
|
fn window_size_dependent_setup(
|
||||||
images: &[Arc<SwapchainImage>],
|
images: &[Arc<SwapchainImage>],
|
||||||
|
@ -386,45 +307,7 @@ fn window_size_dependent_setup(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
})
|
}).collect::<Vec<_>>()
|
||||||
.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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_render_pass(device: &Arc<Device>, swapchain: &Arc<Swapchain>) -> Arc<RenderPass> {
|
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()
|
).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>>) {
|
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
|
// 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
|
// 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()
|
).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