Files
voxel-renderer/src/state.rs
2026-01-11 19:01:50 +01:00

433 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::collections::HashSet;
use std::f32::consts::PI;
use std::sync::Arc;
use bytemuck::bytes_of;
use egui_wgpu::ScreenDescriptor;
use gpu_shared::ChunkPushConstants;
use wgpu::ExperimentalFeatures;
use wgpu::Extent3d;
use wgpu::Features;
use wgpu::FeaturesWGPU;
use wgpu::FeaturesWebGPU;
use wgpu::RenderPipeline;
use wgpu::ShaderStages;
use wgpu::TextureDescriptor;
use wgpu::TextureFormat;
use wgpu::TextureUsages;
use wgpu::TextureView;
use wgpu::include_spirv;
use wgpu::include_spirv_raw;
use winit::event::MouseScrollDelta;
use winit::event::WindowEvent;
use winit::keyboard::KeyCode;
use winit::window::Window;
use crate::egui_renderer::EguiState;
pub struct State
{
window: Arc<Window>,
device: wgpu::Device,
queue: wgpu::Queue,
size: winit::dpi::PhysicalSize<u32>,
surface: wgpu::Surface<'static>,
surface_format: wgpu::TextureFormat,
egui_state: EguiState,
// Camera
camera: Camera,
// pressed events
pressed_set: HashSet<KeyCode>,
pipeline: RenderPipeline,
depth_buffer: TextureView,
}
impl State
{
pub async fn new(window: Arc<Window>) -> State
{
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions::default())
.await
.unwrap();
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
required_features: Features {
features_wgpu: FeaturesWGPU::PUSH_CONSTANTS
| FeaturesWGPU::EXPERIMENTAL_PASSTHROUGH_SHADERS,
features_webgpu: FeaturesWebGPU::empty(),
},
experimental_features: unsafe { ExperimentalFeatures::enabled() },
required_limits: wgpu::Limits {
max_push_constant_size: size_of::<ChunkPushConstants>() as u32,
..Default::default()
},
..Default::default()
})
.await
.unwrap();
let size = window.inner_size();
let surface = instance.create_surface(window.clone()).unwrap();
let cap = surface.get_capabilities(&adapter);
let surface_format = cap.formats[0];
// let shaders = include_spirv!(env!("shaders.spv"));
// let shader_module = device.create_shader_module(shaders);
let shaders = include_spirv_raw!(env!("shaders.spv"));
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("chunk_pipeline"),
layout: Some(
&device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[],
push_constant_ranges: &[wgpu::PushConstantRange {
stages: wgpu::ShaderStages::VERTEX,
range: 0..(size_of::<ChunkPushConstants>() as u32),
}],
}),
),
vertex: wgpu::VertexState {
module: &shader_module,
entry_point: Some("main_vs"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader_module,
entry_point: Some("main_fs"),
targets: &[Some(wgpu::ColorTargetState {
format: surface_format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
let state = State {
egui_state: EguiState::new(&device, surface_format, &window),
camera: Camera {
eye: glam::Vec3::new(0., 0., 0.),
up: glam::Vec3::ZERO.with_y(1.),
aspect: size.width as f32 / size.height as f32,
fovy: 90.,
znear: 0.001,
zfar: 1000.,
radius: 2.,
yaw: 1.,
pitch: 0.,
rotation_speed: 0.005,
speed: 0.005,
},
window,
queue,
size,
surface,
surface_format,
pipeline,
pressed_set: HashSet::new(),
depth_buffer: device
.create_texture(&TextureDescriptor {
label: Some("Depth buffer"),
size: Extent3d {
width: size.width,
height: size.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: TextureFormat::Depth24PlusStencil8,
usage: TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
})
.create_view(&Default::default()),
device,
};
// Configure surface for the first time
state.configure_surface();
state
}
pub fn get_window(&self) -> &Window
{
&self.window
}
pub fn configure_surface(&self)
{
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: self.surface_format,
// Request compatibility with the sRGB-format texture view were going to create later.
view_formats: vec![self.surface_format.add_srgb_suffix()],
alpha_mode: wgpu::CompositeAlphaMode::Auto,
width: self.size.width,
height: self.size.height,
desired_maximum_frame_latency: 2,
present_mode: wgpu::PresentMode::AutoVsync,
};
self.surface.configure(&self.device, &surface_config);
}
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>)
{
self.size = new_size;
// reconfigure the surface
self.configure_surface();
self.depth_buffer = self
.device
.create_texture(&TextureDescriptor {
label: Some("Depth buffer"),
size: Extent3d {
width: new_size.width,
height: new_size.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: TextureFormat::Depth24PlusStencil8,
usage: TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
})
.create_view(&Default::default());
}
pub fn render(&mut self)
{
self.update_camera();
// Create texture view
let surface_texture = self
.surface
.get_current_texture()
.expect("failed to acquire next swapchain texture");
let texture_view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor {
// Without add_srgb_suffix() the image we will be working with
// might not be "gamma correct".
format: Some(self.surface_format.add_srgb_suffix()),
..Default::default()
});
// Renders a GREEN screen
let mut encoder = self.device.create_command_encoder(&Default::default());
{
let mut renderpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &texture_view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.01,
g: 0.01,
b: 0.01,
a: 1.,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_buffer,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.),
store: wgpu::StoreOp::Discard,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
renderpass.set_pipeline(&self.pipeline);
renderpass.set_push_constants(
ShaderStages::VERTEX,
0,
bytes_of(&ChunkPushConstants {
view_projection: self.camera.view_proj(),
transform: glam::Mat4::IDENTITY,
eye_position: self.camera.eye,
_zero_pad: 0.,
}),
);
renderpass.draw(0..36, 0..1);
// End the renderpass.
}
// Egui
{
let screen_descriptor = ScreenDescriptor {
size_in_pixels: [self.size.width, self.size.height],
pixels_per_point: 1.,
};
self.egui_state.begin_frame(&self.window);
egui::Window::new("Hello Window").resizable(true).show(
self.egui_state.context(),
|ui| {
ui.label("Hello, world.");
},
);
self.egui_state.end_frame_and_draw(
&self.device,
&self.queue,
&mut encoder,
&self.window,
&texture_view,
screen_descriptor,
);
}
// Submit the command in the queue to execute
self.queue.submit([encoder.finish()]);
self.window.pre_present_notify();
surface_texture.present();
}
// ------------------------------
// MOVEMENT
// ------------------------------
pub fn handle_event(&mut self, event: &WindowEvent)
{
self.egui_state.handle_event(&self.window, event);
if let WindowEvent::KeyboardInput { event, .. } = event
{
match (event.state, event.physical_key)
{
(winit::event::ElementState::Pressed, winit::keyboard::PhysicalKey::Code(c)) =>
{
self.pressed_set.insert(c);
}
(winit::event::ElementState::Released, winit::keyboard::PhysicalKey::Code(c)) =>
{
self.pressed_set.remove(&c);
}
_ =>
{}
}
}
}
fn update_camera(&mut self)
{
let mut movement = glam::Vec3::new(0., 0., 0.);
if self.pressed_set.contains(&KeyCode::KeyW)
{
movement.z += self.camera.speed;
}
if self.pressed_set.contains(&KeyCode::KeyS)
{
movement.z -= self.camera.speed;
}
// Left rigth
if self.pressed_set.contains(&KeyCode::KeyA)
{
movement.x += self.camera.speed;
}
if self.pressed_set.contains(&KeyCode::KeyD)
{
movement.x -= self.camera.speed;
}
let rot_movement = glam::Mat3::from_rotation_y(-self.camera.yaw)
* glam::Mat3::from_rotation_x(-self.camera.pitch)
* movement;
self.camera.eye -= rot_movement;
}
pub fn cursor_moved(&mut self, x: f32, y: f32)
{
const SENSIBILITY: f32 = 0.0004;
let position = glam::Vec2::new(x, y);
let offset = position * SENSIBILITY;
self.camera.yaw += offset.x;
self.camera.pitch += offset.y;
}
pub fn mouse_wheel(&mut self, delta: MouseScrollDelta)
{
if let MouseScrollDelta::LineDelta(_, y) = delta
{
self.camera.speed += y * (self.camera.speed * 0.05);
}
}
}
#[derive(Clone, Copy)]
pub struct Camera
{
pub eye: glam::Vec3,
pub up: glam::Vec3,
pub aspect: f32,
pub fovy: f32,
pub znear: f32,
pub zfar: f32,
pub radius: f32,
pub yaw: f32,
pub pitch: f32,
pub rotation_speed: f32,
pub speed: f32,
}
#[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = glam::Mat4::from_cols(
glam::Vec4::new(1.0, 0.0, 0.0, 0.0),
glam::Vec4::new(0.0, 1.0, 0.0, 0.0),
glam::Vec4::new(0.0, 0.0, 0.5, 0.0),
glam::Vec4::new(0.0, 0.0, 0.5, 1.0),
);
impl Camera
{
pub fn view_proj(&self) -> glam::Mat4
{
let view = glam::Mat4::from_translation(self.eye)
* glam::Mat4::from_rotation_y(-self.yaw)
* glam::Mat4::from_rotation_x(-self.pitch);
let proj =
glam::Mat4::perspective_rh(PI * self.fovy / 180., self.aspect, self.znear, self.zfar);
proj * view.inverse()
}
}