Implementing garbage collector, tables, renderer and virtual machine.

This commit is contained in:
paladin 2026-08-16 12:06:30 +01:00
parent eba41e4330
commit 35cf269781
10 changed files with 683 additions and 1327 deletions

View file

@ -1,5 +1,74 @@
use std::collections::HashMap;
// Credit of most code to https://sotrh.github.io/learn-wgpu/ since I'm not familiar with wgpu
use std::sync::Arc;
use glam::camera::lh::proj::directx::perspective;
use wgpu::util::DeviceExt;
use glam::prelude::*;
struct Controller {
buttons: HashMap<MouseButton,bool>,
keys: HashMap<KeyCode,bool>,
mouse: Vec2,
}
impl Controller {
fn new() -> Controller {
Controller {
buttons: Default::default(),
keys: HashMap::new(),
mouse: Vec2::new(0.0,0.0),
}
}
}
struct Camera {
frame: Affine3A,
aspect_ratio: f32,
z_near: f32,
z_far: f32,
fov_y: f32,
}
impl Camera {
fn view(&self) -> Mat4 {
let projection = perspective(self.fov_y, self.aspect_ratio, self.z_near, self.z_far);
projection * self.frame.inverse()
}
fn new(window: &Arc<Window>) -> Camera {
Camera {
aspect_ratio: window.inner_size().width as f32 / window.inner_size().height as f32,
frame: Affine3A::IDENTITY,
fov_y: 90.0,
z_near: 0.1,
z_far: 1000.0
}
}
fn resize(&mut self, window: &Arc<Window>) {
self.aspect_ratio = window.inner_size().width as f32 / window.inner_size().height as f32
}
fn control(&mut self, delta: Vec3) {
self.frame = self.frame * Affine3A::from_translation(delta);
}
fn rotate(&mut self, yaw: f32, pitch: f32) {
let (mut y,mut x,z) = self.frame.matrix3.to_euler(EulerRot::YXZ);
x = (x + pitch * 0.005).clamp(-1.4,1.4);
y = y + yaw * 0.005;
self.frame.matrix3 = Mat3A::from_euler(EulerRot::YXZ,y,x,z);
}
}
const VERTICES: &[Vertex] = &[
// Changed
Vertex { position: [-0.5,-0.5,-0.5], tex_coords: [0,0]}
];
const INDICES: &[u16] = &[
0, 1, 4,
1, 2, 4,
2, 3, 4,
];
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
@ -14,17 +83,83 @@ use winit::{
window::Window,
};
// This will store the state of our game
pub struct State {
surface: wgpu::Surface<'static>,
type Kt<K,V> = HashMap<K,Vec<V>>;
struct Renderer {
surface: (wgpu::Surface<'static>,bool),
config: wgpu::SurfaceConfiguration,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
is_surface_configured: bool,
camera: Camera,
clear_color: wgpu::Color,
}
struct UniformPlan {
bind_group: wgpu::BindGroup,
buffer: wgpu::Buffer,
}
struct TexturePlan {
bind_group: wgpu::BindGroup,
texture: wgpu::Texture,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct SimpleVertex {
pos: [f32; 3],
uv: [f32; 2],
norm: [f32; 3],
}
impl SimpleVertex {
fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<SimpleVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
shader_location: 2,
format: wgpu::VertexFormat::Float32x3,
}
]
}
}
}
struct SimpleMesh {
vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer,
indices_count: u32,
}
struct SimpleMaterial {
albedo: TexturePlan,
normal: Option<TexturePlan>,
specular: Option<TexturePlan>,
}
struct SimpleRenderPlan {
pipeline: wgpu::RenderPipeline,
clients: Kt<SimpleMesh,Kt<SimpleMaterial,Affine3A>>
}
pub struct State {
renderer: Renderer,
controller: Controller,
window: Arc<Window>,
render_pipeline1: wgpu::RenderPipeline,
render_pipeline2: wgpu::RenderPipeline,
other_pipeline: bool,
diffuse_bind_group: wgpu::BindGroup,
sky: wgpu::Color,
}
@ -47,7 +182,7 @@ impl State {
display: None,
});
let surface = instance.create_surface(window.clone()).unwrap();
let surface = instance.create_surface(window.clone())?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
@ -95,22 +230,205 @@ impl State {
desired_maximum_frame_latency: 2,
};
let diffuse_bytes = include_bytes!("assets/test.png");
let diffuse_image = image::load_from_memory(diffuse_bytes)?;
let diffuse_rgba = diffuse_image.to_rgba8();
use image::GenericImageView;
let dimensions = diffuse_image.dimensions();
println!("{:?}",dimensions);
let texture_size = wgpu::Extent3d {
width: dimensions.0,
height: dimensions.1,
// All textures are stored as 3D, we represent our 2D texture
// by setting depth to 1.
depth_or_array_layers: 1,
};
let diffuse_texture = device.create_texture(
&wgpu::TextureDescriptor {
size: texture_size,
mip_level_count: 1, // We'll talk about this a little later
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
// Most images are stored using sRGB, so we need to reflect that here.
format: wgpu::TextureFormat::Rgba8UnormSrgb,
// TEXTURE_BINDING tells wgpu that we want to use this texture in shaders
// COPY_DST means that we want to copy data to this texture
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
label: Some("diffuse_texture"),
// This is the same as with the SurfaceConfig. It
// specifies what texture formats can be used to
// create TextureViews for this texture. The base
// texture format (Rgba8UnormSrgb in this case) is
// always supported. Note that using a different
// texture format is not supported on the WebGL2
// backend.
view_formats: &[],
}
);
queue.write_texture(
// Tells wgpu where to copy the pixel data
wgpu::TexelCopyTextureInfo {
texture: &diffuse_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
// The actual pixel data
&diffuse_rgba,
// The layout of the texture
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * dimensions.0),
rows_per_image: Some(dimensions.1),
},
texture_size,
);
let diffuse_texture_view = diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
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 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_sampler),
}
],
label: Some("diffuse_bind_group"),
}
);
let camera = Camera::new(&window);
let camera_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Camera Buffer"),
contents: bytemuck::cast_slice(&[camera.view()]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
}
);
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"),
});
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 vertex_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(VERTICES),
usage: wgpu::BufferUsages::VERTEX,
}
);
let index_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(INDICES),
usage: wgpu::BufferUsages::INDEX,
}
);
let num_indices = INDICES.len() as u32;
let shader1 = device.create_shader_module(wgpu::include_wgsl!("assets/shader.wgsl"));
let render_pipeline_layout1 =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[],
bind_group_layouts: &[
Some(&texture_bind_group_layout),
Some(&camera_bind_group_layout),
],
immediate_size: 0,
});
/*let water_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Water Pipeline"),
layout: Some(&water_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader1,
entry_point: Some("vs_main"),
buffers: &[
],
compilation_options: wgpu::PipelineCompilationOptions::default();
}
});*/
let render_pipeline1 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&render_pipeline_layout1),
vertex: wgpu::VertexState {
module: &shader1,
entry_point: Some("vs_main"), // 1.
buffers: &[], // 2.
entry_point: Some("vs_main"),
buffers: &[
Vertex::desc(),
],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
@ -147,74 +465,28 @@ impl State {
cache: None, // 6.
});
let shader2 = device.create_shader_module(wgpu::include_wgsl!("assets/shader2.wgsl"));
let render_pipeline_layout2 =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[],
immediate_size: 0,
});
let render_pipeline2 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&render_pipeline_layout2),
vertex: wgpu::VertexState {
module: &shader2,
entry_point: Some("vs_main"), // 1.
buffers: &[], // 2.
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
// 3.
module: &shader2,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
// 4.
format: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList, // 1.
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw, // 2.
cull_mode: Some(wgpu::Face::Back),
// Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
polygon_mode: wgpu::PolygonMode::Fill,
// Requires Features::DEPTH_CLIP_CONTROL
unclipped_depth: false,
// Requires Features::CONSERVATIVE_RASTERIZATION
conservative: false,
},
depth_stencil: None, // 1.
multisample: wgpu::MultisampleState {
count: 1, // 2.
mask: !0, // 3.
alpha_to_coverage_enabled: false, // 4.
},
multiview_mask: None, // 5.
cache: None, // 6.
});
Ok(Self {
surface,
camera_buffer,
camera_bind_group,
diffuse_bind_group,
index_buffer,
vertex_buffer,
controller: Controller::new(),
device,
queue,
config,
is_surface_configured: false,
window,
render_pipeline1,
render_pipeline2,
other_pipeline: false,
num_indices,
render_pipeline: render_pipeline1,
sky: wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
},
camera,
})
}
@ -224,6 +496,7 @@ impl State {
self.config.width = width.min(max);
self.config.height = height.min(max);
self.surface.configure(&self.device, &self.config);
self.camera.resize(&self.window);
self.is_surface_configured = true;
}
}
@ -288,12 +561,47 @@ impl State {
multiview_mask: None,
});
if self.other_pipeline {
render_pass.set_pipeline(&self.render_pipeline1);
} else {
render_pass.set_pipeline(&self.render_pipeline2);
let mut movement = Vec3::new(0.0,0.0,0.0);
let pressed = |keycode: KeyCode| {
if let Some(true) = self.controller.keys.get(&keycode) {
true
} else {
false
}
};
if pressed(KeyCode::KeyA) {
movement.x -= 1.0;
}
render_pass.draw(0..3, 0..1); // 3.
if pressed(KeyCode::KeyD) {
movement.x += 1.0;
}
if pressed(KeyCode::KeyW) {
movement.z += 1.0;
}
if pressed(KeyCode::KeyS) {
movement.z -= 1.0;
}
if pressed(KeyCode::KeyE) {
movement.y += 1.0;
}
if pressed(KeyCode::KeyQ) {
movement.y -= 1.0;
}
println!("{:?} {:?} {:?}",movement,self.camera.frame.translation,self.camera.frame.matrix3.to_euler(EulerRot::YXZ));
self.camera.control(movement * 0.1);
self.queue.write_buffer(&self.camera_buffer,0,bytemuck::cast_slice(&[self.camera.view()]));
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(1, &self.camera_bind_group, &[]);
render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]);
render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
render_pass.draw_indexed(0..self.num_indices, 0, 0..1);
}
// submit will accept anything that implements IntoIter
@ -306,11 +614,13 @@ impl State {
fn handle_key(&mut self, event_loop: &ActiveEventLoop, code: KeyCode, is_pressed: bool) {
match (code, is_pressed) {
(KeyCode::Escape, true) => event_loop.exit(),
(KeyCode::Space, true) => {
self.other_pipeline = !self.other_pipeline;
(KeyCode::Space, true) => {},
(KeyCode::KeyR, true) => {
self.camera.frame = Affine3A::IDENTITY
}
_ => {}
}
self.controller.keys.insert(code, is_pressed);
}
fn handle_mouse_moved(&mut self, position: PhysicalPosition<f64>) {
@ -319,7 +629,15 @@ impl State {
g: position.x / 1000.0,
b: position.y / 1000.0,
a: 1.0,
};
if let Some(true) = self.controller.buttons.get(&MouseButton::Right) {
self.camera.rotate(position.x as f32 - self.controller.mouse.x, position.y as f32 - self.controller.mouse.y);
}
self.controller.mouse = Vec2::new(position.x as f32, position.y as f32);
}
fn handle_mouse_button(&mut self, button: MouseButton, state: ElementState ) {
self.controller.buttons.insert(button,state.is_pressed());
}
}
@ -428,6 +746,7 @@ impl ApplicationHandler<State> for App {
}
}
}
WindowEvent::MouseInput { button, state: element, .. } => state.handle_mouse_button(button,element),
WindowEvent::CursorMoved { position: pos, .. } => state.handle_mouse_moved(pos),
WindowEvent::KeyboardInput {
event: