feat(rendering): rendering textures with camera

Co-authored-by: BitSyndicate <contact@bitsyndicate.de>
This commit is contained in:
Chance 2025-04-17 20:33:28 -04:00
parent 506c9a1146
commit aa655a84d3
Signed by: caznix
GPG key ID: 489D213143D753FD
9 changed files with 1037 additions and 329 deletions

863
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -37,11 +37,14 @@ incremental = false
panic = "abort"
[dependencies]
glm = "0.2.3"
bytemuck = "1.22.0"
cgmath = "0.18.0"
image = "0.25.6"
smol = "2.0.2"
terminator = "0.3.2"
thiserror = "2.0.12"
tobj = "4.0.3"
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
vulkano = "0.35.1"

View file

@ -1,8 +1,10 @@
#version 450
layout(location = 0) in vec3 color;
layout(location = 0) in vec2 tex_coords;
layout(set = 0, binding = 0) uniform texture2D t_diffuse;
layout(set = 0, binding = 1) uniform sampler s_diffuse;
layout(location = 0) out vec4 out_color;
// layout(group = 0, binding = 0) out texture2D;
void main() {
out_color = vec4(color, 1.0);
out_color = texture(sampler2D(t_diffuse, s_diffuse), tex_coords);
}

View file

@ -2,9 +2,14 @@
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
layout(location = 0) out vec3 out_color;
layout(location = 3) in vec2 tex_coords;
layout(location = 0) out vec2 tex_coord;
layout(set = 1, binding = 0) uniform UniformBufferObject {
mat4x4 projection;
} view;
void main() {
gl_Position = vec4(position, 1.0);
out_color = color;
gl_Position = view.projection * vec4(position, 1.0);
tex_coord = tex_coords;
// gl_Position
// out_color = color;
}

53
src/camera.rs Normal file
View file

@ -0,0 +1,53 @@
pub struct Camera {
pub eye: cgmath::Point3<f32>,
pub target: cgmath::Point3<f32>,
pub up: cgmath::Vector3<f32>,
pub aspect: f32,
pub fovy: f32,
pub znear: f32,
pub zfar: f32,
}
impl Camera {
fn build_view_projection_matrix(&self) -> cgmath::Matrix4<f32> {
let view = cgmath::Matrix4::look_at_rh(self.eye, self.target, self.up);
let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar);
OPENGL_TO_WGPU_MATRIX * proj * view
}
pub fn update_aspect(&mut self, aspect: f32) {
self.aspect = aspect;
}
}
#[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.5, 0.5,
0.0, 0.0, 0.0, 1.0,
);
unsafe impl bytemuck::Pod for CameraUniform {}
unsafe impl bytemuck::Zeroable for CameraUniform {}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct CameraUniform {
view_proj: cgmath::Matrix4<f32>,
}
impl Default for CameraUniform {
fn default() -> CameraUniform {
use cgmath::SquareMatrix;
Self {
view_proj: cgmath::Matrix4::identity(),
}
}
}
impl CameraUniform {
pub fn update_view_proj(&mut self, camera: &Camera) {
self.view_proj = camera.build_view_projection_matrix();
}
}

View file

@ -1,20 +1,22 @@
use std::collections::BTreeMap;
use std::sync::LazyLock;
use std::{collections::HashMap, sync::Arc};
use std::sync::Arc;
use terminator::Terminator;
use vulkano::buffer::BufferUsage;
use wgpu::util::{DeviceExt, RenderEncoder};
use bytemuck::bytes_of;
use tracing::info;
use wgpu::util::DeviceExt;
use wgpu::{
Backends, BufferSlice, BufferUsages, FragmentState, IndexFormat, Instance, InstanceDescriptor,
PipelineCompilationOptions, Surface,
Backends, BufferUsages, FragmentState, IndexFormat, Instance, InstanceDescriptor,
PipelineCompilationOptions,
};
use winit::application::ApplicationHandler;
use winit::event::{ElementState, MouseButton};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId};
use zlog::LogLevel;
use zlog::{config::LoggerConfig, query::LogQuery};
use zlog::config::LoggerConfig;
pub mod camera;
pub mod model;
pub mod texture;
struct WindowContext<'window> {
window: Arc<Window>,
renderer: WgpuRenderer<'window>,
@ -33,10 +35,52 @@ struct WgpuRenderer<'surface> {
surface: wgpu::Surface<'surface>,
surface_config: wgpu::SurfaceConfiguration,
render_pipeline: wgpu::RenderPipeline,
vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer,
diffuse_bind_group: wgpu::BindGroup,
camera: camera::Camera,
camera_uniform: camera::CameraUniform,
camera_buffer: wgpu::Buffer,
camera_bind_group: wgpu::BindGroup,
}
impl WgpuRenderer<'_> {
pub fn draw(&mut self) {
let surface_texture = self.surface.get_current_texture().unwrap();
let view = surface_texture.texture.create_view(&Default::default());
let mut encoder =
self.device
.create_command_encoder(&wgpu::wgt::CommandEncoderDescriptor {
label: Some("Render encoder"),
});
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&self.render_pipeline);
rpass.set_bind_group(0, &self.diffuse_bind_group, &[]);
rpass.set_bind_group(1, &self.camera_bind_group, &[]);
rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
rpass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint32);
rpass.draw_indexed(0..INDICIES.len() as u32, 0, 0..1);
}
self.queue.submit(Some(encoder.finish()));
surface_texture.present()
}
}
// impl WgpuRenderer {
// pub fn new(window: Arc<Window>) {}
struct App<'window> {
state: WgpuState,
windows: BTreeMap<WindowId, WindowContext<'window>>,
@ -49,6 +93,7 @@ impl App<'_> {
windows: BTreeMap::new(),
}
}
pub fn spawn_window(&mut self, event_loop: &ActiveEventLoop) {
let attr = WindowAttributes::default()
.with_title("Zenyx - SubWindow")
@ -69,14 +114,20 @@ impl App<'_> {
struct WgpuState {
instance: wgpu::Instance,
}
unsafe impl bytemuck::Pod for Vertex {}
unsafe impl bytemuck::Zeroable for Vertex {}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
struct Vertex {
position: glm::Vec3,
color: glm::Vec3,
position: cgmath::Vector3<f32>,
color: cgmath::Vector3<f32>,
normal: cgmath::Vector3<f32>,
tex_coords: cgmath::Vector2<f32>,
}
impl Vertex {
const ATTRIBS: [wgpu::VertexAttribute; 2] = [
const ATTRIBS: [wgpu::VertexAttribute; 4] = [
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
@ -87,6 +138,16 @@ impl Vertex {
shader_location: 1,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: std::mem::offset_of!(Vertex, normal) as u64,
shader_location: 2,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: std::mem::offset_of!(Vertex, tex_coords) as u64,
shader_location: 3,
format: wgpu::VertexFormat::Float32x2,
},
];
const fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
@ -97,7 +158,7 @@ impl Vertex {
}
}
}
static ICON: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Badge.png"));
impl WgpuState {
fn new() -> Self {
let backends = Backends::PRIMARY;
@ -126,31 +187,69 @@ impl WgpuState {
let size = window.inner_size();
let width = size.width.max(1);
let height = size.height.max(1);
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
// This should match the filterable field of the
// corresponding Texture entry above.
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
label: Some("texture_bind_group_layout"),
});
let camera_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: Some("Camera Bind group layout"),
});
#[repr(align(4))]
struct ShaderCode<const N: usize>([u8; N]);
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Pipeline Layout"),
bind_group_layouts: &[],
bind_group_layouts: &[&texture_bind_group_layout, &camera_bind_group_layout],
push_constant_ranges: &[],
});
let vert_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Vertex Shader"),
source: unsafe {
static shader_code: &[u8] =
static SHADER_CODE: &[u8] =
&ShaderCode(*include_bytes!(concat!(env!("OUT_DIR"), "/vert.spv"))).0;
// assert!(bytes.len() % 4 == 0);
let shader = shader_code.align_to::<u32>().1;
let shader = SHADER_CODE.align_to::<u32>().1;
wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(shader))
},
});
let frag_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Fragment Shader"),
source: unsafe {
static shader_code: &[u8] =
static SHADER_CODE: &[u8] =
&ShaderCode(*include_bytes!(concat!(env!("OUT_DIR"), "/frag.spv"))).0;
// assert!(bytes.len() % 4 == 0);
let shader = shader_code.align_to::<u32>().1;
debug_assert!(SHADER_CODE.len() % 4 == 0);
let shader = SHADER_CODE.align_to::<u32>().1;
wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(shader))
},
});
@ -202,7 +301,6 @@ impl WgpuState {
} else {
wgpu::PresentMode::Fifo
};
let surface_config = wgpu::SurfaceConfiguration {
width,
height,
@ -215,36 +313,114 @@ impl WgpuState {
};
surface.configure(&device, &surface_config);
let diffuse_texture =
texture::Texture::from_bytes(&device, &queue, ICON, "zenyx-icon").unwrap();
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex buffer"),
contents: bytemuck::cast_slice(VERTICES.as_ref()),
usage: BufferUsages::VERTEX,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index buffer"),
contents: bytemuck::cast_slice(&INDICIES),
usage: BufferUsages::INDEX,
});
let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
},
],
label: Some("diffuse_bind_group"),
});
let camera = camera::Camera {
// position the camera 1 unit up and 2 units back
// +z is out of the screen
eye: (0.0, 0.0, 2.0).into(),
// have it look at the origin
target: (0.0, 0.0, 0.0).into(),
// which way is "up"
up: cgmath::Vector3::unit_y(),
aspect: surface_config.width as f32 / surface_config.height as f32,
fovy: 45.0,
znear: 0.1,
zfar: 100.0,
};
let mut camera_uniform = camera::CameraUniform::default();
camera_uniform.update_view_proj(&camera);
let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Camera Buffer"),
contents: bytemuck::cast_slice(&[camera_uniform]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &camera_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: camera_buffer.as_entire_binding(),
}],
label: Some("Camera Bind Group"),
});
let render_pipeline = device.create_render_pipeline(&pipeline_descriptor);
WgpuRenderer {
surface,
surface_config,
diffuse_bind_group,
render_pipeline,
device,
vertex_buffer,
index_buffer,
camera,
queue,
camera_uniform,
camera_buffer,
camera_bind_group,
}
}
}
static VERTICES: LazyLock<[Vertex; 3]> = std::sync::LazyLock::new(|| {
static VERTICES: [Vertex; 4] = {
[
Vertex {
position: glm::vec3(-0.6, -0.5, 0.0),
color: glm::vec3(1.0, 0.0, 0.0),
position: cgmath::vec3(-0.5, -0.5, 0.0),
color: cgmath::vec3(1.0, 0.0, 0.0),
normal: cgmath::vec3(0.0, 0.0, 0.0),
tex_coords: cgmath::vec2(1.0, 0.0),
},
Vertex {
position: glm::vec3(0.6, -0.5, 0.0),
color: glm::vec3(0.0, 1.0, 0.0),
position: cgmath::vec3(0.5, -0.5, 0.0),
color: cgmath::vec3(0.0, 1.0, 0.0),
normal: cgmath::vec3(0.0, 0.0, 0.0),
tex_coords: cgmath::vec2(0.0, 0.0),
},
Vertex {
position: glm::vec3(0.0, 0.5, 0.0),
color: glm::vec3(0.0, 0.0, 1.0),
position: cgmath::vec3(0.5, 0.5, 0.0),
color: cgmath::vec3(0.0, 0.0, 1.0),
normal: cgmath::vec3(0.0, 0.0, 0.0),
tex_coords: cgmath::vec2(0.0, 1.0),
},
Vertex {
position: cgmath::vec3(-0.5, 0.5, 0.0),
color: cgmath::vec3(0.0, 0.0, 1.0),
normal: cgmath::vec3(0.0, 0.0, 0.0),
tex_coords: cgmath::vec2(1.0, 1.0),
},
]
});
};
static INDICIES: [u32; 3] = [0, 1, 2];
static INDICIES: [u32; 6] = [0, 1, 2, 2, 3, 0];
impl<'window> ApplicationHandler for App<'window> {
impl ApplicationHandler for App<'_> {
fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
@ -253,50 +429,8 @@ impl<'window> ApplicationHandler for App<'window> {
) {
match event {
winit::event::WindowEvent::RedrawRequested => {
let window_ctx = self.windows.get(&window_id).unwrap();
let surface_texture = window_ctx.renderer.surface.get_current_texture().unwrap();
let view = surface_texture.texture.create_view(&Default::default());
let mut encoder = window_ctx.renderer.device.create_command_encoder(
&wgpu::wgt::CommandEncoderDescriptor {
label: Some("Render encoder"),
},
);
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&window_ctx.renderer.render_pipeline);
let vertex_buffer = window_ctx.renderer.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Vertex buffer"),
contents: unsafe { VERTICES.align_to::<u8>().1 },
usage: BufferUsages::VERTEX,
},
);
let index_buffer = window_ctx.renderer.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Index buffer"),
contents: unsafe { INDICIES.align_to::<u8>().1 },
usage: BufferUsages::INDEX,
},
);
rpass.set_vertex_buffer(0, vertex_buffer.slice(..));
rpass.set_index_buffer(index_buffer.slice(..), IndexFormat::Uint32);
rpass.draw_indexed(0..INDICIES.len() as u32, 0, 0..1);
}
window_ctx.renderer.queue.submit(Some(encoder.finish()));
surface_texture.present()
let window_ctx = self.windows.get_mut(&window_id).unwrap();
window_ctx.renderer.draw()
}
winit::event::WindowEvent::CloseRequested => {
let _ = self.windows.remove(&window_id);
@ -304,23 +438,33 @@ impl<'window> ApplicationHandler for App<'window> {
event_loop.exit();
}
}
winit::event::WindowEvent::MouseInput {
device_id,
state,
button,
} => {
winit::event::WindowEvent::MouseInput { state, button, .. } => {
if button == MouseButton::Left && state == ElementState::Pressed {
self.spawn_window(event_loop);
}
}
winit::event::WindowEvent::Resized(size) => {
if let Some(window_ctx) = self.windows.get(&window_id) {
if let Some(window_ctx) = self.windows.get_mut(&window_id) {
if size.width == 0 || size.height == 0 {
return;
}
window_ctx
.renderer
.camera
.update_aspect(size.width as f32 / size.height as f32);
window_ctx
.renderer
.camera_uniform
.update_view_proj(&window_ctx.renderer.camera);
window_ctx.renderer.queue.write_buffer(
&window_ctx.renderer.camera_buffer,
0,
bytes_of(&window_ctx.renderer.camera_uniform),
);
let mut new_config = window_ctx.renderer.surface_config.clone();
new_config.width = size.width;
new_config.height = size.height;
window_ctx
.renderer
.surface
@ -364,5 +508,6 @@ fn main() -> Result<(), terminator::Terminator> {
let mut app = App::new();
event_loop.run_app(&mut app)?;
info!("Exiting...");
Ok(())
}

14
src/model.rs Normal file
View file

@ -0,0 +1,14 @@
use crate::texture::Texture;
pub struct Material {
pub name: String,
pub diffuse_texture: Texture,
pub bind_group: wgpu::BindGroup,
}
pub struct Mesh {
pub name: String,
pub vertex_buffer: wgpu::Buffer,
pub index_buffer: wgpu::Buffer,
pub num_elements: u32,
pub material: usize,
}

87
src/texture.rs Normal file
View file

@ -0,0 +1,87 @@
use image::GenericImageView;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum TextureError {
#[error("Failed to load image")]
ImageError(
#[from]
#[source]
image::ImageError,
),
}
pub struct Texture {
#[allow(unused)]
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
}
impl Texture {
pub fn from_bytes(
device: &wgpu::Device,
queue: &wgpu::Queue,
bytes: &[u8],
label: &str,
) -> Result<Self, TextureError> {
let img = image::load_from_memory(bytes)?;
Self::from_image(device, queue, &img, Some(label))
}
pub fn from_image(
device: &wgpu::Device,
queue: &wgpu::Queue,
img: &image::DynamicImage,
label: Option<&str>,
) -> Result<Self, TextureError> {
let rgba = img.to_rgba8();
let dimensions = img.dimensions();
let size = wgpu::Extent3d {
width: dimensions.0,
height: dimensions.1,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
label,
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&rgba,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * dimensions.0),
rows_per_image: Some(dimensions.1),
},
size,
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
Ok(Self {
texture,
view,
sampler,
})
}
}

View file

@ -20,7 +20,6 @@ use config::LoggerConfig;
use query::LogQuery;
use tracing::{Event, Level, Subscriber, level_filters::LevelFilter, subscriber::DefaultGuard};
use tracing_subscriber::{
fmt::writer::WithFilter,
layer::{Context, Layer, SubscriberExt},
registry::LookupSpan,
util::SubscriberInitExt,
@ -76,7 +75,9 @@ impl BufferLayer {
impl Drop for BufferLayer {
fn drop(&mut self) {
for tx in &self.senders {
tx.send(LogEvent::Shutdown).unwrap();
if let Err(e) = tx.send(LogEvent::Shutdown) {
panic!("{e}")
}
}
self.senders.clear();
}
@ -143,17 +144,6 @@ pub enum LogLevel {
Trace,
}
impl From<LogLevel> for tracing::Level {
fn from(level: LogLevel) -> Self {
match level {
LogLevel::Error => Level::ERROR,
LogLevel::Info => Level::INFO,
LogLevel::Debug => Level::DEBUG,
LogLevel::Trace => Level::TRACE,
LogLevel::Warn => Level::WARN,
}
}
}
impl From<LogLevel> for LevelFilter {
fn from(level: LogLevel) -> Self {
match level {