dialog WIP paths consolidation and rendering
This commit is contained in:
276
src/render/billboard.rs
Normal file
276
src/render/billboard.rs
Normal file
@@ -0,0 +1,276 @@
|
||||
use crate::paths;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
const MAX_BUBBLES: usize = 8;
|
||||
const UNIFORM_STRIDE: usize = 256;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Pod, Zeroable)]
|
||||
pub struct BillboardVertex
|
||||
{
|
||||
pub position: [f32; 3],
|
||||
pub uv: [f32; 2],
|
||||
}
|
||||
|
||||
impl BillboardVertex
|
||||
{
|
||||
pub fn desc() -> wgpu::VertexBufferLayout<'static>
|
||||
{
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<BillboardVertex>() 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,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Uniform block layout must match `bubble.wgsl` exactly (including implicit WGSL padding).
|
||||
///
|
||||
/// WGSL layout (offsets in bytes):
|
||||
/// view_proj : mat4x4<f32> → offset 0, size 64
|
||||
/// size : vec2<f32> → offset 64, size 8
|
||||
/// body_frac : f32 → offset 72, size 4
|
||||
/// corner_r : f32 → offset 76, size 4
|
||||
/// border_w : f32 → offset 80, size 4
|
||||
/// [12 bytes WGSL padding to align vec4]
|
||||
/// fill_color : vec4<f32> → offset 96, size 16
|
||||
/// border_color : vec4<f32> → offset 112, size 16
|
||||
/// total WGSL struct: 128 bytes
|
||||
///
|
||||
/// Rust struct is padded to 256 bytes for the dynamic-offset alignment requirement.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Pod, Zeroable)]
|
||||
pub struct BubbleUniforms
|
||||
{
|
||||
pub view_proj: [[f32; 4]; 4],
|
||||
pub size: [f32; 2],
|
||||
pub body_frac: f32,
|
||||
pub corner_r: f32,
|
||||
pub border_w: f32,
|
||||
pub _pad1: [f32; 3],
|
||||
pub fill_color: [f32; 4],
|
||||
pub border_color: [f32; 4],
|
||||
pub _pad2: [f32; 32],
|
||||
}
|
||||
|
||||
const _: () = assert!(std::mem::size_of::<BubbleUniforms>() == UNIFORM_STRIDE);
|
||||
|
||||
pub struct BillboardDrawCall
|
||||
{
|
||||
/// Four corners in world space: top-left, top-right, bottom-right, bottom-left.
|
||||
pub vertices: [BillboardVertex; 4],
|
||||
pub uniforms: BubbleUniforms,
|
||||
}
|
||||
|
||||
pub struct BillboardPipeline
|
||||
{
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
vertex_buffer: wgpu::Buffer,
|
||||
index_buffer: wgpu::Buffer,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
impl BillboardPipeline
|
||||
{
|
||||
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self
|
||||
{
|
||||
let shader_source =
|
||||
std::fs::read_to_string(&paths::shaders::bubble()).expect("Failed to read bubble.wgsl");
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Bubble Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Billboard Bind Group Layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: true,
|
||||
min_binding_size: std::num::NonZeroU64::new(
|
||||
std::mem::size_of::<BubbleUniforms>() as u64,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Billboard Uniform Buffer"),
|
||||
size: (MAX_BUBBLES * UNIFORM_STRIDE) as wgpu::BufferAddress,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Billboard Bind Group"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &uniform_buffer,
|
||||
offset: 0,
|
||||
size: std::num::NonZeroU64::new(std::mem::size_of::<BubbleUniforms>() as u64),
|
||||
}),
|
||||
}],
|
||||
});
|
||||
|
||||
let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Billboard Vertex Buffer"),
|
||||
size: (MAX_BUBBLES * 4 * std::mem::size_of::<BillboardVertex>()) as wgpu::BufferAddress,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let indices: [u16; 6] = [0, 1, 2, 2, 3, 0];
|
||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Billboard Index Buffer"),
|
||||
contents: bytemuck::cast_slice(&indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Billboard Pipeline Layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Billboard Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[BillboardVertex::desc()],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: false,
|
||||
depth_compare: wgpu::CompareFunction::LessEqual,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
uniform_buffer,
|
||||
bind_group,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
queue: &wgpu::Queue,
|
||||
color_view: &wgpu::TextureView,
|
||||
depth_view: &wgpu::TextureView,
|
||||
calls: &[BillboardDrawCall],
|
||||
)
|
||||
{
|
||||
if calls.is_empty()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let n = calls.len().min(MAX_BUBBLES);
|
||||
|
||||
let mut all_vertices: Vec<BillboardVertex> = Vec::with_capacity(n * 4);
|
||||
for call in calls.iter().take(n)
|
||||
{
|
||||
all_vertices.extend_from_slice(&call.vertices);
|
||||
}
|
||||
queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&all_vertices));
|
||||
|
||||
for (i, call) in calls.iter().take(n).enumerate()
|
||||
{
|
||||
queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
(i * UNIFORM_STRIDE) as u64,
|
||||
bytemuck::cast_slice(&[call.uniforms]),
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Billboard Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: color_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
depth_slice: None,
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
|
||||
|
||||
for i in 0..n
|
||||
{
|
||||
let dynamic_offset = (i * UNIFORM_STRIDE) as u32;
|
||||
pass.set_bind_group(0, &self.bind_group, &[dynamic_offset]);
|
||||
pass.draw_indexed(0..6, (i * 4) as i32, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::paths;
|
||||
use crate::postprocess::ScreenVertex;
|
||||
|
||||
pub struct DebugOverlay
|
||||
@@ -12,7 +13,7 @@ impl DebugOverlay
|
||||
{
|
||||
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self
|
||||
{
|
||||
let shader_source = std::fs::read_to_string("src/shaders/debug_overlay.wgsl")
|
||||
let shader_source = std::fs::read_to_string(&paths::shaders::debug_overlay())
|
||||
.expect("Failed to read debug_overlay.wgsl");
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
pub mod billboard;
|
||||
mod bind_group;
|
||||
mod debug_overlay;
|
||||
mod pipeline;
|
||||
mod shadow;
|
||||
mod types;
|
||||
|
||||
pub use billboard::{BillboardDrawCall, BillboardPipeline};
|
||||
pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS};
|
||||
|
||||
use crate::entity::EntityHandle;
|
||||
|
||||
use crate::debug::DebugMode;
|
||||
use crate::paths;
|
||||
use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer};
|
||||
use crate::texture::{DitherTextures, FlowmapTexture};
|
||||
use pipeline::{
|
||||
@@ -34,6 +37,7 @@ pub struct Renderer
|
||||
wireframe_pipeline: Option<wgpu::RenderPipeline>,
|
||||
debug_lines_pipeline: Option<wgpu::RenderPipeline>,
|
||||
debug_overlay: Option<debug_overlay::DebugOverlay>,
|
||||
billboard_pipeline: BillboardPipeline,
|
||||
wireframe_supported: bool,
|
||||
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
@@ -162,7 +166,7 @@ impl Renderer
|
||||
};
|
||||
|
||||
let flowmap_texture =
|
||||
match FlowmapTexture::load(&device, &queue, "textures/terrain_flowmap.exr")
|
||||
match FlowmapTexture::load(&device, &queue, &paths::textures::terrain_flowmap())
|
||||
{
|
||||
Ok(texture) =>
|
||||
{
|
||||
@@ -179,7 +183,7 @@ impl Renderer
|
||||
}
|
||||
};
|
||||
|
||||
let blue_noise_data = image::open("textures/blue_noise.png")
|
||||
let blue_noise_data = image::open(&paths::textures::blue_noise())
|
||||
.expect("Failed to load blue noise texture")
|
||||
.to_luma8();
|
||||
let blue_noise_size = blue_noise_data.dimensions();
|
||||
@@ -490,6 +494,8 @@ impl Renderer
|
||||
&bind_group_layout,
|
||||
));
|
||||
|
||||
let billboard_pipeline = BillboardPipeline::new(&device, config.format);
|
||||
|
||||
let debug_overlay = Some(debug_overlay::DebugOverlay::new(&device, config.format));
|
||||
|
||||
let shadow_bind_group_layout =
|
||||
@@ -518,6 +524,7 @@ impl Renderer
|
||||
wireframe_pipeline,
|
||||
debug_lines_pipeline,
|
||||
debug_overlay,
|
||||
billboard_pipeline,
|
||||
wireframe_supported,
|
||||
uniform_buffer,
|
||||
bind_group_layout,
|
||||
@@ -561,6 +568,7 @@ impl Renderer
|
||||
camera_position: glam::Vec3,
|
||||
player_position: glam::Vec3,
|
||||
draw_calls: &[DrawCall],
|
||||
billboard_calls: &[BillboardDrawCall],
|
||||
time: f32,
|
||||
delta_time: f32,
|
||||
debug_mode: DebugMode,
|
||||
@@ -939,6 +947,14 @@ impl Renderer
|
||||
}
|
||||
}
|
||||
|
||||
self.billboard_pipeline.render(
|
||||
&mut encoder,
|
||||
&self.queue,
|
||||
&self.framebuffer.view,
|
||||
&self.framebuffer.depth_view,
|
||||
billboard_calls,
|
||||
);
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
|
||||
let frame = match self.surface.get_current_texture()
|
||||
@@ -1049,7 +1065,7 @@ impl Renderer
|
||||
match crate::texture::HeightmapTexture::load(
|
||||
&self.device,
|
||||
&self.queue,
|
||||
"textures/terrain_heightmap.exr",
|
||||
&paths::textures::terrain_heightmap(),
|
||||
)
|
||||
{
|
||||
Ok(heightmap) =>
|
||||
@@ -1151,6 +1167,7 @@ pub fn render(
|
||||
camera_position: glam::Vec3,
|
||||
player_position: glam::Vec3,
|
||||
draw_calls: &[DrawCall],
|
||||
billboard_calls: &[BillboardDrawCall],
|
||||
time: f32,
|
||||
delta_time: f32,
|
||||
debug_mode: DebugMode,
|
||||
@@ -1165,6 +1182,7 @@ pub fn render(
|
||||
camera_position,
|
||||
player_position,
|
||||
draw_calls,
|
||||
billboard_calls,
|
||||
time,
|
||||
delta_time,
|
||||
debug_mode,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::paths;
|
||||
use wesl::Wesl;
|
||||
|
||||
pub fn create_shadow_pipeline(
|
||||
@@ -5,9 +6,9 @@ pub fn create_shadow_pipeline(
|
||||
bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let compiler = Wesl::new(&paths::SHADERS_DIR);
|
||||
let shader_source = compiler
|
||||
.compile(&"package::shadow".parse().unwrap())
|
||||
.compile(&paths::shaders::SHADOW_PACKAGE.parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
@@ -70,9 +71,9 @@ pub fn create_main_pipeline(
|
||||
label: &str,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let compiler = Wesl::new(&paths::SHADERS_DIR);
|
||||
let shader_source = compiler
|
||||
.compile(&"package::main".parse().unwrap())
|
||||
.compile(&paths::shaders::MAIN_PACKAGE.parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
@@ -142,9 +143,9 @@ pub fn create_wireframe_pipeline(
|
||||
bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let compiler = Wesl::new(&paths::SHADERS_DIR);
|
||||
let shader_source = compiler
|
||||
.compile(&"package::main".parse().unwrap())
|
||||
.compile(&paths::shaders::MAIN_PACKAGE.parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
@@ -214,9 +215,9 @@ pub fn create_debug_lines_pipeline(
|
||||
bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let compiler = Wesl::new(&paths::SHADERS_DIR);
|
||||
let shader_source = compiler
|
||||
.compile(&"package::main".parse().unwrap())
|
||||
.compile(&paths::shaders::MAIN_PACKAGE.parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
@@ -286,9 +287,9 @@ pub fn create_snow_clipmap_pipeline(
|
||||
main_bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let compiler = Wesl::new(&paths::SHADERS_DIR);
|
||||
let shader_source = compiler
|
||||
.compile(&"package::main".parse().unwrap())
|
||||
.compile(&paths::shaders::MAIN_PACKAGE.parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
Reference in New Issue
Block a user