Initial commit

This commit is contained in:
2025-12-19 16:44:16 +01:00
commit dbda39b61a
6 changed files with 402 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

17
Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "wgpu-template"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.100"
bytemuck = "1.24.0"
cgmath = "0.18.0"
crevice = {version = "0.18.0", features = ["cgmath"]}
egui = "0.33.3"
egui-wgpu = "0.33.3"
egui-winit = "0.33.3"
env_logger = "0.11.8"
pollster = "0.4.0"
wgpu = "27.0.1"
winit = "0.30.12"

137
src/egui_renderer.rs Normal file
View File

@ -0,0 +1,137 @@
use egui::{Context, PaintCallbackInfo};
use egui_wgpu::{CallbackResources, CallbackTrait, Renderer, RendererOptions, ScreenDescriptor};
use egui_winit::State;
use wgpu::{CommandEncoder, Device, Queue, RenderPass, TextureFormat, TextureView};
use winit::{event::WindowEvent, window::Window};
pub struct EguiState {
pub state: State,
pub renderer: Renderer,
pub frame_started: bool,
//pub msaa_texture: TextureView,
pub color_format: TextureFormat,
}
impl EguiState {
pub fn context(&self) -> &Context {
self.state.egui_ctx()
}
pub fn new(device: &Device, output_color_format: TextureFormat, window: &Window) -> EguiState {
let egui_context = Context::default();
let egui_state = egui_winit::State::new(
egui_context,
egui::ViewportId::ROOT,
&window,
Some(window.scale_factor() as f32),
None,
Some(2 * 1024),
);
let options = RendererOptions {
//msaa_samples: SAMPLE_COUNT,
//depth_stencil_format: Some(TextureFormat::Depth24PlusStencil8),
..Default::default()
};
let egui_renderer = Renderer::new(device, output_color_format, options);
EguiState {
state: egui_state,
renderer: egui_renderer,
frame_started: false,
color_format: output_color_format,
}
}
pub fn resize(&mut self, _device: &Device, _width: u32, _height: u32) {}
pub fn handle_event(&mut self, window: &Window, event: &WindowEvent) {
let _ = self.state.on_window_event(window, event);
}
pub fn begin_frame(&mut self, window: &Window) {
let input = self.state.take_egui_input(window);
self.state.egui_ctx().begin_pass(input);
self.frame_started = true;
}
pub fn end_frame_and_draw(
&mut self,
device: &Device,
queue: &Queue,
encoder: &mut CommandEncoder,
window: &Window,
window_surface_view: &TextureView,
screen_descriptor: ScreenDescriptor,
) {
if !self.frame_started {
panic!("begin_frame must be called before end_frame_and_draw can be called!");
}
//self.ppp(screen_descriptor.pixels_per_point);
let full_output = self.state.egui_ctx().end_pass();
self.state
.handle_platform_output(window, full_output.platform_output);
let tris = self
.state
.egui_ctx()
.tessellate(full_output.shapes, self.state.egui_ctx().pixels_per_point());
for (id, image_delta) in &full_output.textures_delta.set {
self.renderer
.update_texture(device, queue, *id, image_delta);
}
self.renderer
.update_buffers(device, queue, encoder, &tris, &screen_descriptor);
let rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: window_surface_view,
resolve_target: None,
ops: egui_wgpu::wgpu::Operations {
load: egui_wgpu::wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
label: Some("egui main render pass"),
timestamp_writes: None,
occlusion_query_set: None,
});
self.renderer
.render(&mut rpass.forget_lifetime(), &tris, &screen_descriptor);
for x in &full_output.textures_delta.free {
self.renderer.free_texture(x)
}
self.frame_started = false;
}
}
pub struct CallbackFn<P>
where
P: Fn(PaintCallbackInfo, &mut RenderPass<'static>, &CallbackResources),
{
//pub : FnOnce(&Device, &Queue, &ScreenDescriptor, &mut CommandEncoder, &mut CallbackResources) -> Vec<>
pub paint_fn: P,
}
impl<P> CallbackTrait for CallbackFn<P>
where
P: Fn(PaintCallbackInfo, &mut RenderPass<'static>, &CallbackResources)
+ std::marker::Sync
+ std::marker::Send,
{
fn paint(
&self,
info: egui::PaintCallbackInfo,
render_pass: &mut wgpu::RenderPass<'static>,
callback_resources: &egui_wgpu::CallbackResources,
) {
(self.paint_fn)(info, render_pass, callback_resources)
}
}

74
src/lib.rs Normal file
View File

@ -0,0 +1,74 @@
pub mod egui_renderer;
pub mod state;
use std::sync::Arc;
use winit::{
application::ApplicationHandler,
event::WindowEvent,
event_loop::{self, EventLoop},
window::Window,
};
use crate::state::State;
pub fn run() -> anyhow::Result<()> {
env_logger::init();
let event_loop = EventLoop::with_user_event().build()?;
let mut app = App::default();
event_loop.run_app(&mut app)?;
Ok(())
}
// App struct
#[derive(Default)]
pub struct App {
state: Option<State>,
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &event_loop::ActiveEventLoop) {
// Create window
let window = Arc::new(
event_loop
.create_window(
Window::default_attributes()
.with_title("Wgpu Template")
.with_resizable(true),
)
.unwrap(),
);
let state = pollster::block_on(State::new(window.clone()));
self.state = Some(state);
window.request_redraw();
}
fn window_event(
&mut self,
event_loop: &event_loop::ActiveEventLoop,
_window_id: winit::window::WindowId,
event: winit::event::WindowEvent,
) {
let state = self.state.as_mut().unwrap();
state.handle_event(&event);
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::RedrawRequested => {
state.render();
state.get_window().request_redraw();
}
WindowEvent::Resized(size) => {
state.resize(size);
}
_ => {}
}
}
}

3
src/main.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() -> anyhow::Result<()> {
wgpu_template::run()
}

169
src/state.rs Normal file
View File

@ -0,0 +1,169 @@
use std::sync::Arc;
use cgmath::{Matrix4, Vector3};
use crevice::std430::AsStd430;
use egui_wgpu::ScreenDescriptor;
use wgpu::{Features, FeaturesWGPU, FeaturesWebGPU};
use winit::{event::WindowEvent, 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,
}
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,
features_webgpu: FeaturesWebGPU::empty(),
},
required_limits: wgpu::Limits {
max_push_constant_size: RayMarchingPushConstants::std430_size_static() 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 state = State {
egui_state: EguiState::new(&device, surface_format, &window),
window,
device,
queue,
size,
surface,
surface_format,
};
// 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();
}
pub fn handle_event(&mut self, event: &WindowEvent) {
self.egui_state.handle_event(&self.window, event);
}
pub fn render(&mut self) {
// 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());
// Create the renderpass which will clear the screen.
let 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::GREEN),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
// End the renderpass.
drop(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();
}
}
#[derive(AsStd430)]
pub struct RayMarchingPushConstants {
inverse_projection_matrix: Matrix4<f32>,
view_matrix: Matrix4<f32>,
camera_pos: Vector3<f32>,
}