diff --git a/kubi/shaders/selection_box.wgsl b/kubi/shaders/selection_box.wgsl new file mode 100644 index 0000000..09a097e --- /dev/null +++ b/kubi/shaders/selection_box.wgsl @@ -0,0 +1,27 @@ +struct CameraUniform { + view_proj: mat4x4, +}; + +@group(0) @binding(0) +var camera: CameraUniform; + +struct SboxUniform { + position: vec3, +}; + +@group(1) @binding(0) +var sbox: SboxUniform; + +@vertex +fn vs_main( + @location(0) position: vec3, +) -> @builtin(position) vec4 { + return camera.view_proj * vec4(position + sbox.position, 1.0); +} + +@fragment +fn fs_main( + @builtin(position) in: vec4, +) -> @location(0) vec4 { + return vec4(0.0, 0.0, 0.0, 0.5); +} diff --git a/kubi/src/rendering.rs b/kubi/src/rendering.rs index e194906..688aa9d 100644 --- a/kubi/src/rendering.rs +++ b/kubi/src/rendering.rs @@ -1,15 +1,16 @@ -use shipyard::{AllStoragesViewMut, IntoIter, IntoWorkload, Unique, UniqueView, UniqueViewMut, View, Workload, WorkloadModificator}; +use shipyard::{AllStoragesViewMut, IntoIter, IntoWorkload, SystemModificator, Unique, UniqueView, UniqueViewMut, View, Workload, WorkloadModificator}; use winit::dpi::PhysicalSize; use glam::Vec3; use crate::{events::WindowResizedEvent, state::is_ingame}; mod renderer; mod primitives; +mod selection_box; pub use renderer::Renderer; pub mod background; pub mod world; -pub mod camera; +pub mod camera_uniform; pub mod depth; pub struct BufferPair { @@ -28,14 +29,15 @@ pub struct RenderCtx<'a> { pub surface_view: &'a wgpu::TextureView, } -//TODO run init_world_render_state only once ingame? +//TODO run init_world_render_state, init_selection_box_state, etc. only once ingame? pub fn init_rendering() -> Workload { ( depth::init_depth_texture, - camera::init_camera_uniform_buffer, - world::init_world_render_state, //requires depth and camera buffers + camera_uniform::init_camera_uniform_buffer, + world::init_world_render_state, //req: depth, camera primitives::init_primitives, + selection_box::init_selection_box_render_state, //req: depth, camera, primitives ).into_sequential_workload() } @@ -48,7 +50,8 @@ pub fn update_rendering_early() -> Workload { pub fn update_rendering_late() -> Workload { ( - camera::update_camera_uniform_buffer, + camera_uniform::update_camera_uniform_buffer, + selection_box::update_selection_box_render_state.run_if(is_ingame), ).into_workload() } @@ -70,6 +73,7 @@ pub fn render_master(storages: AllStoragesViewMut) { storages.run_with_data(background::clear_bg, &mut data); if storages.run(is_ingame) { storages.run_with_data(world::draw_world, &mut data); + storages.run_with_data(selection_box::draw_selection_box, &mut data); } renderer.queue().submit([encoder.finish()]); diff --git a/kubi/src/rendering/camera.rs b/kubi/src/rendering/camera_uniform.rs similarity index 97% rename from kubi/src/rendering/camera.rs rename to kubi/src/rendering/camera_uniform.rs index 8cd815b..03bb3d7 100644 --- a/kubi/src/rendering/camera.rs +++ b/kubi/src/rendering/camera_uniform.rs @@ -11,7 +11,6 @@ pub struct CameraUniformData { pub view_proj: [f32; 4 * 4], } - //TODO if multiple cameras, buffer per camera #[derive(Unique)] pub struct CameraUniformBuffer { diff --git a/kubi/src/rendering/primitives.rs b/kubi/src/rendering/primitives.rs index 8dfbfa4..c7e85f3 100644 --- a/kubi/src/rendering/primitives.rs +++ b/kubi/src/rendering/primitives.rs @@ -2,6 +2,7 @@ use bytemuck::{Pod, Zeroable}; use shipyard::{IntoWorkload, Workload}; mod cube; +pub use cube::CubePrimitive; pub fn init_primitives() -> Workload { ( diff --git a/kubi/src/rendering/selection_box.rs b/kubi/src/rendering/selection_box.rs index c434527..d33b4c7 100644 --- a/kubi/src/rendering/selection_box.rs +++ b/kubi/src/rendering/selection_box.rs @@ -1,9 +1,65 @@ +use shipyard::{AllStoragesView, IntoIter, Unique, UniqueView, View}; +use wgpu::RenderPassDescriptor; +use crate::{player::MainPlayer, world::raycast::LookingAtBlock}; +use super::{camera_uniform::CameraUniformBuffer, depth::DepthTexture, primitives::CubePrimitive, RenderCtx}; + mod pipeline; +mod uniform; + +use uniform::SelectionBoxUniform; + +#[derive(Unique)] +pub struct SboxRenderState { + pipeline: wgpu::RenderPipeline, + uniform: SelectionBoxUniform, +} + +pub fn init_selection_box_render_state(storages: AllStoragesView) { + let uniform = storages.run(uniform::init_selection_box_uniform); + let pipeline = storages.run_with_data(pipeline::init_selection_box_pipeline, &uniform); + storages.add_unique(SboxRenderState { pipeline, uniform }); +} + +pub use uniform::update_selection_box_uniform + as update_selection_box_render_state; pub fn draw_selection_box( ctx: &mut RenderCtx, + state: UniqueView, + depth: UniqueView, + cube: UniqueView, + camera_ubo: UniqueView, lookat: View, - camera: View, + player: View, ) { - //TODO + let Some((LookingAtBlock(Some(_)), _)) = (&lookat, &player).iter().next() else { + return + }; + let mut rpass = ctx.encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("rpass_selection_box"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: ctx.surface_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &depth.depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Discard, + }), + stencil_ops: None, + }), + ..Default::default() + }); + + rpass.set_pipeline(&state.pipeline); + rpass.set_bind_group(0, &camera_ubo.camera_bind_group, &[]); + rpass.set_bind_group(1, &state.uniform.bind_group, &[]); + rpass.set_index_buffer(cube.0.index.slice(..), wgpu::IndexFormat::Uint16); + rpass.set_vertex_buffer(0, cube.0.vertex.slice(..)); + rpass.draw_indexed(0..cube.0.index_len, 0, 0..1); } diff --git a/kubi/src/rendering/selection_box/pipeline.rs b/kubi/src/rendering/selection_box/pipeline.rs index e69de29..352ce83 100644 --- a/kubi/src/rendering/selection_box/pipeline.rs +++ b/kubi/src/rendering/selection_box/pipeline.rs @@ -0,0 +1,68 @@ +use shipyard::UniqueView; +use crate::rendering::{ + camera_uniform::CameraUniformBuffer, + depth::DepthTexture, + primitives::PrimitiveVertex, + Renderer +}; + +pub fn init_selection_box_pipeline( + uniform: &super::uniform::SelectionBoxUniform, + ren: UniqueView, + depth: UniqueView, + camera_ubo: UniqueView, +) -> wgpu::RenderPipeline { + let shader = ren.device().create_shader_module( + wgpu::include_wgsl!("../../../shaders/selection_box.wgsl") + ); + + let selection_box_pipeline_layout = ren.device().create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("selection_box_pipeline_layout"), + bind_group_layouts: &[ + &camera_ubo.camera_bind_group_layout, + &uniform.bind_group_layout, + ], + push_constant_ranges: &[], + }); + + ren.device().create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("selection_box_pipeline"), + layout: Some(&selection_box_pipeline_layout), + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + compilation_options: wgpu::PipelineCompilationOptions::default(), + targets: &[Some(wgpu::ColorTargetState { + format: ren.surface_config().format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::COLOR, + })], + }), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + compilation_options: wgpu::PipelineCompilationOptions::default(), + buffers: &[ + PrimitiveVertex::LAYOUT, + ], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + cull_mode: Some(wgpu::Face::Back), + front_face: wgpu::FrontFace::Ccw, + unclipped_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: depth.depth_texture.format(), + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState::default(), + multiview: None, + }) +} diff --git a/kubi/src/rendering/selection_box/uniform.rs b/kubi/src/rendering/selection_box/uniform.rs new file mode 100644 index 0000000..8ee8f5e --- /dev/null +++ b/kubi/src/rendering/selection_box/uniform.rs @@ -0,0 +1,79 @@ +use glam::Vec3; +use shipyard::{IntoIter, UniqueView, View}; +use bytemuck::{Pod, Zeroable}; +use crate::{ + player::MainPlayer, + rendering::Renderer, + world::raycast::LookingAtBlock, +}; +use super::SboxRenderState; + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +pub struct SelectionBoxUniformData { + pub position: [f32; 3], + pub _padding: u32, +} + +pub struct SelectionBoxUniform { + pub buffer: wgpu::Buffer, + pub bind_group_layout: wgpu::BindGroupLayout, + pub bind_group: wgpu::BindGroup, +} + +pub fn init_selection_box_uniform(renderer: UniqueView) -> SelectionBoxUniform { + let buffer = renderer.device().create_buffer(&wgpu::BufferDescriptor { + label: Some("selection_box_uniform"), + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let bind_group_layout = renderer.device().create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("selection_box_bind_group_layout"), + 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, + }], + }); + + let bind_group = renderer.device().create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("selection_box_bind_group"), + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + SelectionBoxUniform { + buffer, + bind_group_layout, + bind_group, + } +} + +pub fn update_selection_box_uniform( + renderer: UniqueView, + state: UniqueView, + lookat: View, + player: View, +) { + //TODO: only update if changed + if let Some((LookingAtBlock(Some(lookat)), _)) = (&lookat, &player).iter().next() { + renderer.queue().write_buffer( + &state.uniform.buffer, + 0, + bytemuck::cast_slice(&[SelectionBoxUniformData { + position: (lookat.position.floor() + Vec3::splat(0.5)).to_array(), + _padding: 0, + }]), + ); + }; +} diff --git a/kubi/src/rendering/world.rs b/kubi/src/rendering/world.rs index e25d545..5e57781 100644 --- a/kubi/src/rendering/world.rs +++ b/kubi/src/rendering/world.rs @@ -6,7 +6,7 @@ use crate::{ prefabs::TexturePrefabs, world::{ChunkMeshStorage, ChunkStorage}, }; -use super::{camera::CameraUniformBuffer, depth::DepthTexture, RenderCtx}; +use super::{camera_uniform::CameraUniformBuffer, depth::DepthTexture, RenderCtx}; mod pipeline; mod vertex; @@ -39,7 +39,7 @@ pub fn draw_world( let camera = camera.iter().next().expect("No cameras in the scene"); let mut render_pass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("draw_world"), + label: Some("rpass_draw_world"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: ctx.surface_view, resolve_target: None, diff --git a/kubi/src/rendering/world/pipeline.rs b/kubi/src/rendering/world/pipeline.rs index 040ed4d..9a63f65 100644 --- a/kubi/src/rendering/world/pipeline.rs +++ b/kubi/src/rendering/world/pipeline.rs @@ -1,7 +1,7 @@ use shipyard::UniqueView; use crate::{ prefabs::TexturePrefabs, - rendering::{camera::CameraUniformBuffer, depth::DepthTexture, world::ChunkVertex, Renderer} + rendering::{camera_uniform::CameraUniformBuffer, depth::DepthTexture, world::ChunkVertex, Renderer} }; pub fn init_world_pipeline( @@ -33,7 +33,7 @@ pub fn init_world_pipeline( targets: &[Some(wgpu::ColorTargetState { format: ren.surface_config().format, blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, + write_mask: wgpu::ColorWrites::COLOR, })], }), vertex: wgpu::VertexState {