Basic mesh rendering
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+2930
File diff suppressed because it is too large
Load Diff
+15
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "gaym"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { version = "1.22.0", features = ["derive"] }
|
||||
env_logger = "0.11.8"
|
||||
glam = { version = "0.30.2", features = ["bytemuck"] }
|
||||
log = "0.4.27"
|
||||
thiserror = "2.0.12"
|
||||
vulkano = "0.35.1"
|
||||
vulkano-shaders = "0.35.0"
|
||||
vulkano-win = "0.34.0"
|
||||
winit = "0.30.9"
|
||||
@@ -0,0 +1,55 @@
|
||||
use vulkano::{
|
||||
buffer::AllocateBufferError, command_buffer::CommandBufferExecError, image::AllocateImageError,
|
||||
memory::allocator::MemoryAllocatorError, pipeline::layout::IntoPipelineLayoutCreateInfoError,
|
||||
swapchain::FromWindowError, sync::HostAccessError, LoadingError, Validated, ValidationError,
|
||||
VulkanError,
|
||||
};
|
||||
use winit::raw_window_handle::HandleError;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Event loop error: {0}")]
|
||||
WinitEventLoop(#[from] winit::error::EventLoopError),
|
||||
#[error("Window creation error: {0}")]
|
||||
WinitOs(#[from] winit::error::OsError),
|
||||
|
||||
#[error("Vulkan loading error: {0}")]
|
||||
Loading(#[from] LoadingError),
|
||||
#[error("Window handle error: {0}")]
|
||||
HandleError(#[from] HandleError),
|
||||
#[error("Vulkan error: {0}")]
|
||||
Vulkan(#[from] VulkanError),
|
||||
#[error("Buffer allocation error: {0}")]
|
||||
BufferAllocation(#[from] AllocateBufferError),
|
||||
#[error("Command buffer execution error: {0}")]
|
||||
CommandBufferExec(#[from] CommandBufferExecError),
|
||||
#[error("Cannot select a physical device")]
|
||||
NoValidDevice,
|
||||
#[error("Vulkan surface creation error: {0}")]
|
||||
FromWindow(#[from] FromWindowError),
|
||||
#[error("Pipeline layout create error: {0}")]
|
||||
IntoPipelineLayoutCreateInfo(#[from] IntoPipelineLayoutCreateInfoError),
|
||||
#[error("Host buffer access error: {0}")]
|
||||
HostAccess(#[from] HostAccessError),
|
||||
#[error("Image allocation error: {0}")]
|
||||
AllocateImage(#[from] AllocateImageError),
|
||||
#[error("GPU memory allocation error: {0}")]
|
||||
MemoryAllocator(#[from] MemoryAllocatorError),
|
||||
}
|
||||
|
||||
impl From<Box<ValidationError>> for Error {
|
||||
fn from(value: Box<ValidationError>) -> Self {
|
||||
log::error!("Validation error: {value}");
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Validated<T>> for Error
|
||||
where
|
||||
Error: From<T>,
|
||||
{
|
||||
fn from(value: Validated<T>) -> Self {
|
||||
Error::from(value.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
use std::{process::ExitCode, sync::Arc};
|
||||
|
||||
use error::Error;
|
||||
use render::Renderer;
|
||||
use winit::{
|
||||
application::ApplicationHandler,
|
||||
dpi::PhysicalSize,
|
||||
event::{DeviceEvent, DeviceId, StartCause, WindowEvent},
|
||||
event_loop::{ActiveEventLoop, EventLoop},
|
||||
window::{Window, WindowId},
|
||||
};
|
||||
|
||||
pub mod error;
|
||||
pub mod render;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Engine {
|
||||
window: Option<Arc<Window>>,
|
||||
renderer: Option<Renderer>,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn ensure_renderer(&mut self, event_loop: &ActiveEventLoop) -> Result<(), Error> {
|
||||
if self.window.is_none() {
|
||||
let window_attributes = Window::default_attributes()
|
||||
.with_resizable(false)
|
||||
.with_inner_size(PhysicalSize::new(800, 600));
|
||||
|
||||
let window = Arc::new(event_loop.create_window(window_attributes)?);
|
||||
let render = Renderer::from_window(window.clone())?;
|
||||
|
||||
self.window = Some(window);
|
||||
self.renderer = Some(render);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationHandler for Engine {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
if let Err(error) = self.ensure_renderer(event_loop) {
|
||||
log::error!("Renderer setup error: {error}");
|
||||
event_loop.exit();
|
||||
}
|
||||
}
|
||||
|
||||
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
|
||||
let _ = event_loop;
|
||||
log::info!("Exiting");
|
||||
self.renderer = None;
|
||||
}
|
||||
|
||||
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
|
||||
let _ = event_loop;
|
||||
}
|
||||
|
||||
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
|
||||
let _ = event_loop;
|
||||
let _ = cause;
|
||||
}
|
||||
|
||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: ()) {
|
||||
let _ = event_loop;
|
||||
let _ = event;
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
let _ = window_id;
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
log::info!("Close requested");
|
||||
event_loop.exit();
|
||||
}
|
||||
WindowEvent::Resized(size) => {
|
||||
log::info!("Window resized: {size:?}");
|
||||
if let Some(render) = self.renderer.as_mut() {
|
||||
render.set_swapchain_dirty();
|
||||
}
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Some(render) = self.renderer.as_mut() {
|
||||
if let Err(error) = render.render() {
|
||||
log::error!("Render error: {error}");
|
||||
event_loop.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn device_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
device_id: DeviceId,
|
||||
event: DeviceEvent,
|
||||
) {
|
||||
let _ = event_loop;
|
||||
let _ = device_id;
|
||||
let _ = event;
|
||||
}
|
||||
|
||||
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||||
let _ = event_loop;
|
||||
|
||||
if let Some(window) = self.window.as_ref() {
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
|
||||
let _ = event_loop;
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), Error> {
|
||||
let event_loop = EventLoop::new()?;
|
||||
let mut engine = Engine::default();
|
||||
|
||||
event_loop.run_app(&mut engine)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
env_logger::init();
|
||||
match run() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(error) => {
|
||||
log::error!("Exited with error: {error}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use glam::Vec3;
|
||||
use vulkano::{
|
||||
buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer},
|
||||
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
|
||||
};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
use super::{util::Allocators, vertex::InputVertex};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MeshBuilder {
|
||||
vertices: Vec<InputVertex>,
|
||||
}
|
||||
|
||||
impl MeshBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vertices: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_cube(&mut self, x: i32, y: i32, z: i32) {
|
||||
let position = Vec3::new(x as f32, y as f32, z as f32);
|
||||
// Front
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
);
|
||||
// Left
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
Vec3::new(0.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
);
|
||||
// Back
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
Vec3::new(1.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
);
|
||||
// Right
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
Vec3::new(1.0, 1.0, 1.0),
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
);
|
||||
// Bottom
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
Vec3::new(1.0, 0.0, 1.0),
|
||||
);
|
||||
// Top
|
||||
self.add_face(
|
||||
position,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
Vec3::new(1.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, 1.0, 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
fn add_face(&mut self, d: Vec3, p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, c: Vec3) {
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p0 + d,
|
||||
i_color: c,
|
||||
});
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p1 + d,
|
||||
i_color: c,
|
||||
});
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p2 + d,
|
||||
i_color: c,
|
||||
});
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p2 + d,
|
||||
i_color: c,
|
||||
});
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p3 + d,
|
||||
i_color: c,
|
||||
});
|
||||
self.vertices.push(InputVertex {
|
||||
i_position: p0 + d,
|
||||
i_color: c,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn upload(self, allocators: &Arc<Allocators>) -> Result<Subbuffer<[InputVertex]>, Error> {
|
||||
let buffer = Buffer::from_iter(
|
||||
allocators.memory.clone(),
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::VERTEX_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
memory_type_filter: MemoryTypeFilter::HOST_SEQUENTIAL_WRITE
|
||||
| MemoryTypeFilter::PREFER_DEVICE,
|
||||
..Default::default()
|
||||
},
|
||||
self.vertices.into_iter(),
|
||||
)?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
use std::{sync::Arc, time::Instant};
|
||||
|
||||
use glam::{Mat4, Vec3};
|
||||
use mesh::MeshBuilder;
|
||||
use util::{Allocators, ViewportExt};
|
||||
use vertex::InputVertex;
|
||||
use vulkano::{
|
||||
buffer::{
|
||||
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
|
||||
BufferUsage, Subbuffer,
|
||||
},
|
||||
command_buffer::{
|
||||
AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassBeginInfo,
|
||||
SubpassContents, SubpassEndInfo,
|
||||
},
|
||||
descriptor_set::{DescriptorSet, WriteDescriptorSet},
|
||||
device::{Device, DeviceExtensions, Queue},
|
||||
format::Format,
|
||||
image::Image,
|
||||
instance::{InstanceCreateFlags, InstanceCreateInfo},
|
||||
memory::allocator::MemoryTypeFilter,
|
||||
pipeline::{graphics::viewport::Viewport, GraphicsPipeline, Pipeline, PipelineBindPoint},
|
||||
render_pass::{Framebuffer, RenderPass},
|
||||
shader::EntryPoint,
|
||||
swapchain::{self, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo},
|
||||
sync::{self, GpuFuture},
|
||||
Validated, VulkanError,
|
||||
};
|
||||
use winit::window::Window;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
pub mod mesh;
|
||||
pub mod shaders;
|
||||
pub mod util;
|
||||
pub mod vertex;
|
||||
|
||||
pub const DEPTH_FORMAT: Format = Format::D32_SFLOAT;
|
||||
|
||||
pub struct Renderer {
|
||||
window: Arc<Window>,
|
||||
device: Arc<Device>,
|
||||
queue: Arc<Queue>,
|
||||
|
||||
allocators: Arc<Allocators>,
|
||||
|
||||
swapchain: Arc<Swapchain>,
|
||||
swapchain_images: Vec<Arc<Image>>,
|
||||
swapchain_dirty: bool,
|
||||
|
||||
vertex_buffer: Subbuffer<[InputVertex]>,
|
||||
uniform_allocator: SubbufferAllocator,
|
||||
|
||||
render_pass: Arc<RenderPass>,
|
||||
viewport: Viewport,
|
||||
framebuffers: Vec<Arc<Framebuffer>>,
|
||||
|
||||
vertex_shader: EntryPoint,
|
||||
fragment_shader: EntryPoint,
|
||||
pipeline: Arc<GraphicsPipeline>,
|
||||
|
||||
start: Instant,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn from_window(window: Arc<Window>) -> Result<Self, Error> {
|
||||
let instance_extensions = Surface::required_extensions(&window)?;
|
||||
let device_extensions = DeviceExtensions {
|
||||
khr_swapchain: true,
|
||||
khr_maintenance1: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let instance = util::create_instance(InstanceCreateInfo {
|
||||
flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
|
||||
enabled_extensions: instance_extensions,
|
||||
..Default::default()
|
||||
})?;
|
||||
|
||||
let surface = Surface::from_window(instance.clone(), window.clone())?;
|
||||
|
||||
let (device, queue) = util::create_device(&instance, device_extensions, &surface)?;
|
||||
let dimensions = window.inner_size();
|
||||
let (swapchain, swapchain_images) = util::create_swapchain(&device, &surface, dimensions)?;
|
||||
|
||||
let allocators = Allocators::new_default(device.clone())?;
|
||||
|
||||
let vertex_buffer = {
|
||||
let mut builder = MeshBuilder::new();
|
||||
builder.add_cube(0, 0, 0);
|
||||
builder.add_cube(-1, 0, 0);
|
||||
builder.add_cube(0, 0, -1);
|
||||
builder.add_cube(-1, 0, -1);
|
||||
builder.add_cube(0, 1, 0);
|
||||
builder.upload(&allocators)?
|
||||
};
|
||||
|
||||
let uniform_allocator = SubbufferAllocator::new(
|
||||
allocators.memory.clone(),
|
||||
SubbufferAllocatorCreateInfo {
|
||||
buffer_usage: BufferUsage::UNIFORM_BUFFER,
|
||||
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
|
||||
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let render_pass = vulkano::single_pass_renderpass!(
|
||||
device.clone(),
|
||||
attachments: {
|
||||
color: {
|
||||
format: swapchain.image_format(),
|
||||
samples: 1,
|
||||
load_op: DontCare,
|
||||
store_op: Store,
|
||||
},
|
||||
msaa: {
|
||||
format: swapchain.image_format(),
|
||||
samples: 4,
|
||||
load_op: Clear,
|
||||
store_op: DontCare,
|
||||
},
|
||||
depth: {
|
||||
format: DEPTH_FORMAT,
|
||||
samples: 4,
|
||||
load_op: Clear,
|
||||
store_op: DontCare,
|
||||
}
|
||||
},
|
||||
pass: {
|
||||
color: [msaa],
|
||||
color_resolve: [color],
|
||||
depth_stencil: {depth},
|
||||
},
|
||||
)?;
|
||||
|
||||
let framebuffers = util::create_3d_framebuffers(
|
||||
&allocators,
|
||||
&render_pass,
|
||||
&swapchain_images,
|
||||
DEPTH_FORMAT,
|
||||
)?;
|
||||
|
||||
let viewport = Viewport::from_window(&window);
|
||||
let vertex_shader = shaders::plain::vs::load(device.clone())?
|
||||
.entry_point("main")
|
||||
.expect("shader entry point");
|
||||
let fragment_shader = shaders::plain::fs::load(device.clone())?
|
||||
.entry_point("main")
|
||||
.expect("shader entry point");
|
||||
let pipeline = shaders::create_two_stage_3d_pipeline::<InputVertex>(
|
||||
&device,
|
||||
&render_pass,
|
||||
viewport.clone(),
|
||||
&vertex_shader,
|
||||
&fragment_shader,
|
||||
)?;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
Ok(Self {
|
||||
window,
|
||||
device,
|
||||
queue,
|
||||
|
||||
allocators,
|
||||
|
||||
swapchain,
|
||||
swapchain_images,
|
||||
swapchain_dirty: false,
|
||||
|
||||
vertex_buffer,
|
||||
uniform_allocator,
|
||||
|
||||
render_pass,
|
||||
viewport,
|
||||
framebuffers,
|
||||
|
||||
vertex_shader,
|
||||
fragment_shader,
|
||||
pipeline,
|
||||
|
||||
start,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(&mut self) -> Result<(), Error> {
|
||||
self.refresh_swapchain()?;
|
||||
|
||||
let (image_index, suboptimal, acquire_future) =
|
||||
match swapchain::acquire_next_image(self.swapchain.clone(), None)
|
||||
.map_err(Validated::unwrap)
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(VulkanError::OutOfDate) => {
|
||||
self.swapchain_dirty = true;
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
if suboptimal {
|
||||
self.swapchain_dirty = true;
|
||||
}
|
||||
|
||||
let framebuffer = &self.framebuffers[image_index as usize];
|
||||
|
||||
let dimensions = self.window.inner_size();
|
||||
let aspect = dimensions.width as f32 / dimensions.height as f32;
|
||||
|
||||
let delta = self.start.elapsed();
|
||||
let dt = delta.as_secs_f64() * 1.0;
|
||||
let model = Mat4::IDENTITY;
|
||||
let projection = Mat4::perspective_rh_gl(45.0f32.to_radians(), aspect, 0.01, 100.0);
|
||||
|
||||
fn make_view(camera: Vec3, target: Vec3) -> Mat4 {
|
||||
Mat4::look_at_rh(camera, target, Vec3::Y)
|
||||
}
|
||||
|
||||
let view = make_view(
|
||||
Vec3::new((dt.cos() * 3.0) as f32, 3.0, (dt.sin() * 3.0) as f32),
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
);
|
||||
|
||||
let uniform_buffer = self
|
||||
.uniform_allocator
|
||||
.allocate_sized::<shaders::plain::SceneData>()?;
|
||||
{
|
||||
let mut write = uniform_buffer.write()?;
|
||||
*write = shaders::plain::SceneData {
|
||||
model: model.to_cols_array_2d(),
|
||||
view: view.to_cols_array_2d(),
|
||||
projection: projection.to_cols_array_2d(),
|
||||
};
|
||||
}
|
||||
|
||||
let descriptor_layout = &self.pipeline.layout().set_layouts()[0];
|
||||
let set0 = DescriptorSet::new(
|
||||
self.allocators.descriptor_set.clone(),
|
||||
descriptor_layout.clone(),
|
||||
[WriteDescriptorSet::buffer(0, uniform_buffer)],
|
||||
[],
|
||||
)?;
|
||||
|
||||
let mut builder = AutoCommandBufferBuilder::primary(
|
||||
self.allocators.command_buffer.clone(),
|
||||
self.queue.queue_family_index(),
|
||||
CommandBufferUsage::OneTimeSubmit,
|
||||
)?;
|
||||
|
||||
builder.begin_render_pass(
|
||||
RenderPassBeginInfo {
|
||||
clear_values: vec![None, Some([0.3, 0.5, 0.9, 1.0].into()), Some(1.0.into())],
|
||||
..RenderPassBeginInfo::framebuffer(framebuffer.clone())
|
||||
},
|
||||
SubpassBeginInfo {
|
||||
contents: SubpassContents::Inline,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
builder
|
||||
.bind_pipeline_graphics(self.pipeline.clone())?
|
||||
.bind_descriptor_sets(
|
||||
PipelineBindPoint::Graphics,
|
||||
self.pipeline.layout().clone(),
|
||||
0,
|
||||
set0.offsets([0]),
|
||||
)?
|
||||
.bind_vertex_buffers(0, self.vertex_buffer.clone())?;
|
||||
|
||||
unsafe {
|
||||
builder.draw(self.vertex_buffer.len() as _, 1, 0, 0)?;
|
||||
}
|
||||
|
||||
builder.end_render_pass(SubpassEndInfo::default())?;
|
||||
|
||||
let command_buffer = builder.build()?;
|
||||
|
||||
sync::now(self.device.clone())
|
||||
.join(acquire_future)
|
||||
.then_execute(self.queue.clone(), command_buffer)?
|
||||
.then_swapchain_present(
|
||||
self.queue.clone(),
|
||||
SwapchainPresentInfo::swapchain_image_index(self.swapchain.clone(), image_index),
|
||||
)
|
||||
.then_signal_fence_and_flush()?
|
||||
.wait(None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn refresh_swapchain(&mut self) -> Result<(), Error> {
|
||||
if !self.swapchain_dirty {
|
||||
return Ok(());
|
||||
}
|
||||
log::info!("Recreate swapchain");
|
||||
|
||||
let dimensions = self.window.inner_size();
|
||||
let create_info = self.swapchain.create_info();
|
||||
let create_info = SwapchainCreateInfo {
|
||||
image_extent: dimensions.into(),
|
||||
..create_info
|
||||
};
|
||||
let (new_swapchain, new_swapchain_images) = self.swapchain.recreate(create_info)?;
|
||||
|
||||
self.viewport = Viewport::from_window(&self.window);
|
||||
self.swapchain = new_swapchain;
|
||||
self.swapchain_images = new_swapchain_images;
|
||||
|
||||
self.rebuild_pipelines()?;
|
||||
|
||||
self.framebuffers = util::create_3d_framebuffers(
|
||||
&self.allocators,
|
||||
&self.render_pass,
|
||||
&self.swapchain_images,
|
||||
DEPTH_FORMAT,
|
||||
)?;
|
||||
|
||||
self.swapchain_dirty = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rebuild_pipelines(&mut self) -> Result<(), Error> {
|
||||
self.pipeline = shaders::create_two_stage_3d_pipeline::<InputVertex>(
|
||||
&self.device,
|
||||
&self.render_pass,
|
||||
self.viewport.clone(),
|
||||
&self.vertex_shader,
|
||||
&self.fragment_shader,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_swapchain_dirty(&mut self) {
|
||||
self.swapchain_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use vulkano::{
|
||||
descriptor_set::layout::DescriptorType,
|
||||
device::Device,
|
||||
pipeline::{
|
||||
graphics::{
|
||||
color_blend::{ColorBlendAttachmentState, ColorBlendState},
|
||||
depth_stencil::{DepthState, DepthStencilState},
|
||||
input_assembly::InputAssemblyState,
|
||||
multisample::MultisampleState,
|
||||
rasterization::RasterizationState,
|
||||
vertex_input::{Vertex, VertexDefinition},
|
||||
viewport::{Viewport, ViewportState},
|
||||
GraphicsPipelineCreateInfo,
|
||||
},
|
||||
layout::PipelineDescriptorSetLayoutCreateInfo,
|
||||
GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo,
|
||||
},
|
||||
render_pass::{RenderPass, Subpass},
|
||||
shader::EntryPoint,
|
||||
};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
pub mod plain {
|
||||
pub mod vs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "vertex",
|
||||
path: "src/render/shaders/plain.vert",
|
||||
}
|
||||
}
|
||||
pub mod fs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "fragment",
|
||||
path: "src/render/shaders/plain.frag",
|
||||
}
|
||||
}
|
||||
|
||||
pub use vs::SceneData;
|
||||
}
|
||||
|
||||
pub fn create_two_stage_3d_pipeline<V: Vertex>(
|
||||
device: &Arc<Device>,
|
||||
render_pass: &Arc<RenderPass>,
|
||||
viewport: Viewport,
|
||||
vertex_shader: &EntryPoint,
|
||||
fragment_shader: &EntryPoint,
|
||||
) -> Result<Arc<GraphicsPipeline>, Error> {
|
||||
let vertex_input_state = V::per_vertex().definition(vertex_shader)?;
|
||||
let stages = [
|
||||
PipelineShaderStageCreateInfo::new(vertex_shader.clone()),
|
||||
PipelineShaderStageCreateInfo::new(fragment_shader.clone()),
|
||||
];
|
||||
|
||||
let layout = {
|
||||
let mut layout_create_info = PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages);
|
||||
layout_create_info.set_layouts[0]
|
||||
.bindings
|
||||
.get_mut(&0)
|
||||
.unwrap()
|
||||
.descriptor_type = DescriptorType::UniformBufferDynamic;
|
||||
|
||||
PipelineLayout::new(
|
||||
device.clone(),
|
||||
layout_create_info.into_pipeline_layout_create_info(device.clone())?,
|
||||
)?
|
||||
};
|
||||
|
||||
let subpass = Subpass::from(render_pass.clone(), 0).unwrap();
|
||||
|
||||
let pipeline = GraphicsPipeline::new(
|
||||
device.clone(),
|
||||
None,
|
||||
GraphicsPipelineCreateInfo {
|
||||
stages: stages.into_iter().collect(),
|
||||
vertex_input_state: Some(vertex_input_state),
|
||||
input_assembly_state: Some(InputAssemblyState::default()),
|
||||
viewport_state: Some(ViewportState {
|
||||
viewports: [viewport].into_iter().collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
rasterization_state: Some(RasterizationState::default()),
|
||||
multisample_state: Some(MultisampleState {
|
||||
rasterization_samples: subpass.num_samples().unwrap(),
|
||||
..Default::default()
|
||||
}),
|
||||
color_blend_state: Some(ColorBlendState::with_attachment_states(
|
||||
subpass.num_color_attachments(),
|
||||
ColorBlendAttachmentState::default(),
|
||||
)),
|
||||
depth_stencil_state: Some(DepthStencilState {
|
||||
depth: Some(DepthState::simple()),
|
||||
..Default::default()
|
||||
}),
|
||||
subpass: Some(subpass.into()),
|
||||
..GraphicsPipelineCreateInfo::layout(layout)
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(pipeline)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#version 460
|
||||
|
||||
layout(location = 0) in vec3 i_color;
|
||||
|
||||
layout(location = 0) out vec4 o_color;
|
||||
|
||||
void main() {
|
||||
o_color = vec4(i_color, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#version 460
|
||||
|
||||
layout(location = 0) in vec3 i_position;
|
||||
layout(location = 1) in vec3 i_color;
|
||||
|
||||
layout(location = 0) out vec3 o_color;
|
||||
|
||||
layout(set = 0, binding = 0) uniform SceneData {
|
||||
mat4 model;
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
} u_scene;
|
||||
|
||||
void main() {
|
||||
vec4 m_position = vec4(i_position, 1.0);
|
||||
vec4 s_position = u_scene.projection * u_scene.view * m_position;
|
||||
|
||||
gl_Position = s_position;
|
||||
o_color = i_color;
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use vulkano::{
|
||||
command_buffer::allocator::{CommandBufferAllocator, StandardCommandBufferAllocator},
|
||||
descriptor_set::allocator::{DescriptorSetAllocator, StandardDescriptorSetAllocator},
|
||||
device::{
|
||||
physical::PhysicalDeviceType, Device, DeviceCreateInfo, DeviceExtensions, Queue,
|
||||
QueueCreateInfo, QueueFlags,
|
||||
},
|
||||
format::Format,
|
||||
image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage, SampleCount},
|
||||
instance::{Instance, InstanceCreateInfo},
|
||||
memory::allocator::StandardMemoryAllocator,
|
||||
pipeline::graphics::viewport::Viewport,
|
||||
render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass},
|
||||
swapchain::{Surface, Swapchain, SwapchainCreateInfo},
|
||||
VulkanLibrary,
|
||||
};
|
||||
use winit::{dpi::PhysicalSize, window::Window};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
pub struct Allocators {
|
||||
pub memory: Arc<StandardMemoryAllocator>,
|
||||
pub command_buffer: Arc<dyn CommandBufferAllocator>,
|
||||
pub descriptor_set: Arc<dyn DescriptorSetAllocator>,
|
||||
}
|
||||
|
||||
pub trait ViewportExt {
|
||||
fn from_window(window: &Arc<Window>) -> Self;
|
||||
}
|
||||
|
||||
impl ViewportExt for Viewport {
|
||||
fn from_window(window: &Arc<Window>) -> Self {
|
||||
let dimensions = window.inner_size();
|
||||
|
||||
Self {
|
||||
offset: [0.0, dimensions.height as _],
|
||||
extent: [dimensions.width as _, -(dimensions.height as f32)],
|
||||
depth_range: 0.0..=1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Allocators {
|
||||
pub fn new_default(device: Arc<Device>) -> Result<Arc<Self>, Error> {
|
||||
let memory = Arc::new(StandardMemoryAllocator::new_default(device.clone()));
|
||||
let command_buffer = Arc::new(StandardCommandBufferAllocator::new(
|
||||
device.clone(),
|
||||
Default::default(),
|
||||
));
|
||||
let descriptor_set = Arc::new(StandardDescriptorSetAllocator::new(
|
||||
device.clone(),
|
||||
Default::default(),
|
||||
));
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
memory,
|
||||
command_buffer,
|
||||
descriptor_set,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_instance(info: InstanceCreateInfo) -> Result<Arc<Instance>, Error> {
|
||||
let library = VulkanLibrary::new()?;
|
||||
let instance = Instance::new(library, info)?;
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
pub fn create_device(
|
||||
instance: &Arc<Instance>,
|
||||
required_extensions: DeviceExtensions,
|
||||
surface: &Arc<Surface>,
|
||||
) -> Result<(Arc<Device>, Arc<Queue>), Error> {
|
||||
let (physical_device, queue_family_index) = instance
|
||||
.enumerate_physical_devices()?
|
||||
.filter(|device| device.supported_extensions().contains(&required_extensions))
|
||||
.filter_map(|device| {
|
||||
let queue_family_index = device
|
||||
.queue_family_properties()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.position(|(index, qf)| {
|
||||
qf.queue_flags.contains(QueueFlags::GRAPHICS)
|
||||
&& device
|
||||
.surface_support(index as u32, surface)
|
||||
.unwrap_or(false)
|
||||
})?;
|
||||
|
||||
Some((device, queue_family_index as u32))
|
||||
})
|
||||
.min_by_key(|(device, _)| match device.properties().device_type {
|
||||
PhysicalDeviceType::DiscreteGpu => 0,
|
||||
PhysicalDeviceType::IntegratedGpu => 1,
|
||||
PhysicalDeviceType::VirtualGpu => 2,
|
||||
PhysicalDeviceType::Cpu => 3,
|
||||
_ => 3,
|
||||
})
|
||||
.ok_or(Error::NoValidDevice)?;
|
||||
|
||||
let (device, mut queues) = Device::new(
|
||||
physical_device,
|
||||
DeviceCreateInfo {
|
||||
queue_create_infos: vec![QueueCreateInfo {
|
||||
queue_family_index,
|
||||
..Default::default()
|
||||
}],
|
||||
enabled_extensions: required_extensions,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let queue = queues.next().unwrap();
|
||||
|
||||
Ok((device, queue))
|
||||
}
|
||||
|
||||
pub fn create_swapchain(
|
||||
device: &Arc<Device>,
|
||||
surface: &Arc<Surface>,
|
||||
dimensions: PhysicalSize<u32>,
|
||||
) -> Result<(Arc<Swapchain>, Vec<Arc<Image>>), Error> {
|
||||
let physical_device = device.physical_device();
|
||||
let caps = physical_device.surface_capabilities(surface, Default::default())?;
|
||||
|
||||
let composite_alpha = caps
|
||||
.supported_composite_alpha
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("a supported composite alpha");
|
||||
let image_format = physical_device.surface_formats(surface, Default::default())?[0].0;
|
||||
|
||||
let (swapchain, images) = Swapchain::new(
|
||||
device.clone(),
|
||||
surface.clone(),
|
||||
SwapchainCreateInfo {
|
||||
min_image_count: caps.min_image_count + 1,
|
||||
image_format,
|
||||
image_extent: dimensions.into(),
|
||||
image_usage: ImageUsage::COLOR_ATTACHMENT
|
||||
| ImageUsage::TRANSFER_DST
|
||||
| ImageUsage::STORAGE,
|
||||
composite_alpha,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok((swapchain, images))
|
||||
}
|
||||
|
||||
pub fn create_3d_framebuffers(
|
||||
allocators: &Allocators,
|
||||
render_pass: &Arc<RenderPass>,
|
||||
swapchain_images: &[Arc<Image>],
|
||||
depth_format: Format,
|
||||
) -> Result<Vec<Arc<Framebuffer>>, Error> {
|
||||
let msaa_image = Image::new(
|
||||
allocators.memory.clone(),
|
||||
ImageCreateInfo {
|
||||
image_type: ImageType::Dim2d,
|
||||
format: swapchain_images[0].format(),
|
||||
extent: swapchain_images[0].extent(),
|
||||
usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT,
|
||||
samples: SampleCount::Sample4,
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
)?;
|
||||
let depth_image = Image::new(
|
||||
allocators.memory.clone(),
|
||||
ImageCreateInfo {
|
||||
format: depth_format,
|
||||
image_type: ImageType::Dim2d,
|
||||
extent: swapchain_images[0].extent(),
|
||||
usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT,
|
||||
samples: SampleCount::Sample4,
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
)?;
|
||||
let msaa_attachment = ImageView::new_default(msaa_image)?;
|
||||
let depth_attachment = ImageView::new_default(depth_image)?;
|
||||
|
||||
let framebuffers = swapchain_images
|
||||
.iter()
|
||||
.map(|image| {
|
||||
let color_attachment = ImageView::new_default(image.clone())?;
|
||||
let framebuffer = Framebuffer::new(
|
||||
render_pass.clone(),
|
||||
FramebufferCreateInfo {
|
||||
attachments: vec![
|
||||
color_attachment,
|
||||
msaa_attachment.clone(),
|
||||
depth_attachment.clone(),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(framebuffer)
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
|
||||
Ok(framebuffers)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use glam::Vec3;
|
||||
use vulkano::{buffer::BufferContents, pipeline::graphics::vertex_input::Vertex};
|
||||
|
||||
#[derive(Debug, Clone, Copy, BufferContents, Vertex)]
|
||||
#[repr(C)]
|
||||
pub struct InputVertex {
|
||||
#[format(R32G32B32_SFLOAT)]
|
||||
pub i_position: Vec3,
|
||||
#[format(R32G32B32_SFLOAT)]
|
||||
pub i_color: Vec3,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user