added obj loader

This commit is contained in:
Sven Vogel 2023-04-13 23:17:15 +02:00
parent a99a15dded
commit dce8b57b0e
22 changed files with 5766 additions and 313 deletions

39
Cargo.lock generated
View File

@ -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"

View File

@ -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"

52
res/example-scene.mtl Normal file
View File

@ -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

4788
res/example-scene.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,4 +3,4 @@ mod shader;
fn main() {
vulkan::init();
}
}

View File

@ -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"
}
}

View File

@ -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);
}

140
src/shader/composite/mod.rs Normal file
View File

@ -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()
}
}

View File

@ -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,
}
}

View File

@ -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);
}
}

View File

@ -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)
}

View File

@ -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));
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,7 +0,0 @@
#define DegreeToRadians(x) (x * 0.01745329251994330)
struct Ray {
vec3 origin;
vec3 direction;
};

View File

@ -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;
}

133
src/vulkan/device.rs Normal file
View File

@ -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);
}

View File

@ -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);
}

View File

@ -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)
}