- The state of the game is now tracked using a simple state machine, where the main struct representing the game is `Game` and its state a generic implementing `GameState` on that struct, the following states exist: - `Unstarted`: Nothing has been done yet - `RenderAvailable`: The very basic things for rendering are available (sdl context, entry, instance, device, cmdbuf, surface, etc) - `LoadingWorld`: We are loading into a world, the only thing that can be done here currently is add things to be rendered (Components) of which only the `Cube` is available, with the skybox being automaticall added. - `InWorld`: We have loaded a world and can (theoretically) render things and move about - The render logic now lives in `render/` and is split both by phase, as well as components and the swapchain having their own modules - The input logic now lives in input and constits of one trait that should be implemented for states that should handle input, and is currently capable of handling mouse movenent, key presses and keys that should be held down (ex, movement keys) - The game logic lives in `game/` and is/will be responsible for driving both the input and render modules Note: the current commit is broken, as our surface is lost when returning from `Game<Unstarted>::init_render`pull/3/head
parent
8168eebf7b
commit
2f15842ca4
@ -0,0 +1,96 @@
|
||||
use std::{
|
||||
process, thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use sdl2::{
|
||||
event::{Event, WindowEvent},
|
||||
keyboard::Keycode,
|
||||
};
|
||||
|
||||
use crate::{Game, InWorld, RenderAvailable, input::InputHandler};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Vector3 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Camera {
|
||||
pub origin: Vector3,
|
||||
pub rotation: Vector3,
|
||||
}
|
||||
|
||||
impl Game<InWorld> {
|
||||
pub fn main_loop(mut self) -> Game<RenderAvailable> {
|
||||
let mut event_pump = self
|
||||
.state
|
||||
.sdl_context
|
||||
.event_pump()
|
||||
.expect("Failed to get event pump");
|
||||
let mouse = self.state.sdl_context.mouse();
|
||||
|
||||
mouse.set_relative_mouse_mode(true);
|
||||
|
||||
let mut running = true;
|
||||
while running {
|
||||
let now = Instant::now();
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
// Setting running to false just returns to
|
||||
// RenderAvailable state (and then main menu)
|
||||
Event::Quit { .. } => process::exit(0),
|
||||
Event::Window {
|
||||
win_event: WindowEvent::Resized(w, h),
|
||||
..
|
||||
} => {
|
||||
self.window_resize((w as u32, h as u32));
|
||||
}
|
||||
Event::KeyDown {
|
||||
keycode: Some(keycode),
|
||||
keymod,
|
||||
repeat,
|
||||
..
|
||||
} => self.handle_keydown(keycode, keymod, repeat),
|
||||
|
||||
Event::KeyUp {
|
||||
keycode: Some(keycode),
|
||||
keymod,
|
||||
repeat,
|
||||
..
|
||||
} => self.handle_keyup(keycode, keymod, repeat),
|
||||
Event::MouseMotion {
|
||||
mousestate,
|
||||
x,
|
||||
y,
|
||||
xrel,
|
||||
yrel,
|
||||
..
|
||||
} => self.handle_mouse_motion(mousestate, (x, y), (xrel, yrel)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
event_pump
|
||||
.keyboard_state()
|
||||
.pressed_scancodes()
|
||||
.filter_map(Keycode::from_scancode)
|
||||
.for_each(|k| self.handle_cont_key(k));
|
||||
|
||||
self.render_frame();
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
let delay = (1_000_000u128 / 60).saturating_sub(elapsed.as_micros());
|
||||
thread::sleep(Duration::from_micros(delay as u64));
|
||||
}
|
||||
|
||||
Game {
|
||||
state: RenderAvailable {
|
||||
sdl_context: self.state.sdl_context,
|
||||
window: self.state.window,
|
||||
ctx: self.state.ctx,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
use sdl2::{
|
||||
keyboard::{Keycode, Mod},
|
||||
mouse::MouseState,
|
||||
};
|
||||
|
||||
use crate::{Game, InWorld};
|
||||
|
||||
use super::InputHandler;
|
||||
|
||||
const MOV_STEP: f32 = 0.05;
|
||||
const ROT_MOUSE_SCALE: f32 = 0.001;
|
||||
|
||||
impl InputHandler for Game<InWorld> {
|
||||
fn handle_cont_key(&mut self, keycode: Keycode) {
|
||||
let Self {
|
||||
state: InWorld { camera, .. },
|
||||
} = self;
|
||||
|
||||
match keycode {
|
||||
Keycode::W => {
|
||||
camera.origin.x -= -camera.rotation.y.sin() * MOV_STEP;
|
||||
camera.origin.z -= camera.rotation.y.cos() * MOV_STEP;
|
||||
}
|
||||
Keycode::S => {
|
||||
camera.origin.x += -camera.rotation.y.sin() * MOV_STEP;
|
||||
camera.origin.z += camera.rotation.y.cos() * MOV_STEP;
|
||||
}
|
||||
Keycode::A => {
|
||||
camera.origin.x += camera.rotation.y.cos() * MOV_STEP;
|
||||
camera.origin.z += camera.rotation.y.sin() * MOV_STEP;
|
||||
}
|
||||
Keycode::D => {
|
||||
camera.origin.z -= camera.rotation.y.sin() * MOV_STEP;
|
||||
camera.origin.x -= camera.rotation.y.cos() * MOV_STEP;
|
||||
}
|
||||
Keycode::Space => {
|
||||
camera.origin.y += MOV_STEP;
|
||||
}
|
||||
Keycode::LShift => {
|
||||
camera.origin.y -= MOV_STEP;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_keydown(&mut self, keycode: Keycode, modifier: Mod, repeat: bool) {
|
||||
match keycode {
|
||||
Keycode::Escape => {
|
||||
let mouse = self.state.sdl_context.mouse();
|
||||
mouse.set_relative_mouse_mode(!mouse.relative_mouse_mode());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_motion(
|
||||
&mut self,
|
||||
state: MouseState,
|
||||
abs: (i32, i32),
|
||||
(xrel, yrel): (i32, i32),
|
||||
) {
|
||||
if self.state.sdl_context.mouse().relative_mouse_mode() {
|
||||
self.state.camera.rotation.y -= xrel as f32 * ROT_MOUSE_SCALE;
|
||||
self.state.camera.rotation.x -= yrel as f32 * ROT_MOUSE_SCALE;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
mod ingame_input;
|
||||
|
||||
use sdl2::{
|
||||
keyboard::{Keycode, Mod},
|
||||
mouse::MouseState,
|
||||
};
|
||||
|
||||
// More methods to be added for other forms of input
|
||||
pub trait InputHandler {
|
||||
// Handle keys which are meant to be held down and repeated every frame, ex movement keys
|
||||
fn handle_cont_key(&mut self, keycode: Keycode) {}
|
||||
|
||||
// Handle single keypresses or keys held down after the repear delay
|
||||
fn handle_keydown(&mut self, keycode: Keycode, modifier: Mod, repeat: bool) {}
|
||||
fn handle_keyup(&mut self, keycode: Keycode, modifier: Mod, repeat: bool) {}
|
||||
|
||||
// Handle mouse movements
|
||||
fn handle_mouse_motion(&mut self, state: MouseState, abs: (i32, i32), rel: (i32, i32)) {}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,413 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ash::vk;
|
||||
|
||||
use crate::{Game, LoadingWorld, RenderCtx, render::MemType};
|
||||
|
||||
use super::loading_world::WorldComponent;
|
||||
|
||||
pub struct Cube {
|
||||
mesh_buf: vk::Buffer,
|
||||
pipeline: vk::Pipeline,
|
||||
pipe_layout: vk::PipelineLayout,
|
||||
}
|
||||
|
||||
const MESH_SIZE: u64 = 36 * 2 * 4 * 4;
|
||||
|
||||
#[rustfmt::skip]
|
||||
const POSITIONS: [f32; 36 * 2 * 4] = [
|
||||
// BOTTOM
|
||||
-0.5, 0.5, 1.0, 1.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
|
||||
0.5, 0.5, 2.0, 1.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
|
||||
-0.5, 0.5, 2.0, 1.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
|
||||
0.5, 0.5, 1.0, 1.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
|
||||
0.5, 0.5, 2.0, 1.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
|
||||
-0.5, 0.5, 1.0, 1.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
|
||||
// TOP
|
||||
0.5, -0.5, 2.0, 1.0,
|
||||
0.0, -1.0, 0.0, 0.0,
|
||||
|
||||
-0.5, -0.5, 1.0, 1.0,
|
||||
0.0, -1.0, 0.0, 0.0,
|
||||
|
||||
-0.5, -0.5, 2.0, 1.0,
|
||||
0.0, -1.0, 0.0, 0.0,
|
||||
|
||||
0.5, -0.5, 2.0, 1.0,
|
||||
0.0, -1.0, 0.0, 0.0,
|
||||
|
||||
0.5, -0.5, 1.0, 1.0,
|
||||
0.0, -1.0, 0.0, 0.0,
|
||||
|
||||
-0.5, -0.5, 1.0, 1.0,
|
||||
0.0, -1.0, 0.0, 0.0,
|
||||
|
||||
// FRONT
|
||||
-0.5, -0.5, 1.0, 1.0,
|
||||
0.0, 0.0, -1.0, 0.0,
|
||||
|
||||
0.5, 0.5, 1.0, 1.0,
|
||||
0.0, 0.0, -1.0, 0.0,
|
||||
|
||||
-0.5, 0.5, 1.0, 1.0,
|
||||
0.0, 0.0, -1.0, 0.0,
|
||||
|
||||
0.5, -0.5, 1.0, 1.0,
|
||||
0.0, 0.0, -1.0, 0.0,
|
||||
|
||||
0.5, 0.5, 1.0, 1.0,
|
||||
0.0, 0.0, -1.0, 0.0,
|
||||
|
||||
-0.5, -0.5, 1.0, 1.0,
|
||||
0.0, 0.0, -1.0, 0.0,
|
||||
|
||||
// BACK
|
||||
0.5, 0.5, 2.0, 1.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
|
||||
-0.5, -0.5, 2.0, 1.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
|
||||
-0.5, 0.5, 2.0, 1.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
|
||||
0.5, 0.5, 2.0, 1.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
|
||||
0.5, -0.5, 2.0, 1.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
|
||||
-0.5, -0.5, 2.0, 1.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
|
||||
// LEFT
|
||||
-0.5, -0.5, 1.0, 1.0,
|
||||
-1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
-0.5, 0.5, 2.0, 1.0,
|
||||
-1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
-0.5, -0.5, 2.0, 1.0,
|
||||
-1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
-0.5, 0.5, 1.0, 1.0,
|
||||
-1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
-0.5, 0.5, 2.0, 1.0,
|
||||
-1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
-0.5, -0.5, 1.0, 1.0,
|
||||
-1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
// RIGHT
|
||||
0.5, 0.5, 2.0, 1.0,
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
0.5, -0.5, 1.0, 1.0,
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
0.5, -0.5, 2.0, 1.0,
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
0.5, 0.5, 2.0, 1.0,
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
0.5, 0.5, 1.0, 1.0,
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
|
||||
0.5, -0.5, 1.0, 1.0,
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
];
|
||||
|
||||
impl WorldComponent for Cube {
|
||||
fn descriptors(&self) -> std::collections::HashMap<vk::DescriptorType, u32> {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1);
|
||||
map.insert(vk::DescriptorType::STORAGE_BUFFER, 1);
|
||||
map
|
||||
}
|
||||
|
||||
fn desc_layout(&self, ctx: &RenderCtx) -> vk::DescriptorSetLayout {
|
||||
let storage_binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(0)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX);
|
||||
let image_binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(1)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::FRAGMENT);
|
||||
let layouts = [storage_binding, image_binding];
|
||||
|
||||
let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts);
|
||||
let layout = unsafe {
|
||||
ctx.dev
|
||||
.create_descriptor_set_layout(&layout_info, None)
|
||||
.expect("Failed to create descriptor set layout")
|
||||
};
|
||||
layout
|
||||
}
|
||||
|
||||
fn write_desc_set(
|
||||
&self,
|
||||
ctx: &RenderCtx,
|
||||
desc_set: vk::DescriptorSet,
|
||||
skybox: (vk::ImageView, vk::Sampler),
|
||||
) {
|
||||
let buf_info = vk::DescriptorBufferInfo::default()
|
||||
.buffer(self.mesh_buf)
|
||||
.offset(0)
|
||||
.range(vk::WHOLE_SIZE);
|
||||
let buf_infos = [buf_info];
|
||||
let buf_desc = vk::WriteDescriptorSet::default()
|
||||
.dst_set(desc_set)
|
||||
.dst_binding(0)
|
||||
.dst_array_element(0)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.buffer_info(&buf_infos);
|
||||
|
||||
let img_info = vk::DescriptorImageInfo::default()
|
||||
.image_layout(vk::ImageLayout::GENERAL)
|
||||
.image_view(skybox.0)
|
||||
.sampler(skybox.1);
|
||||
let img_infos = &[img_info];
|
||||
let img_desc = vk::WriteDescriptorSet::default()
|
||||
.dst_set(desc_set)
|
||||
.dst_binding(1)
|
||||
.dst_array_element(0)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.image_info(img_infos);
|
||||
|
||||
unsafe {
|
||||
ctx.dev.update_descriptor_sets(&[buf_desc, img_desc], &[]);
|
||||
}
|
||||
}
|
||||
|
||||
fn pipeline(&self) -> (vk::Pipeline, vk::PipelineLayout) {
|
||||
(self.pipeline, self.pipe_layout)
|
||||
}
|
||||
|
||||
fn push_constants(
|
||||
&self,
|
||||
camera: &crate::game::Camera,
|
||||
res: [u32; 2],
|
||||
base_color: [f32; 4],
|
||||
) -> Vec<(vk::ShaderStageFlags, u32, Vec<u8>)> {
|
||||
let mut constants = Vec::new();
|
||||
let cam_data: Vec<u8> = (Vec::<f32>::from([
|
||||
camera.origin.x,
|
||||
camera.origin.y,
|
||||
camera.origin.z,
|
||||
0.0,
|
||||
camera.rotation.x,
|
||||
camera.rotation.y,
|
||||
camera.rotation.z,
|
||||
]))
|
||||
.iter()
|
||||
.map(|f| f.to_ne_bytes())
|
||||
.collect::<Vec<_>>()
|
||||
.into_flattened();
|
||||
constants.push((vk::ShaderStageFlags::VERTEX, 0, cam_data));
|
||||
|
||||
let screen_res: Vec<u8> = res
|
||||
.iter()
|
||||
.map(|f| f.to_ne_bytes())
|
||||
.collect::<Vec<_>>()
|
||||
.into_flattened();
|
||||
constants.push((vk::ShaderStageFlags::VERTEX, 32, screen_res));
|
||||
|
||||
let base_color: Vec<u8> = (Vec::<f32>::from([0.0, 1.0, 1.0, 1.0]))
|
||||
.iter()
|
||||
.map(|f| f.to_ne_bytes())
|
||||
.collect::<Vec<_>>()
|
||||
.into_flattened();
|
||||
constants.push((vk::ShaderStageFlags::FRAGMENT, 48, base_color));
|
||||
|
||||
constants
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderCtx {
|
||||
fn create_cube(&self, pass: vk::RenderPass) -> Cube {
|
||||
let mesh_mem = self.mem_alloc(MemType::HostVisibile, MESH_SIZE);
|
||||
|
||||
let mesh_buf = self.alloc_buf(
|
||||
mesh_mem,
|
||||
MESH_SIZE,
|
||||
vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::STORAGE_BUFFER,
|
||||
);
|
||||
|
||||
let mesh_mem = self.buf_to_ptr(mesh_mem, mesh_buf);
|
||||
unsafe {
|
||||
std::ptr::copy(POSITIONS.as_ptr(), mesh_mem as *mut f32, POSITIONS.len());
|
||||
}
|
||||
|
||||
let pipe_layout = setup_pipe_layout(self);
|
||||
let pipeline = setup_pipeline(self, &pass, pipe_layout);
|
||||
|
||||
Cube {
|
||||
mesh_buf,
|
||||
pipeline,
|
||||
pipe_layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Game<LoadingWorld> {
|
||||
pub fn create_cube(&self) -> Cube {
|
||||
let Self {
|
||||
state: LoadingWorld { ctx, pass, .. },
|
||||
} = self;
|
||||
ctx.create_cube(*pass)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_cube_shaders(ctx: &RenderCtx) -> (vk::ShaderModule, vk::ShaderModule) {
|
||||
let shader_vert_shader = ctx.shader_mod_from_file(
|
||||
"shaders/shader_vert.spv",
|
||||
vk::ShaderModuleCreateFlags::empty(),
|
||||
);
|
||||
let shader_frag_shader = ctx.shader_mod_from_file(
|
||||
"shaders/shader_rag.spv",
|
||||
vk::ShaderModuleCreateFlags::empty(),
|
||||
);
|
||||
|
||||
(shader_vert_shader, shader_frag_shader)
|
||||
}
|
||||
|
||||
fn setup_desc_layout(ctx: &RenderCtx) -> vk::DescriptorSetLayout {
|
||||
let storage_binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(0)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX);
|
||||
let image_binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(1)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::FRAGMENT);
|
||||
let layouts = [storage_binding, image_binding];
|
||||
|
||||
let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts);
|
||||
let layout = unsafe {
|
||||
ctx.dev
|
||||
.create_descriptor_set_layout(&layout_info, None)
|
||||
.expect("Failed to create descriptor set layout")
|
||||
};
|
||||
layout
|
||||
}
|
||||
|
||||
fn setup_pipe_layout(ctx: &RenderCtx) -> vk::PipelineLayout {
|
||||
let layout = setup_desc_layout(ctx);
|
||||
let push_range: [vk::PushConstantRange; 2] = [
|
||||
vk::PushConstantRange::default()
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX)
|
||||
.offset(0)
|
||||
.size(48), // vec4 camera_orig, camera_rot; uvec2 screen_res
|
||||
vk::PushConstantRange::default()
|
||||
.stage_flags(vk::ShaderStageFlags::FRAGMENT)
|
||||
.offset(48)
|
||||
.size(16), // vec4 base_color
|
||||
];
|
||||
|
||||
let layouts = &[layout];
|
||||
|
||||
let pipe_layout = vk::PipelineLayoutCreateInfo::default()
|
||||
.set_layouts(layouts.as_ref())
|
||||
.push_constant_ranges(&push_range);
|
||||
let pipe_layout = unsafe {
|
||||
ctx.dev
|
||||
.create_pipeline_layout(&pipe_layout, None)
|
||||
.expect("Failed to create pipeline layout")
|
||||
};
|
||||
pipe_layout
|
||||
}
|
||||
|
||||
fn setup_pipeline(
|
||||
ctx: &RenderCtx,
|
||||
pass: &vk::RenderPass,
|
||||
layout: vk::PipelineLayout,
|
||||
) -> vk::Pipeline {
|
||||
let (vert_shader, frag_shader) = load_cube_shaders(ctx);
|
||||
let shader_stages = ctx.setup_simple_shader_stage(vert_shader, frag_shader);
|
||||
let vert_input = vk::PipelineVertexInputStateCreateInfo::default();
|
||||
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
||||
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
|
||||
let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
|
||||
.polygon_mode(vk::PolygonMode::FILL)
|
||||
.cull_mode(vk::CullModeFlags::BACK)
|
||||
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
|
||||
.line_width(1.0);
|
||||
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
|
||||
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
||||
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
|
||||
.depth_test_enable(true)
|
||||
.depth_write_enable(true)
|
||||
.depth_compare_op(vk::CompareOp::LESS)
|
||||
.depth_bounds_test_enable(true)
|
||||
.stencil_test_enable(false)
|
||||
.min_depth_bounds(0.0)
|
||||
.max_depth_bounds(1.0);
|
||||
|
||||
let blend = vk::PipelineColorBlendAttachmentState::default()
|
||||
.src_color_blend_factor(vk::BlendFactor::SRC_COLOR)
|
||||
.dst_color_blend_factor(vk::BlendFactor::SRC_COLOR)
|
||||
.color_blend_op(vk::BlendOp::ADD)
|
||||
.src_alpha_blend_factor(vk::BlendFactor::SRC_COLOR)
|
||||
.dst_alpha_blend_factor(vk::BlendFactor::SRC_COLOR)
|
||||
.alpha_blend_op(vk::BlendOp::ADD)
|
||||
.color_write_mask(
|
||||
vk::ColorComponentFlags::R
|
||||
| vk::ColorComponentFlags::G
|
||||
| vk::ColorComponentFlags::B
|
||||
| vk::ColorComponentFlags::A,
|
||||
);
|
||||
|
||||
let blend_attachments = [blend];
|
||||
|
||||
let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
|
||||
.attachments(blend_attachments.as_ref())
|
||||
.logic_op_enable(false)
|
||||
.logic_op(vk::LogicOp::COPY)
|
||||
.blend_constants([1.0, 1.0, 1.0, 1.0]);
|
||||
|
||||
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&[
|
||||
vk::DynamicState::VIEWPORT_WITH_COUNT,
|
||||
vk::DynamicState::SCISSOR_WITH_COUNT,
|
||||
]);
|
||||
|
||||
let pipe_info = vk::GraphicsPipelineCreateInfo::default()
|
||||
.stages(&shader_stages)
|
||||
.vertex_input_state(&vert_input)
|
||||
.input_assembly_state(&input_assembly)
|
||||
.rasterization_state(&rasterization)
|
||||
.multisample_state(&multisample)
|
||||
.depth_stencil_state(&depth_stencil)
|
||||
.color_blend_state(&color_blend)
|
||||
.layout(layout)
|
||||
.dynamic_state(&dynamic)
|
||||
.render_pass(*pass)
|
||||
.subpass(0);
|
||||
|
||||
let pipe = unsafe {
|
||||
ctx.dev
|
||||
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipe_info], None)
|
||||
.expect("Failed to create graphics pipeline")
|
||||
.remove(0)
|
||||
};
|
||||
println!("Created pipeline");
|
||||
pipe
|
||||
}
|
@ -0,0 +1,284 @@
|
||||
use ash::vk::{self};
|
||||
|
||||
use crate::{Game, InWorld, RenderCtx, Window};
|
||||
|
||||
use super::{
|
||||
SwapchainCtx,
|
||||
loading_world::{Component, WorldComponent as _},
|
||||
swapchain::{
|
||||
make_depth_img, make_depth_view, make_framebufs, make_swap_images, make_swap_views,
|
||||
setup_swapchain,
|
||||
},
|
||||
};
|
||||
|
||||
impl Game<InWorld> {
|
||||
pub fn window_resize(&mut self, size: (u32, u32)) {
|
||||
let Self {
|
||||
state:
|
||||
InWorld {
|
||||
sdl_context,
|
||||
window,
|
||||
ctx,
|
||||
skybox,
|
||||
components,
|
||||
sem_avail,
|
||||
sem_finish,
|
||||
fence_flight,
|
||||
swapchain:
|
||||
SwapchainCtx {
|
||||
surface_loader,
|
||||
swapchain_loader,
|
||||
swap_images,
|
||||
swap_views,
|
||||
depth_mem,
|
||||
depth_image,
|
||||
depth_view,
|
||||
framebufs,
|
||||
swapchain,
|
||||
},
|
||||
pass,
|
||||
camera,
|
||||
},
|
||||
} = self;
|
||||
|
||||
window.width = size.0;
|
||||
window.height = size.1;
|
||||
|
||||
unsafe {
|
||||
ctx.dev
|
||||
.device_wait_idle()
|
||||
.expect("Failed to wait for device idle");
|
||||
swapchain_loader.destroy_swapchain(*swapchain, None);
|
||||
}
|
||||
(*swapchain, _) = setup_swapchain(
|
||||
&ctx,
|
||||
&surface_loader,
|
||||
&swapchain_loader,
|
||||
window.width,
|
||||
window.height,
|
||||
);
|
||||
*swap_images = make_swap_images(*swapchain, swapchain_loader);
|
||||
*swap_views = make_swap_views(&ctx, &swap_images, vk::Format::B8G8R8A8_UNORM);
|
||||
*depth_image = make_depth_img(&ctx, window.width, window.height, *depth_mem);
|
||||
*depth_view = make_depth_view(&ctx, *depth_image, vk::Format::D32_SFLOAT);
|
||||
*framebufs = make_framebufs(
|
||||
&ctx,
|
||||
&swap_views,
|
||||
&depth_view,
|
||||
&pass,
|
||||
window.width,
|
||||
window.height,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn render_frame(&self) {
|
||||
let Self {
|
||||
state:
|
||||
InWorld {
|
||||
sdl_context,
|
||||
window,
|
||||
ctx,
|
||||
skybox,
|
||||
components,
|
||||
sem_avail,
|
||||
sem_finish,
|
||||
fence_flight,
|
||||
swapchain,
|
||||
pass,
|
||||
camera,
|
||||
},
|
||||
} = self;
|
||||
|
||||
ctx.wait_for_fence(*fence_flight);
|
||||
ctx.reset_fence(*fence_flight);
|
||||
|
||||
let img_idx = ack_next_img(swapchain, sem_avail);
|
||||
|
||||
ctx.start_cmd_buf();
|
||||
|
||||
self.record_commands(img_idx as usize);
|
||||
|
||||
ctx.end_cmd_buf();
|
||||
|
||||
ctx.submit_queue(*sem_avail, *sem_finish, *fence_flight);
|
||||
|
||||
ctx.queue_present(swapchain, img_idx, *sem_finish);
|
||||
}
|
||||
|
||||
fn record_commands(&self, img_idx: usize) {
|
||||
let Self {
|
||||
state:
|
||||
InWorld {
|
||||
sdl_context,
|
||||
window: Window { width, height },
|
||||
ctx: RenderCtx { dev, cmd_buf, .. },
|
||||
skybox,
|
||||
components,
|
||||
swapchain,
|
||||
pass,
|
||||
camera,
|
||||
..
|
||||
},
|
||||
} = self;
|
||||
|
||||
let clear_vals = [
|
||||
vk::ClearValue {
|
||||
color: vk::ClearColorValue {
|
||||
float32: [0.0, 0.0, 0.0, 1.0],
|
||||
},
|
||||
},
|
||||
vk::ClearValue {
|
||||
depth_stencil: vk::ClearDepthStencilValue {
|
||||
depth: 1.0,
|
||||
stencil: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let render_pass_info = vk::RenderPassBeginInfo::default()
|
||||
.render_pass(*pass)
|
||||
.framebuffer(swapchain.framebufs[img_idx])
|
||||
.render_area(
|
||||
vk::Rect2D::default()
|
||||
.offset(vk::Offset2D::default())
|
||||
.extent(vk::Extent2D {
|
||||
width: *width,
|
||||
height: *height,
|
||||
}),
|
||||
)
|
||||
.clear_values(&clear_vals);
|
||||
|
||||
unsafe {
|
||||
let regions = (0..6)
|
||||
.map(|i| {
|
||||
vk::BufferImageCopy::default()
|
||||
.buffer_offset(3 * 2048 * 2048 * i)
|
||||
.buffer_row_length(0)
|
||||
.buffer_image_height(0)
|
||||
.image_offset(vk::Offset3D::default())
|
||||
.image_extent(vk::Extent3D::default().width(2048).height(2048).depth(1))
|
||||
.image_subresource(
|
||||
vk::ImageSubresourceLayers::default()
|
||||
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||
.mip_level(0)
|
||||
.layer_count(1)
|
||||
.base_array_layer(i as u32),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
dev.cmd_copy_buffer_to_image(
|
||||
*cmd_buf,
|
||||
skybox.0.buf,
|
||||
skybox.0.image,
|
||||
vk::ImageLayout::GENERAL,
|
||||
®ions,
|
||||
);
|
||||
dev.cmd_begin_render_pass(*cmd_buf, &render_pass_info, vk::SubpassContents::INLINE);
|
||||
|
||||
let base_color = [0.0, 1.0, 1.0, 1.0];
|
||||
|
||||
for Component { inner, desc_set } in components {
|
||||
let (pipe, layout) = inner.pipeline();
|
||||
dev.cmd_bind_pipeline(*cmd_buf, vk::PipelineBindPoint::GRAPHICS, pipe);
|
||||
|
||||
let desc_sets = [*desc_set];
|
||||
|
||||
dev.cmd_bind_descriptor_sets(
|
||||
*cmd_buf,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
layout,
|
||||
0,
|
||||
desc_sets.as_ref(),
|
||||
&[],
|
||||
);
|
||||
|
||||
dev.cmd_set_viewport_with_count(
|
||||
*cmd_buf,
|
||||
&[vk::Viewport::default()
|
||||
.width(*width as f32)
|
||||
.height(*height as f32)],
|
||||
);
|
||||
dev.cmd_set_scissor_with_count(
|
||||
*cmd_buf,
|
||||
&[vk::Rect2D::default().extent(vk::Extent2D {
|
||||
width: *width,
|
||||
height: *height,
|
||||
})],
|
||||
);
|
||||
|
||||
inner
|
||||
.push_constants(camera, [*width, *height], base_color)
|
||||
.into_iter()
|
||||
.for_each(|(f, o, d)| {
|
||||
dev.cmd_push_constants(
|
||||
*cmd_buf,
|
||||
layout,
|
||||
vk::ShaderStageFlags::VERTEX,
|
||||
0,
|
||||
d.as_ref(),
|
||||
);
|
||||
});
|
||||
|
||||
dev.cmd_draw(*cmd_buf, 36, 1, 0, 0);
|
||||
}
|
||||
|
||||
{
|
||||
let (pipe, layout) = skybox.0.pipeline();
|
||||
dev.cmd_bind_pipeline(*cmd_buf, vk::PipelineBindPoint::GRAPHICS, pipe);
|
||||
|
||||
let desc_sets = [skybox.1];
|
||||
|
||||
dev.cmd_bind_descriptor_sets(
|
||||
*cmd_buf,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
layout,
|
||||
0,
|
||||
desc_sets.as_ref(),
|
||||
&[],
|
||||
);
|
||||
|
||||
dev.cmd_set_viewport_with_count(
|
||||
*cmd_buf,
|
||||
&[vk::Viewport::default()
|
||||
.width(*width as f32)
|
||||
.height(*height as f32)],
|
||||
);
|
||||
dev.cmd_set_scissor_with_count(
|
||||
*cmd_buf,
|
||||
&[vk::Rect2D::default().extent(vk::Extent2D {
|
||||
width: *width,
|
||||
height: *height,
|
||||
})],
|
||||
);
|
||||
|
||||
skybox
|
||||
.0
|
||||
.push_constants(camera, [*width, *height], base_color)
|
||||
.into_iter()
|
||||
.for_each(|(f, o, d)| {
|
||||
dev.cmd_push_constants(
|
||||
*cmd_buf,
|
||||
layout,
|
||||
vk::ShaderStageFlags::VERTEX,
|
||||
0,
|
||||
d.as_ref(),
|
||||
);
|
||||
});
|
||||
|
||||
dev.cmd_draw(*cmd_buf, 36, 1, 0, 0);
|
||||
}
|
||||
|
||||
dev.cmd_end_render_pass(*cmd_buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ack_next_img(swapchain: &SwapchainCtx, sem_avail: &vk::Semaphore) -> u32 {
|
||||
unsafe {
|
||||
let (img_inx, _) = swapchain
|
||||
.swapchain_loader
|
||||
.acquire_next_image(swapchain.swapchain, u64::MAX, *sem_avail, vk::Fence::null())
|
||||
.expect("Failed to acquire next image");
|
||||
img_inx
|
||||
}
|
||||
}
|
@ -0,0 +1,250 @@
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
use ash::{
|
||||
Device, Entry, Instance,
|
||||
khr::surface,
|
||||
vk::{self},
|
||||
};
|
||||
use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
|
||||
use sdl2::{Sdl, video::Window};
|
||||
|
||||
use crate::{APP_NAME, Game, RenderAvailable, RenderCtx, Unstarted};
|
||||
|
||||
impl Game<Unstarted> {
|
||||
pub fn init_render(self, sdl_context: Sdl, window: Window) -> Game<RenderAvailable> {
|
||||
let (width, height) = window.size();
|
||||
|
||||
let entry = Entry::linked();
|
||||
|
||||
let instance = create_instance(&window, &entry);
|
||||
|
||||
let surface = make_surface(&entry, &instance, &window);
|
||||
|
||||
let pdev = find_pdev_dgpu(&instance);
|
||||
|
||||
let queue_idx = find_rcs_queue_inx(&instance, &pdev).expect("No RCS queue found");
|
||||
|
||||
let dev = make_dev(
|
||||
&instance,
|
||||
&pdev,
|
||||
queue_idx,
|
||||
vec![c"VK_KHR_swapchain", c"VK_EXT_extended_dynamic_state3"],
|
||||
);
|
||||
|
||||
let queue = unsafe { dev.get_device_queue(queue_idx, 0) };
|
||||
|
||||
let cmd_pool = make_command_pool(&dev, queue_idx);
|
||||
|
||||
let cmd_buf = alloc_cmd_buf(&dev, cmd_pool);
|
||||
|
||||
let host_vis_idx =
|
||||
find_host_visible(&instance, &pdev).expect("No host visible memory found");
|
||||
|
||||
let host_invis_idx =
|
||||
find_host_invisible(&instance, &pdev).expect("No host invisible memory found");
|
||||
|
||||
let surface_loader = surface::Instance::new(&entry, &instance);
|
||||
|
||||
Game {
|
||||
state: RenderAvailable {
|
||||
sdl_context,
|
||||
window: crate::Window { width, height },
|
||||
ctx: RenderCtx {
|
||||
entry,
|
||||
instance,
|
||||
pdev,
|
||||
dev,
|
||||
surface,
|
||||
surface_loader,
|
||||
queue,
|
||||
queue_idx,
|
||||
cmd_buf,
|
||||
host_vis_idx,
|
||||
host_invis_idx,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_instance(window: &Window, entry: &Entry) -> Instance {
|
||||
let n_app_name = CString::new(APP_NAME).unwrap();
|
||||
let n_engine_name = CString::new(format!("{} Engine", APP_NAME)).unwrap();
|
||||
|
||||
let appinfo = vk::ApplicationInfo::default()
|
||||
.application_name(n_app_name.as_c_str())
|
||||
.application_version(0)
|
||||
.engine_name(n_engine_name.as_c_str())
|
||||
.engine_version(0)
|
||||
.api_version(vk::make_api_version(0, 1, 3, 0));
|
||||
|
||||
let default_exts = window
|
||||
.vulkan_instance_extensions()
|
||||
.expect("Failed to get list of required extensions from SDL2");
|
||||
|
||||
let mut exts = vec![];
|
||||
exts.extend(default_exts);
|
||||
|
||||
let exts: Vec<*const i8> = exts
|
||||
.iter()
|
||||
.map(|s| s.as_ptr() as *const i8)
|
||||
.collect::<Vec<_>>();
|
||||
let exts: &[*const i8] = exts.as_slice();
|
||||
|
||||
let insinfo = vk::InstanceCreateInfo::default()
|
||||
.application_info(&appinfo)
|
||||
.enabled_extension_names(exts);
|
||||
|
||||
let instance =
|
||||
unsafe { entry.create_instance(&insinfo, None) }.expect("Failed to create instance");
|
||||
println!("Created instance");
|
||||
instance
|
||||
}
|
||||
|
||||
fn make_surface(entry: &Entry, instance: &Instance, window: &Window) -> vk::SurfaceKHR {
|
||||
let surface = unsafe {
|
||||
ash_window::create_surface(
|
||||
&entry,
|
||||
&instance,
|
||||
window.display_handle().unwrap().as_raw(),
|
||||
window.window_handle().unwrap().as_raw(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
.expect("Failed to create surface");
|
||||
println!("Created surface");
|
||||
surface
|
||||
}
|
||||
|
||||
fn find_pdev_dgpu(instance: &Instance) -> vk::PhysicalDevice {
|
||||
let mut pdevs =
|
||||
unsafe { instance.enumerate_physical_devices() }.expect("Failed to enumerate devices");
|
||||
for (i, d) in pdevs.iter().enumerate() {
|
||||
let props = unsafe { instance.get_physical_device_properties(*d) };
|
||||
if props.device_type == vk::PhysicalDeviceType::DISCRETE_GPU {
|
||||
// TODO this assumes that the first discrete GPU will have an RCS queue family
|
||||
// This is not guaranteed by the spec (example, a compute-only GPU)
|
||||
// Fix.
|
||||
println!(
|
||||
"Found discrete GPU: {}",
|
||||
unsafe { std::str::from_utf8(std::mem::transmute(props.device_name.as_slice())) }
|
||||
.unwrap()
|
||||
);
|
||||
return pdevs.remove(i);
|
||||
}
|
||||
}
|
||||
println!("No discrete GPU found");
|
||||
assert!(pdevs.len() > 0, "No GPU found");
|
||||
return pdevs.remove(0);
|
||||
}
|
||||
|
||||
// Using Intel GPU hardware terminology 🥴
|
||||
fn find_rcs_queue_inx(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option<u32> {
|
||||
let qfams = unsafe { instance.get_physical_device_queue_family_properties(*pdev) };
|
||||
for (inx, qfam) in qfams.iter().enumerate() {
|
||||
if qfam
|
||||
.queue_flags
|
||||
.contains(vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE)
|
||||
{
|
||||
println!("Found RCS queue at index {}", inx);
|
||||
return Some(inx as u32);
|
||||
}
|
||||
}
|
||||
println!("No RCS queue found");
|
||||
return None;
|
||||
}
|
||||
|
||||
fn make_dev(
|
||||
instance: &Instance,
|
||||
pdev: &vk::PhysicalDevice,
|
||||
rcs_queue_inx: u32,
|
||||
exts: Vec<&CStr>,
|
||||
) -> Device {
|
||||
unsafe {
|
||||
let dev_queue = vec![
|
||||
vk::DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(rcs_queue_inx)
|
||||
.queue_priorities(&[1.0]),
|
||||
];
|
||||
|
||||
let exts = exts.iter().map(|&s| s.as_ptr()).collect::<Vec<_>>();
|
||||
let exts = exts.as_slice();
|
||||
|
||||
// Enable all features
|
||||
let features = instance.get_physical_device_features(*pdev);
|
||||
|
||||
let dev_info = vk::DeviceCreateInfo::default()
|
||||
.queue_create_infos(&dev_queue)
|
||||
.enabled_extension_names(exts)
|
||||
.enabled_features(&features);
|
||||
|
||||
let dev = instance
|
||||
.create_device(*pdev, &dev_info, None)
|
||||
.expect("Failed to create device");
|
||||
println!("Created device");
|
||||
dev
|
||||
}
|
||||
}
|
||||
|
||||
fn make_command_pool(dev: &Device, rcs_queue_inx: u32) -> vk::CommandPool {
|
||||
unsafe {
|
||||
let pool_info = vk::CommandPoolCreateInfo::default()
|
||||
.queue_family_index(rcs_queue_inx)
|
||||
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
|
||||
let pool = dev
|
||||
.create_command_pool(&pool_info, None)
|
||||
.expect("Failed to create command pool");
|
||||
println!("Created command pool");
|
||||
pool
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_cmd_buf(dev: &Device, pool: vk::CommandPool) -> vk::CommandBuffer {
|
||||
unsafe {
|
||||
let alloc_info = vk::CommandBufferAllocateInfo::default()
|
||||
.command_pool(pool)
|
||||
.level(vk::CommandBufferLevel::PRIMARY)
|
||||
.command_buffer_count(1);
|
||||
let cmd_buf = dev
|
||||
.allocate_command_buffers(&alloc_info)
|
||||
.expect("Failed to allocate command buffer")
|
||||
.remove(0);
|
||||
println!("Allocated command buffer");
|
||||
cmd_buf
|
||||
}
|
||||
}
|
||||
|
||||
fn find_host_visible(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option<u32> {
|
||||
let mem_props = unsafe { instance.get_physical_device_memory_properties(*pdev) };
|
||||
|
||||
for (i, mem_type) in mem_props.memory_types.iter().enumerate() {
|
||||
if mem_type.property_flags.contains(
|
||||
vk::MemoryPropertyFlags::DEVICE_LOCAL
|
||||
| vk::MemoryPropertyFlags::HOST_VISIBLE
|
||||
| vk::MemoryPropertyFlags::HOST_COHERENT,
|
||||
) {
|
||||
println!("Found host visible memory at index {}", i);
|
||||
return Some(i as u32);
|
||||
}
|
||||
}
|
||||
println!("No host visible memory found");
|
||||
return None;
|
||||
}
|
||||
|
||||
fn find_host_invisible(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option<u32> {
|
||||
let mem_props = unsafe { instance.get_physical_device_memory_properties(*pdev) };
|
||||
for (i, mem_type) in mem_props.memory_types.iter().enumerate() {
|
||||
if mem_type
|
||||
.property_flags
|
||||
.contains(vk::MemoryPropertyFlags::DEVICE_LOCAL)
|
||||
&& !mem_type.property_flags.contains(
|
||||
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
|
||||
)
|
||||
{
|
||||
println!("Found host invisible memory at index {}", i);
|
||||
return Some(i as u32);
|
||||
}
|
||||
}
|
||||
println!("No host invisible memory found");
|
||||
return None;
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
use std::{collections::HashMap, iter};
|
||||
|
||||
use ash::{
|
||||
khr::{surface, swapchain},
|
||||
vk::{self, DescriptorSet},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Game, InWorld, LoadingWorld, RenderAvailable, RenderCtx, game::Camera, render::MemType,
|
||||
};
|
||||
|
||||
use super::{
|
||||
MAX_HEIGHT, MAX_WIDTH, SwapchainCtx,
|
||||
swapchain::{
|
||||
make_depth_img, make_depth_view, make_framebufs, make_swap_images, make_swap_views,
|
||||
setup_swapchain,
|
||||
},
|
||||
};
|
||||
|
||||
/// Anything can be rendered in world
|
||||
pub trait WorldComponent {
|
||||
fn descriptors(&self) -> HashMap<vk::DescriptorType, u32>;
|
||||
|
||||
fn desc_layout(&self, ctx: &RenderCtx) -> vk::DescriptorSetLayout;
|
||||
|
||||
/// Skybox is passed in for reflections
|
||||
fn write_desc_set(
|
||||
&self,
|
||||
ctx: &RenderCtx,
|
||||
desc_set: vk::DescriptorSet,
|
||||
skybox: (vk::ImageView, vk::Sampler),
|
||||
);
|
||||
|
||||
fn pipeline(&self) -> (vk::Pipeline, vk::PipelineLayout);
|
||||
|
||||
fn push_constants(
|
||||
&self,
|
||||
camera: &Camera,
|
||||
res: [u32; 2],
|
||||
base_color: [f32; 4],
|
||||
) -> Vec<(vk::ShaderStageFlags, u32, Vec<u8>)>;
|
||||
}
|
||||
|
||||
pub struct Component {
|
||||
pub inner: Box<dyn WorldComponent>,
|
||||
pub desc_set: DescriptorSet,
|
||||
}
|
||||
|
||||
impl Game<RenderAvailable> {
|
||||
pub fn start_load_world(self) -> Game<LoadingWorld> {
|
||||
let Self {
|
||||
state:
|
||||
RenderAvailable {
|
||||
sdl_context,
|
||||
ctx,
|
||||
window,
|
||||
},
|
||||
} = self;
|
||||
|
||||
let depth_mem = ctx.mem_alloc(MemType::HostInvisible, (MAX_WIDTH * MAX_HEIGHT * 8) as u64);
|
||||
|
||||
let pass = setup_render_pass(&ctx);
|
||||
|
||||
let surface_loader = surface::Instance::new(&ctx.entry, &ctx.instance);
|
||||
let swapchain_loader = swapchain::Device::new(&ctx.instance, &ctx.dev);
|
||||
|
||||
let (swapchain, extents) = setup_swapchain(
|
||||
&ctx,
|
||||
&surface_loader,
|
||||
&swapchain_loader,
|
||||
window.width,
|
||||
window.height,
|
||||
);
|
||||
|
||||
let depth_img = make_depth_img(&ctx, window.width, window.height, depth_mem);
|
||||
let swap_images = make_swap_images(swapchain, &swapchain_loader);
|
||||
let swap_views = make_swap_views(&ctx, &swap_images, vk::Format::B8G8R8A8_UNORM);
|
||||
let depth_view = make_depth_view(&ctx, depth_img, vk::Format::D32_SFLOAT);
|
||||
let framebufs = make_framebufs(
|
||||
&ctx,
|
||||
&swap_views,
|
||||
&depth_view,
|
||||
&pass,
|
||||
window.width,
|
||||
window.height,
|
||||
);
|
||||
|
||||
let skybox = ctx.create_skybox(pass);
|
||||
|
||||
let requested_descriptors = skybox.descriptors();
|
||||
|
||||
Game {
|
||||
state: LoadingWorld {
|
||||
sdl_context,
|
||||
ctx,
|
||||
window,
|
||||
|
||||
requested_descriptors,
|
||||
|
||||
swapchain: SwapchainCtx {
|
||||
surface_loader,
|
||||
swapchain_loader,
|
||||
swap_images,
|
||||
swap_views,
|
||||
depth_mem,
|
||||
depth_image: depth_img,
|
||||
depth_view,
|
||||
framebufs,
|
||||
swapchain,
|
||||
},
|
||||
pass,
|
||||
|
||||
skybox,
|
||||
components: Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Game<LoadingWorld> {
|
||||
pub fn add_component<C: WorldComponent + 'static>(&mut self, component: C) {
|
||||
let Self {
|
||||
state:
|
||||
LoadingWorld {
|
||||
requested_descriptors,
|
||||
components,
|
||||
..
|
||||
},
|
||||
} = self;
|
||||
for (desc, n) in component.descriptors() {
|
||||
match requested_descriptors.remove(&desc) {
|
||||
Some(m) => requested_descriptors.insert(desc, n + m),
|
||||
None => requested_descriptors.insert(desc, n),
|
||||
};
|
||||
}
|
||||
components.push(Box::new(component));
|
||||
}
|
||||
|
||||
pub fn start_world(self) -> Game<InWorld> {
|
||||
let Self {
|
||||
state:
|
||||
LoadingWorld {
|
||||
sdl_context,
|
||||
window,
|
||||
ctx,
|
||||
|
||||
requested_descriptors,
|
||||
|
||||
swapchain,
|
||||
pass,
|
||||
|
||||
skybox,
|
||||
components,
|
||||
},
|
||||
} = self;
|
||||
let desc_pool = setup_desc_pool(&ctx, requested_descriptors);
|
||||
|
||||
let mut desc_sets = make_desc_sets(
|
||||
&ctx,
|
||||
&desc_pool,
|
||||
&components
|
||||
.iter()
|
||||
.map(|c| c.desc_layout(&ctx))
|
||||
.chain(iter::once(skybox.desc_layout(&ctx)))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let skybox_descriptor = desc_sets.pop().unwrap();
|
||||
|
||||
let components = components
|
||||
.into_iter()
|
||||
.zip(desc_sets.into_iter())
|
||||
.map(|(c, d)| Component {
|
||||
inner: c,
|
||||
desc_set: d,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for comp in &components {
|
||||
let Component { inner, desc_set } = comp;
|
||||
inner.write_desc_set(&ctx, *desc_set, (skybox.imageview, skybox.sampler));
|
||||
}
|
||||
|
||||
skybox.write_desc_set(&ctx, skybox_descriptor, (skybox.imageview, skybox.sampler));
|
||||
|
||||
let sem_avail = ctx.make_sem();
|
||||
let sem_finish = ctx.make_sem();
|
||||
let fence_flight = ctx.make_fence();
|
||||
|
||||
Game {
|
||||
state: InWorld {
|
||||
sdl_context,
|
||||
window,
|
||||
ctx,
|
||||
|
||||
skybox: (skybox, skybox_descriptor),
|
||||
components,
|
||||
|
||||
sem_avail,
|
||||
sem_finish,
|
||||
fence_flight,
|
||||
|
||||
swapchain,
|
||||
pass,
|
||||
|
||||
camera: Camera::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_render_pass(ctx: &RenderCtx) -> vk::RenderPass {
|
||||
let color_attach = vk::AttachmentDescription::default()
|
||||
.format(vk::Format::B8G8R8A8_UNORM)
|
||||
.samples(vk::SampleCountFlags::TYPE_1)
|
||||
.load_op(vk::AttachmentLoadOp::CLEAR)
|
||||
.store_op(vk::AttachmentStoreOp::STORE)
|
||||
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
|
||||
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED)
|
||||
.final_layout(vk::ImageLayout::PRESENT_SRC_KHR);
|
||||
let depth_attach = vk::AttachmentDescription::default()
|
||||
.format(vk::Format::D32_SFLOAT)
|
||||
.samples(vk::SampleCountFlags::TYPE_1)
|
||||
.load_op(vk::AttachmentLoadOp::CLEAR)
|
||||
.store_op(vk::AttachmentStoreOp::DONT_CARE)
|
||||
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
|
||||
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED)
|
||||
.final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
|
||||
let color_ref = vk::AttachmentReference::default()
|
||||
.attachment(0)
|
||||
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
|
||||
|
||||
let depth_ref = vk::AttachmentReference::default()
|
||||
.attachment(1)
|
||||
.layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
|
||||
let color_refs = [color_ref];
|
||||
let depth_refs = [depth_ref];
|
||||
|
||||
let subpass = vk::SubpassDescription::default()
|
||||
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
|
||||
.color_attachments(color_refs.as_ref())
|
||||
.depth_stencil_attachment(&depth_ref);
|
||||
|
||||
let subpass_dep = vk::SubpassDependency::default()
|
||||
.src_subpass(vk::SUBPASS_EXTERNAL)
|
||||
.dst_subpass(0)
|
||||
.src_stage_mask(
|
||||
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
|
||||
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
|
||||
)
|
||||
.dst_stage_mask(
|
||||
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
|
||||
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
|
||||
)
|
||||
.src_access_mask(vk::AccessFlags::empty())
|
||||
.dst_access_mask(
|
||||
vk::AccessFlags::COLOR_ATTACHMENT_WRITE
|
||||
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
|
||||
);
|
||||
|
||||
let attach_desc = [color_attach, depth_attach];
|
||||
|
||||
let subpasses = [subpass];
|
||||
|
||||
let deps = [subpass_dep];
|
||||
|
||||
let pass_info = vk::RenderPassCreateInfo::default()
|
||||
.attachments(&attach_desc)
|
||||
.subpasses(subpasses.as_ref())
|
||||
.dependencies(deps.as_ref());
|
||||
|
||||
let pass = unsafe {
|
||||
ctx.dev
|
||||
.create_render_pass(&pass_info, None)
|
||||
.expect("Failed to create render pass")
|
||||
};
|
||||
pass
|
||||
}
|
||||
|
||||
fn setup_desc_pool(
|
||||
ctx: &RenderCtx,
|
||||
requested_descriptors: HashMap<vk::DescriptorType, u32>,
|
||||
) -> vk::DescriptorPool {
|
||||
let pool_sizes = requested_descriptors
|
||||
.into_iter()
|
||||
.map(|(desc, count)| {
|
||||
vk::DescriptorPoolSize::default()
|
||||
.ty(desc)
|
||||
.descriptor_count(count)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let pool_info = vk::DescriptorPoolCreateInfo::default()
|
||||
.max_sets(1)
|
||||
.pool_sizes(&pool_sizes);
|
||||
let pool = unsafe {
|
||||
ctx.dev
|
||||
.create_descriptor_pool(&pool_info, None)
|
||||
.expect("Failed to create descriptor pool")
|
||||
};
|
||||
pool
|
||||
}
|
||||
|
||||
fn make_desc_sets(
|
||||
ctx: &RenderCtx,
|
||||
desc_pool: &vk::DescriptorPool,
|
||||
layouts: &[vk::DescriptorSetLayout],
|
||||
) -> Vec<vk::DescriptorSet> {
|
||||
let alloc_info = vk::DescriptorSetAllocateInfo::default()
|
||||
.descriptor_pool(*desc_pool)
|
||||
.set_layouts(&layouts);
|
||||
let sets = unsafe {
|
||||
ctx.dev
|
||||
.allocate_descriptor_sets(&alloc_info)
|
||||
.expect("Failed to allocate descriptor sets")
|
||||
};
|
||||
sets
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
use ash::{
|
||||
khr::{surface, swapchain as khr_swapchain},
|
||||
vk,
|
||||
};
|
||||
|
||||
use crate::RenderCtx;
|
||||
|
||||
mod cube;
|
||||
pub mod in_world;
|
||||
mod init;
|
||||
pub mod loading_world;
|
||||
pub mod skybox;
|
||||
mod swapchain;
|
||||
|
||||
const MAX_WIDTH: u32 = 3440;
|
||||
const MAX_HEIGHT: u32 = 1440;
|
||||
|
||||
pub struct SwapchainCtx {
|
||||
surface_loader: surface::Instance,
|
||||
swapchain_loader: khr_swapchain::Device,
|
||||
swap_images: Vec<vk::Image>,
|
||||
swap_views: Vec<vk::ImageView>,
|
||||
depth_mem: vk::DeviceMemory,
|
||||
depth_image: vk::Image,
|
||||
depth_view: vk::ImageView,
|
||||
framebufs: Vec<vk::Framebuffer>,
|
||||
swapchain: vk::SwapchainKHR,
|
||||
}
|
||||
|
||||
pub enum MemType {
|
||||
HostVisibile,
|
||||
HostInvisible,
|
||||
}
|
||||
|
||||
impl RenderCtx {
|
||||
pub fn mem_alloc(&self, typ: MemType, size: u64) -> vk::DeviceMemory {
|
||||
let mem_info = vk::MemoryAllocateInfo::default()
|
||||
.allocation_size(size)
|
||||
.memory_type_index(match typ {
|
||||
MemType::HostVisibile => self.host_vis_idx,
|
||||
MemType::HostInvisible => self.host_invis_idx,
|
||||
});
|
||||
let mem = unsafe { self.dev.allocate_memory(&mem_info, None) }
|
||||
.expect("Failed to allocate memory");
|
||||
println!("Allocated memory");
|
||||
mem
|
||||
}
|
||||
|
||||
pub fn alloc_buf(
|
||||
&self,
|
||||
mem: vk::DeviceMemory,
|
||||
size: vk::DeviceSize,
|
||||
usage: vk::BufferUsageFlags,
|
||||
) -> vk::Buffer {
|
||||
unsafe {
|
||||
let buf_info = vk::BufferCreateInfo::default()
|
||||
.size(size)
|
||||
.usage(usage)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||
let buf = self
|
||||
.dev
|
||||
.create_buffer(&buf_info, None)
|
||||
.expect("Failed to create buffer");
|
||||
|
||||
self.dev
|
||||
.bind_buffer_memory(buf, mem, 0)
|
||||
.expect("Failed to bind buffer memory");
|
||||
println!("Created buffer");
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buf_to_ptr(&self, mem: vk::DeviceMemory, buf: vk::Buffer) -> *mut std::ffi::c_void {
|
||||
let ptr = unsafe {
|
||||
self.dev
|
||||
.map_memory(mem, 0, vk::WHOLE_SIZE, vk::MemoryMapFlags::empty())
|
||||
}
|
||||
.expect("Failed to map memory");
|
||||
println!("Mapped memory");
|
||||
ptr
|
||||
}
|
||||
|
||||
pub fn shader_mod_from_file(
|
||||
&self,
|
||||
file: &str,
|
||||
flags: vk::ShaderModuleCreateFlags,
|
||||
) -> vk::ShaderModule {
|
||||
let shader_bin = std::fs::read(file).unwrap();
|
||||
|
||||
unsafe {
|
||||
self.dev.create_shader_module(
|
||||
&vk::ShaderModuleCreateInfo {
|
||||
code_size: shader_bin.len(),
|
||||
p_code: shader_bin.as_ptr() as *const u32,
|
||||
flags,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
.expect(&format!("Failed to create shader module form file {file}"))
|
||||
}
|
||||
|
||||
fn setup_simple_shader_stage(
|
||||
&self,
|
||||
vert_shader: vk::ShaderModule,
|
||||
frag_shader: vk::ShaderModule,
|
||||
) -> [vk::PipelineShaderStageCreateInfo; 2] {
|
||||
let vert_stage = vk::PipelineShaderStageCreateInfo::default()
|
||||
.stage(vk::ShaderStageFlags::VERTEX)
|
||||
.module(vert_shader)
|
||||
.name(c"main");
|
||||
let frag_stage = vk::PipelineShaderStageCreateInfo::default()
|
||||
.stage(vk::ShaderStageFlags::FRAGMENT)
|
||||
.module(frag_shader)
|
||||
.name(c"main");
|
||||
[vert_stage, frag_stage]
|
||||
}
|
||||
|
||||
fn make_sem(&self) -> vk::Semaphore {
|
||||
let sem_info = vk::SemaphoreCreateInfo::default();
|
||||
let sem = unsafe {
|
||||
self.dev
|
||||
.create_semaphore(&sem_info, None)
|
||||
.expect("Failed to create semaphore")
|
||||
};
|
||||
sem
|
||||
}
|
||||
|
||||
fn make_fence(&self) -> vk::Fence {
|
||||
let fence_info = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED);
|
||||
let fence = unsafe {
|
||||
self.dev
|
||||
.create_fence(&fence_info, None)
|
||||
.expect("Failed to create fence")
|
||||
};
|
||||
fence
|
||||
}
|
||||
|
||||
fn wait_for_fence(&self, fence: vk::Fence) {
|
||||
unsafe {
|
||||
let fence = [fence];
|
||||
self.dev
|
||||
.wait_for_fences(&fence, true, u64::MAX)
|
||||
.expect("Failed to wait for fence");
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_fence(&self, fence: vk::Fence) {
|
||||
unsafe {
|
||||
let fence = [fence];
|
||||
self.dev
|
||||
.reset_fences(&fence)
|
||||
.expect("Failed to reset fence");
|
||||
}
|
||||
}
|
||||
|
||||
fn start_cmd_buf(&self) {
|
||||
unsafe {
|
||||
self.dev
|
||||
.reset_command_buffer(self.cmd_buf, vk::CommandBufferResetFlags::empty())
|
||||
.expect("Failed to reset command buffer");
|
||||
let begin_info =
|
||||
vk::CommandBufferBeginInfo::default().flags(vk::CommandBufferUsageFlags::empty());
|
||||
self.dev
|
||||
.begin_command_buffer(self.cmd_buf, &begin_info)
|
||||
.expect("Failed to begin command buffer");
|
||||
}
|
||||
}
|
||||
|
||||
fn end_cmd_buf(&self) {
|
||||
unsafe {
|
||||
self.dev
|
||||
.end_command_buffer(self.cmd_buf)
|
||||
.expect("Failed to end command buffer");
|
||||
}
|
||||
}
|
||||
|
||||
fn submit_queue(&self, sem_avail: vk::Semaphore, sem_finish: vk::Semaphore, fence: vk::Fence) {
|
||||
let cmd_bufs = [self.cmd_buf];
|
||||
let wait_sems = [sem_avail];
|
||||
let signal_sems = [sem_finish];
|
||||
let submit_info = vk::SubmitInfo::default()
|
||||
.command_buffers(&cmd_bufs)
|
||||
.wait_semaphores(&wait_sems)
|
||||
.wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT])
|
||||
.signal_semaphores(&signal_sems);
|
||||
unsafe {
|
||||
self.dev
|
||||
.queue_submit(self.queue, &[submit_info], fence)
|
||||
.expect("Failed to submit queue");
|
||||
}
|
||||
}
|
||||
|
||||
fn queue_present(&self, swapchain: &SwapchainCtx, img_inx: u32, sem_finish: vk::Semaphore) {
|
||||
let swapchains = [swapchain.swapchain];
|
||||
let img_inxs = [img_inx];
|
||||
let wait_sems = [sem_finish];
|
||||
let present_info = vk::PresentInfoKHR::default()
|
||||
.swapchains(&swapchains)
|
||||
.image_indices(&img_inxs)
|
||||
.wait_semaphores(&wait_sems);
|
||||
unsafe {
|
||||
swapchain
|
||||
.swapchain_loader
|
||||
.queue_present(self.queue, &present_info)
|
||||
.expect("Failed to present queue");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,318 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ash::vk::{self};
|
||||
|
||||
use crate::{RenderCtx, decode_rif, game::Camera};
|
||||
|
||||
use super::{MemType, loading_world::WorldComponent};
|
||||
|
||||
pub struct Skybox {
|
||||
pub buf: vk::Buffer,
|
||||
pub image: vk::Image,
|
||||
pub imageview: vk::ImageView,
|
||||
pub sampler: vk::Sampler,
|
||||
pipeline: vk::Pipeline,
|
||||
pipe_layout: vk::PipelineLayout,
|
||||
}
|
||||
|
||||
impl WorldComponent for Skybox {
|
||||
fn descriptors(&self) -> HashMap<vk::DescriptorType, u32> {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1);
|
||||
map
|
||||
}
|
||||
|
||||
fn desc_layout(&self, ctx: &RenderCtx) -> vk::DescriptorSetLayout {
|
||||
let image_binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(0)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::FRAGMENT);
|
||||
let layouts = [image_binding];
|
||||
|
||||
let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts);
|
||||
let layout = unsafe {
|
||||
ctx.dev
|
||||
.create_descriptor_set_layout(&layout_info, None)
|
||||
.expect("Failed to create descriptor set layout")
|
||||
};
|
||||
layout
|
||||
}
|
||||
|
||||
fn write_desc_set(
|
||||
&self,
|
||||
ctx: &RenderCtx,
|
||||
desc_set: vk::DescriptorSet,
|
||||
_: (vk::ImageView, vk::Sampler),
|
||||
) {
|
||||
let img_info = vk::DescriptorImageInfo::default()
|
||||
.image_layout(vk::ImageLayout::GENERAL)
|
||||
.image_view(self.imageview)
|
||||
.sampler(self.sampler);
|
||||
let img_infos = &[img_info];
|
||||
let img_desc = vk::WriteDescriptorSet::default()
|
||||
.dst_set(desc_set)
|
||||
.dst_binding(0)
|
||||
.dst_array_element(0)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.image_info(img_infos);
|
||||
|
||||
unsafe {
|
||||
ctx.dev.update_descriptor_sets(&[img_desc], &[]);
|
||||
}
|
||||
}
|
||||
|
||||
fn pipeline(&self) -> (vk::Pipeline, vk::PipelineLayout) {
|
||||
(self.pipeline, self.pipe_layout)
|
||||
}
|
||||
|
||||
fn push_constants(
|
||||
&self,
|
||||
camera: &Camera,
|
||||
res: [u32; 2],
|
||||
base_color: [f32; 4],
|
||||
) -> Vec<(vk::ShaderStageFlags, u32, Vec<u8>)> {
|
||||
let mut constants = Vec::new();
|
||||
let cam_data: Vec<u8> = (Vec::<f32>::from([
|
||||
camera.origin.x,
|
||||
camera.origin.y,
|
||||
camera.origin.z,
|
||||
0.0,
|
||||
camera.rotation.x,
|
||||
camera.rotation.y,
|
||||
camera.rotation.z,
|
||||
]))
|
||||
.iter()
|
||||
.map(|f| f.to_ne_bytes())
|
||||
.collect::<Vec<_>>()
|
||||
.into_flattened();
|
||||
constants.push((vk::ShaderStageFlags::VERTEX, 0, cam_data));
|
||||
|
||||
let screen_res: Vec<u8> = res
|
||||
.iter()
|
||||
.map(|f| f.to_ne_bytes())
|
||||
.collect::<Vec<_>>()
|
||||
.into_flattened();
|
||||
constants.push((vk::ShaderStageFlags::VERTEX, 32, screen_res));
|
||||
|
||||
constants
|
||||
}
|
||||
}
|
||||
|
||||
impl Skybox {}
|
||||
|
||||
impl RenderCtx {
|
||||
pub(super) fn create_skybox(&self, pass: vk::RenderPass) -> Skybox {
|
||||
let skybox_mem = self.mem_alloc(MemType::HostVisibile, 3 * 2048 * 2048 * 6);
|
||||
let skybox_buf = self.alloc_buf(
|
||||
skybox_mem,
|
||||
3 * 2048 * 2048 * 6,
|
||||
vk::BufferUsageFlags::TRANSFER_SRC,
|
||||
);
|
||||
let skybox_ptr = self.buf_to_ptr(skybox_mem, skybox_buf);
|
||||
let img = std::fs::read("resources/skybox.rif").unwrap();
|
||||
let data = decode_rif(&img);
|
||||
unsafe {
|
||||
std::ptr::copy(data.as_ptr(), skybox_ptr as *mut u8, data.len());
|
||||
}
|
||||
|
||||
let image = make_skybox_image(self);
|
||||
let imageview = make_skybox_image_view(self, image);
|
||||
let sampler = make_skybox_sampler(self);
|
||||
|
||||
let pipe_layout = setup_pipe_layout(self);
|
||||
let pipeline = setup_pipeline(self, pass, pipe_layout);
|
||||
|
||||
Skybox {
|
||||
buf: skybox_buf,
|
||||
image,
|
||||
imageview,
|
||||
sampler,
|
||||
pipeline,
|
||||
pipe_layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_skybox_image(ctx: &RenderCtx) -> vk::Image {
|
||||
let qf_idxs = [ctx.queue_idx];
|
||||
let create_info = vk::ImageCreateInfo::default()
|
||||
.flags(vk::ImageCreateFlags::CUBE_COMPATIBLE)
|
||||
.image_type(vk::ImageType::TYPE_2D)
|
||||
.format(vk::Format::R8G8B8_UNORM)
|
||||
.extent(vk::Extent3D::default().width(2048).height(2048).depth(1))
|
||||
.mip_levels(1)
|
||||
.array_layers(6)
|
||||
.samples(vk::SampleCountFlags::TYPE_1)
|
||||
.tiling(vk::ImageTiling::OPTIMAL)
|
||||
.usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED)
|
||||
.queue_family_indices(&qf_idxs);
|
||||
let image =
|
||||
unsafe { ctx.dev.create_image(&create_info, None) }.expect("Failed to create image");
|
||||
|
||||
let skybox_mem_size: vk::MemoryRequirements =
|
||||
unsafe { ctx.dev.get_image_memory_requirements(image) };
|
||||
let skybox_dev_mem = ctx.mem_alloc(MemType::HostInvisible, skybox_mem_size.size);
|
||||
unsafe { ctx.dev.bind_image_memory(image, skybox_dev_mem, 0) }
|
||||
.expect("Failed to bind image memory");
|
||||
|
||||
image
|
||||
}
|
||||
|
||||
fn make_skybox_image_view(ctx: &RenderCtx, image: vk::Image) -> vk::ImageView {
|
||||
let create_info = vk::ImageViewCreateInfo::default()
|
||||
.image(image)
|
||||
.view_type(vk::ImageViewType::CUBE)
|
||||
.format(vk::Format::R8G8B8_UNORM)
|
||||
.components(vk::ComponentMapping::default())
|
||||
.subresource_range(
|
||||
vk::ImageSubresourceRange::default()
|
||||
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||
.base_mip_level(0)
|
||||
.level_count(1)
|
||||
.base_array_layer(0)
|
||||
.layer_count(6),
|
||||
);
|
||||
unsafe { ctx.dev.create_image_view(&create_info, None) }.expect("Failed to create image view")
|
||||
}
|
||||
|
||||
fn make_skybox_sampler(ctx: &RenderCtx) -> vk::Sampler {
|
||||
let create_info = vk::SamplerCreateInfo::default()
|
||||
.mag_filter(vk::Filter::LINEAR)
|
||||
.min_filter(vk::Filter::LINEAR)
|
||||
.mipmap_mode(vk::SamplerMipmapMode::LINEAR)
|
||||
.address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
|
||||
.address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
|
||||
.address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE)
|
||||
.max_anisotropy(1.0)
|
||||
.max_lod(1.0)
|
||||
.border_color(vk::BorderColor::FLOAT_OPAQUE_WHITE);
|
||||
unsafe { ctx.dev.create_sampler(&create_info, None) }.expect("Failed to create sampler")
|
||||
}
|
||||
|
||||
fn load_skybox_shaders(ctx: &RenderCtx) -> (vk::ShaderModule, vk::ShaderModule) {
|
||||
let skybox_vert_shader = ctx.shader_mod_from_file(
|
||||
"shaders/skybox_vert.spv",
|
||||
vk::ShaderModuleCreateFlags::empty(),
|
||||
);
|
||||
let skybox_frag_shader = ctx.shader_mod_from_file(
|
||||
"shaders/skybox_rag.spv",
|
||||
vk::ShaderModuleCreateFlags::empty(),
|
||||
);
|
||||
|
||||
(skybox_vert_shader, skybox_frag_shader)
|
||||
}
|
||||
|
||||
fn setup_desc_layout(ctx: &RenderCtx) -> vk::DescriptorSetLayout {
|
||||
let image_binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(0)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::FRAGMENT);
|
||||
let layouts = [image_binding];
|
||||
|
||||
let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts);
|
||||
let layout = unsafe { ctx.dev.create_descriptor_set_layout(&layout_info, None) }
|
||||
.expect("Failed to create descriptor set layout");
|
||||
layout
|
||||
}
|
||||
|
||||
fn setup_pipe_layout(ctx: &RenderCtx) -> vk::PipelineLayout {
|
||||
let layout = setup_desc_layout(ctx);
|
||||
let push_range: [vk::PushConstantRange; 1] = [
|
||||
vk::PushConstantRange::default()
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX)
|
||||
.offset(0)
|
||||
.size(48), // vec4 camera_orig, camera_rot; uvec2 screen_res
|
||||
];
|
||||
|
||||
let layouts = &[layout];
|
||||
|
||||
let pipe_layout = vk::PipelineLayoutCreateInfo::default()
|
||||
.set_layouts(layouts.as_ref())
|
||||
.push_constant_ranges(&push_range);
|
||||
let pipe_layout = unsafe {
|
||||
ctx.dev
|
||||
.create_pipeline_layout(&pipe_layout, None)
|
||||
.expect("Failed to create pipeline layout")
|
||||
};
|
||||
pipe_layout
|
||||
}
|
||||
|
||||
fn setup_pipeline(
|
||||
ctx: &RenderCtx,
|
||||
pass: vk::RenderPass,
|
||||
layout: vk::PipelineLayout,
|
||||
) -> vk::Pipeline {
|
||||
let (vert_shader, frag_shader) = load_skybox_shaders(ctx);
|
||||
let shader_stages = ctx.setup_simple_shader_stage(vert_shader, frag_shader);
|
||||
let vert_input = vk::PipelineVertexInputStateCreateInfo::default();
|
||||
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
||||
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
|
||||
let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
|
||||
.polygon_mode(vk::PolygonMode::FILL)
|
||||
.cull_mode(vk::CullModeFlags::NONE)
|
||||
.front_face(vk::FrontFace::CLOCKWISE)
|
||||
.line_width(1.0);
|
||||
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
|
||||
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
||||
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
|
||||
.depth_test_enable(true)
|
||||
.depth_write_enable(true)
|
||||
.depth_compare_op(vk::CompareOp::LESS)
|
||||
.depth_bounds_test_enable(true)
|
||||
.stencil_test_enable(false)
|
||||
.min_depth_bounds(0.0)
|
||||
.max_depth_bounds(1.0);
|
||||
|
||||
let blend = vk::PipelineColorBlendAttachmentState::default()
|
||||
.src_color_blend_factor(vk::BlendFactor::SRC_COLOR)
|
||||
.dst_color_blend_factor(vk::BlendFactor::SRC_COLOR)
|
||||
.color_blend_op(vk::BlendOp::ADD)
|
||||
.src_alpha_blend_factor(vk::BlendFactor::SRC_COLOR)
|
||||
.dst_alpha_blend_factor(vk::BlendFactor::SRC_COLOR)
|
||||
.alpha_blend_op(vk::BlendOp::ADD)
|
||||
.color_write_mask(
|
||||
vk::ColorComponentFlags::R
|
||||
| vk::ColorComponentFlags::G
|
||||
| vk::ColorComponentFlags::B
|
||||
| vk::ColorComponentFlags::A,
|
||||
);
|
||||
|
||||
let blend_attachments = [blend];
|
||||
|
||||
let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
|
||||
.attachments(blend_attachments.as_ref())
|
||||
.logic_op_enable(false)
|
||||
.logic_op(vk::LogicOp::COPY)
|
||||
.blend_constants([1.0, 1.0, 1.0, 1.0]);
|
||||
|
||||
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&[
|
||||
vk::DynamicState::VIEWPORT_WITH_COUNT,
|
||||
vk::DynamicState::SCISSOR_WITH_COUNT,
|
||||
]);
|
||||
|
||||
let pipe_info = vk::GraphicsPipelineCreateInfo::default()
|
||||
.stages(&shader_stages)
|
||||
.vertex_input_state(&vert_input)
|
||||
.input_assembly_state(&input_assembly)
|
||||
.rasterization_state(&rasterization)
|
||||
.multisample_state(&multisample)
|
||||
.depth_stencil_state(&depth_stencil)
|
||||
.color_blend_state(&color_blend)
|
||||
.layout(layout)
|
||||
.dynamic_state(&dynamic)
|
||||
.render_pass(pass)
|
||||
.subpass(0);
|
||||
|
||||
let pipe = unsafe {
|
||||
ctx.dev
|
||||
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipe_info], None)
|
||||
.expect("Failed to create graphics pipeline")
|
||||
.remove(0)
|
||||
};
|
||||
println!("Created pipeline");
|
||||
pipe
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
use std::cmp;
|
||||
|
||||
use ash::{
|
||||
khr::{surface, swapchain as khr_swapchain},
|
||||
vk,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
RenderCtx,
|
||||
render::{MAX_HEIGHT, MAX_WIDTH},
|
||||
};
|
||||
|
||||
pub fn setup_swapchain(
|
||||
ctx: &RenderCtx,
|
||||
surface_loader: &surface::Instance,
|
||||
swapchain_loader: &khr_swapchain::Device,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> (vk::SwapchainKHR, vk::Extent2D) {
|
||||
let caps = unsafe {
|
||||
surface_loader
|
||||
.get_physical_device_surface_capabilities(ctx.pdev, ctx.surface)
|
||||
.expect("Failed to get surface capabilities")
|
||||
};
|
||||
|
||||
let formats = unsafe {
|
||||
surface_loader
|
||||
.get_physical_device_surface_formats(ctx.pdev, ctx.surface)
|
||||
.expect("Failed to get surface formats")
|
||||
};
|
||||
|
||||
let format = (formats)
|
||||
.iter()
|
||||
.filter(|f| {
|
||||
f.format == vk::Format::B8G8R8A8_UNORM
|
||||
&& f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR
|
||||
})
|
||||
.next()
|
||||
.expect("No suitable format found");
|
||||
|
||||
// When the window is resized fast, then the caps goes out of sync of the target extents
|
||||
// Thus we are using the passed-in (target) extents instead of the cap extents.
|
||||
// assert_eq!(caps.current_extent.width, width);
|
||||
// assert_eq!(caps.current_extent.height, height);
|
||||
|
||||
let swap_create = vk::SwapchainCreateInfoKHR::default()
|
||||
.surface(ctx.surface)
|
||||
.min_image_count(caps.min_image_count)
|
||||
.image_format(format.format)
|
||||
.image_color_space(format.color_space)
|
||||
.image_extent(vk::Extent2D {
|
||||
width: cmp::min(cmp::max(caps.current_extent.width, width), MAX_WIDTH),
|
||||
height: cmp::min(cmp::max(caps.current_extent.height, height), MAX_HEIGHT),
|
||||
})
|
||||
.image_array_layers(1)
|
||||
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
||||
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY)
|
||||
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||
.present_mode(vk::PresentModeKHR::MAILBOX)
|
||||
.clipped(true);
|
||||
|
||||
let swapchain = unsafe {
|
||||
swapchain_loader
|
||||
.create_swapchain(&swap_create, None)
|
||||
.expect("Failed to create swapchain")
|
||||
};
|
||||
println!("Created swapchain");
|
||||
(swapchain, caps.current_extent)
|
||||
}
|
||||
|
||||
pub fn make_swap_images(
|
||||
swapchain: vk::SwapchainKHR,
|
||||
swapchain_loader: &khr_swapchain::Device,
|
||||
) -> Vec<vk::Image> {
|
||||
unsafe {
|
||||
let images = swapchain_loader
|
||||
.get_swapchain_images(swapchain)
|
||||
.expect("Failed to get swapchain images");
|
||||
println!("Fetched swapchain images");
|
||||
images
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_swap_views(
|
||||
ctx: &RenderCtx,
|
||||
swap_images: &[vk::Image],
|
||||
format: vk::Format,
|
||||
) -> Vec<vk::ImageView> {
|
||||
let mut views = Vec::new();
|
||||
for img in swap_images.iter() {
|
||||
let view_info = vk::ImageViewCreateInfo::default()
|
||||
.image(*img)
|
||||
.view_type(vk::ImageViewType::TYPE_2D)
|
||||
.format(format)
|
||||
.components(vk::ComponentMapping::default())
|
||||
.subresource_range(
|
||||
vk::ImageSubresourceRange::default()
|
||||
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||
.base_mip_level(0)
|
||||
.level_count(1)
|
||||
.base_array_layer(0)
|
||||
.layer_count(1),
|
||||
);
|
||||
let view = unsafe {
|
||||
ctx.dev
|
||||
.create_image_view(&view_info, None)
|
||||
.expect("Failed to create image view")
|
||||
};
|
||||
views.push(view);
|
||||
}
|
||||
views
|
||||
}
|
||||
|
||||
pub fn make_depth_img(
|
||||
ctx: &RenderCtx,
|
||||
width: u32,
|
||||
height: u32,
|
||||
mem: vk::DeviceMemory,
|
||||
) -> vk::Image {
|
||||
let queue_fams = [ctx.queue_idx];
|
||||
let img_info = vk::ImageCreateInfo::default()
|
||||
.flags(vk::ImageCreateFlags::empty())
|
||||
.image_type(vk::ImageType::TYPE_2D)
|
||||
.format(vk::Format::D32_SFLOAT)
|
||||
.extent(vk::Extent3D {
|
||||
width,
|
||||
height,
|
||||
depth: 1,
|
||||
})
|
||||
.mip_levels(1)
|
||||
.array_layers(1)
|
||||
.samples(vk::SampleCountFlags::TYPE_1)
|
||||
.tiling(vk::ImageTiling::OPTIMAL)
|
||||
.usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.queue_family_indices(&queue_fams)
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED);
|
||||
|
||||
let img = unsafe {
|
||||
ctx.dev
|
||||
.create_image(&img_info, None)
|
||||
.expect("Failed to create image")
|
||||
};
|
||||
println!("Created image");
|
||||
|
||||
unsafe {
|
||||
ctx.dev
|
||||
.bind_image_memory(img, mem, 0)
|
||||
.expect("Failed to bind image memory")
|
||||
};
|
||||
|
||||
img
|
||||
}
|
||||
|
||||
pub fn make_depth_view(ctx: &RenderCtx, depth_img: vk::Image, format: vk::Format) -> vk::ImageView {
|
||||
let view_info = vk::ImageViewCreateInfo::default()
|
||||
.image(depth_img)
|
||||
.view_type(vk::ImageViewType::TYPE_2D)
|
||||
.format(format)
|
||||
.components(vk::ComponentMapping::default())
|
||||
.subresource_range(
|
||||
vk::ImageSubresourceRange::default()
|
||||
.aspect_mask(vk::ImageAspectFlags::DEPTH)
|
||||
.base_mip_level(0)
|
||||
.level_count(1)
|
||||
.base_array_layer(0)
|
||||
.layer_count(1),
|
||||
);
|
||||
let view = unsafe {
|
||||
ctx.dev
|
||||
.create_image_view(&view_info, None)
|
||||
.expect("Failed to create image view")
|
||||
};
|
||||
view
|
||||
}
|
||||
|
||||
pub fn make_framebufs(
|
||||
ctx: &RenderCtx,
|
||||
swap_views: &Vec<vk::ImageView>,
|
||||
depth_view: &vk::ImageView,
|
||||
pass: &vk::RenderPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Vec<vk::Framebuffer> {
|
||||
let mut framebufs = Vec::new();
|
||||
for view in swap_views.iter() {
|
||||
let attachments = [*view, *depth_view];
|
||||
let framebuf_info = vk::FramebufferCreateInfo::default()
|
||||
.render_pass(*pass)
|
||||
.attachments(attachments.as_ref())
|
||||
.width(width)
|
||||
.height(height)
|
||||
.layers(1);
|
||||
let framebuf = unsafe {
|
||||
ctx.dev
|
||||
.create_framebuffer(&framebuf_info, None)
|
||||
.expect("Failed to create framebuffer")
|
||||
};
|
||||
framebufs.push(framebuf);
|
||||
}
|
||||
framebufs
|
||||
}
|
Loading…
Reference in new issue