Skip to content

Commit

Permalink
Add demo raymarching
Browse files Browse the repository at this point in the history
  • Loading branch information
Azkellas committed Sep 25, 2023
1 parent c838d23 commit f5e5f87
Show file tree
Hide file tree
Showing 9 changed files with 582 additions and 7 deletions.
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ wgpu.workspace = true
egui.workspace = true
pollster.workspace = true
log.workspace = true
bytemuck = "1.14.0"
bytemuck = { version = "1.14.0", features = ["derive"] }
instant = "0.1.12"
nanorand = { version = "0.7", default-features = false, features = ["wyrand"] }

Expand Down
2 changes: 1 addition & 1 deletion lib/src/demo_boids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl Program for DemoBoidsProgram {
settings,
compute_pass,
render_pass,
frame_rate: FrameRate::default(),
frame_rate: FrameRate::new(500),
last_update: instant::Instant::now(),
})
}
Expand Down
288 changes: 288 additions & 0 deletions lib/src/demo_raymarching.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
use wgpu::util::DeviceExt;

use crate::frame_rate::FrameRate;
use crate::program::{Program, ProgramError};
use crate::shader_builder::ShaderBuilder;

/// A simple struct to store a wgpu pass with a uniform buffer.
#[derive(Debug)]
pub struct Pass {
/// Pipeline that will be called to render the pass
pub pipeline: wgpu::RenderPipeline,
/// Buffer bind group for this pass.
pub bind_group: wgpu::BindGroup,
/// Single uniform buffer for this pass.
pub uniform_buf: wgpu::Buffer,
// Index buffer.
pub index_buffer: wgpu::Buffer,
// Vertex buffer.
pub vertex_buffer: wgpu::Buffer,
//
pub index_count: u32,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
position: [f32; 3],
}

// lib.rs
impl Vertex {
fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
}],
}
}
}

/// Demo raymarching program.
/// Everything is done in the shader.
/// Provides both 2d and 3d raymarching.
#[derive(Debug)]
pub struct DemoRaymarchingProgram {
render_pass: Pass,
_start_time: instant::Instant, // std::time::Instant is not compatible with wasm
last_update: instant::Instant,
elapsed: f32, // elapsed take the speed into consideration
frame_rate: FrameRate,
size: [f32; 2],
}

impl Program for DemoRaymarchingProgram {
/// Create program.
/// Assume the `render_pipeline` will be properly initialized.
fn init(
surface: &wgpu::Surface,
device: &wgpu::Device,
adapter: &wgpu::Adapter,
) -> Result<Self, ProgramError> {
let render_pass = Self::create_render_pass(surface, device, adapter)?;

Ok(Self {
render_pass,
_start_time: instant::Instant::now(),
last_update: instant::Instant::now(),
elapsed: 0.0,
frame_rate: FrameRate::new(200),
size: [0.0, 0.0],
})
}

/// Get program name.
fn get_name(&self) -> &'static str {
"Demo raymarching"
}

/// Recreate render pass.
fn update_passes(
&mut self,
surface: &wgpu::Surface,
device: &wgpu::Device,
adapter: &wgpu::Adapter,
) -> Result<(), ProgramError> {
self.render_pass = Self::create_render_pass(surface, device, adapter)?;
Ok(())
}

// Resize owned textures if needed, nothing for the demo here.
fn resize(
&mut self,
surface_configuration: &wgpu::SurfaceConfiguration,
_device: &wgpu::Device,
_queue: &wgpu::Queue,
) {
self.size[0] = surface_configuration.width as f32;
self.size[1] = surface_configuration.height as f32;
}

/// Update program before rendering.
fn update(&mut self, queue: &wgpu::Queue) {
// Set the edge count of the regular raymarching.
// This is not exposed in the ui on purpose to demonstrate the rust hot reload.

// update elapsed time, taking speed into consideration.
let last_frame_duration = self.last_update.elapsed().as_secs_f32();
self.elapsed += last_frame_duration;
self.frame_rate.update(last_frame_duration);
self.last_update = instant::Instant::now();
queue.write_buffer(
&self.render_pass.uniform_buf,
0,
bytemuck::cast_slice(&[self.elapsed, self.size[0], self.size[1], 0.0]),
);
}

/// Render program.
fn render(&self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
// Create a command encoder.
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });

{
// render pass.
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: true,
},
})],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&self.render_pass.pipeline);
render_pass.set_bind_group(0, &self.render_pass.bind_group, &[]);
render_pass.set_vertex_buffer(0, self.render_pass.vertex_buffer.slice(..));
render_pass.set_index_buffer(
self.render_pass.index_buffer.slice(..),
wgpu::IndexFormat::Uint16,
); // 1.
render_pass.draw_indexed(0..self.render_pass.index_count, 0, 0..1); // 2.
}

queue.submit(Some(encoder.finish()));
}

/// Draw ui with egui.
fn draw_ui(&mut self, ui: &mut egui::Ui) {
ui.heading("Settings");
ui.separator();
ui.label(std::format!("framerate: {:.0}fps", self.frame_rate.get()));
}
}

impl DemoRaymarchingProgram {
/// Create render pipeline.
/// In debug mode it will return a `ProgramError` if it failed compiling a shader
/// In release/wasm, il will crash since wgpu does not return errors in such situations.
fn create_render_pipeline(
surface: &wgpu::Surface,
device: &wgpu::Device,
adapter: &wgpu::Adapter,
uniforms_bind_group_layout: &wgpu::BindGroupLayout,
) -> Result<wgpu::RenderPipeline, ProgramError> {
let shader = ShaderBuilder::create_module(device, "demo_raymarching/draw.wgsl")?;
// let shader = ShaderBuilder::create_module(device, "test_preprocessor/draw.wgsl")?; // uncomment to test preprocessor

let swapchain_capabilities = surface.get_capabilities(adapter);
let swapchain_format = swapchain_capabilities.formats[0];

let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[uniforms_bind_group_layout],
push_constant_ranges: &[],
});

let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[Vertex::desc()],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(swapchain_format.into())],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});

Ok(pipeline)
}

/// Create render pass.
/// Will return an error in debug, and crash in release/wasm if a shader is malformed.
fn create_render_pass(
surface: &wgpu::Surface,
device: &wgpu::Device,
adapter: &wgpu::Adapter,
) -> Result<Pass, ProgramError> {
// create uniform buffer.
let uniforms = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Camera Buffer"),
contents: bytemuck::cast_slice(&[0.0, 0.0, 0.0, 0.0]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});

let uniforms_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: Some("uniforms_bind_group_layout"),
});

let uniforms_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &uniforms_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniforms.as_entire_binding(),
}],
label: Some("uniforms_bind_group"),
});

// lib.rs
const VERTICES: &[Vertex] = &[
Vertex {
position: [-1.0, -1.0, 0.0],
},
Vertex {
position: [-1.0, 1.0, 0.0],
},
Vertex {
position: [1.0, 1.0, 0.0],
},
Vertex {
position: [1.0, -1.0, 0.0],
},
];

let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(VERTICES),
usage: wgpu::BufferUsages::VERTEX,
});

const INDICES: &[u16] = &[1, 0, 2, 2, 0, 3];
let index_count = INDICES.len() as u32;

let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(INDICES),
usage: wgpu::BufferUsages::INDEX,
});

let pipeline =
Self::create_render_pipeline(surface, device, adapter, &uniforms_bind_group_layout)?;

Ok(Pass {
pipeline,
bind_group: uniforms_bind_group,
uniform_buf: uniforms,
index_buffer,
vertex_buffer,
index_count,
})
}
}
11 changes: 6 additions & 5 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
//! To avoid name clashes, the functions in this file
//! should not share names with other functions in the library.
pub mod demo_boids;
pub mod demo_polygon;
mod demo_boids;
mod demo_polygon;
mod demo_raymarching;

mod frame_rate;
pub mod program;
Expand All @@ -14,9 +15,9 @@ mod shader_builder;
use crate::program::{Program, ProgramError};

/// Specify which program we want to run here.
/// This should also be specified in `src/hot_lib.rs`
// pub use crate::demo_polygon::DemoPolygonProgram as CurrentProgram;
pub use crate::demo_boids::DemoBoidsProgram as CurrentProgram;
pub use crate::demo_polygon::DemoPolygonProgram as CurrentProgram;
// pub use crate::demo_boids::DemoBoidsProgram as CurrentProgram;
// pub use crate::demo_raymarching::DemoRaymarchingProgram as CurrentProgram;

/// Hot-reloading does not support generics, so we need to specialize
/// the functions we want to call from the outside.
Expand Down
19 changes: 19 additions & 0 deletions shaders/demo_raymarching/common.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Vertex shader
struct Uniforms {
elapsed: f32,
width: f32,
height: f32,
_padding: f32, // padding to 16 bytes, required for WebGL.
};

struct VertexInput {
@location(0) position: vec3<f32>,
};

struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
};

@group(0) @binding(0)
var<uniform> uniforms: Uniforms;

24 changes: 24 additions & 0 deletions shaders/demo_raymarching/draw.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#import "demo_raymarching/common.wgsl"

#import "demo_raymarching/draw_2d.wgsl"
#import "demo_raymarching/draw_3d.wgsl"

@vertex
fn vs_main(
model: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.clip_position = vec4<f32>(model.position, 1.0);
return out;
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
// let color = sdf_2d(in.clip_position.xy);

let xy = in.clip_position.xy / vec2<f32>(uniforms.width, uniforms.height);
let color = sdf_3d(in.clip_position.xy);
return color;
}


Loading

0 comments on commit f5e5f87

Please sign in to comment.