pub mod cube; mod buffer; mod uniforms; mod vertex; pub use uniforms::Uniforms; use buffer::Buffer; use vertex::Vertex; use crate::wgpu; use crate::wgpu::util::DeviceExt; use iced::{Rectangle, Size}; const SKY_TEXTURE_SIZE: u32 = 128; pub struct Pipeline { pipeline: wgpu::RenderPipeline, vertices: wgpu::Buffer, cubes: Buffer, uniforms: wgpu::Buffer, uniform_bind_group: wgpu::BindGroup, depth_texture_size: Size, depth_view: wgpu::TextureView, depth_pipeline: DepthPipeline, } impl Pipeline { pub fn new( device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat, target_size: Size, ) -> Self { //vertices of one cube let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("cubes vertex buffer"), contents: bytemuck::cast_slice(&cube::Raw::vertices()), usage: wgpu::BufferUsages::VERTEX, }); //cube instance data let cubes_buffer = Buffer::new( device, "cubes instance buffer", std::mem::size_of::() as u64, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, ); //uniforms for all cubes let uniforms = device.create_buffer(&wgpu::BufferDescriptor { label: Some("cubes uniform buffer"), size: std::mem::size_of::() as u64, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); //depth buffer let depth_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("cubes depth texture"), size: wgpu::Extent3d { width: target_size.width, height: target_size.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); let normal_map_data = load_normal_map_data(); //normal map let normal_texture = device.create_texture_with_data( queue, &wgpu::TextureDescriptor { label: Some("cubes normal map texture"), size: wgpu::Extent3d { width: 1024, height: 1024, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }, wgpu::util::TextureDataOrder::LayerMajor, &normal_map_data, ); let normal_view = normal_texture.create_view(&wgpu::TextureViewDescriptor::default()); //skybox texture for reflection/refraction let skybox_data = load_skybox_data(); let skybox_texture = device.create_texture_with_data( queue, &wgpu::TextureDescriptor { label: Some("cubes skybox texture"), size: wgpu::Extent3d { width: SKY_TEXTURE_SIZE, height: SKY_TEXTURE_SIZE, depth_or_array_layers: 6, //one for each face of the cube }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }, wgpu::util::TextureDataOrder::LayerMajor, &skybox_data, ); let sky_view = skybox_texture.create_view(&wgpu::TextureViewDescriptor { label: Some("cubes skybox texture view"), dimension: Some(wgpu::TextureViewDimension::Cube), ..Default::default() }); let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("cubes skybox sampler"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::FilterMode::Linear, ..Default::default() }); let uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("cubes uniform bind group layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true, }, view_dimension: wgpu::TextureViewDimension::Cube, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler( wgpu::SamplerBindingType::Filtering, ), count: None, }, wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true, }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, ], }); let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("cubes uniform bind group"), layout: &uniform_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: uniforms.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&sky_view), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&sky_sampler), }, wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView( &normal_view, ), }, ], }); let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("cubes pipeline layout"), bind_group_layouts: &[&uniform_bind_group_layout], push_constant_ranges: &[], }); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("cubes shader"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( include_str!("../shaders/cubes.wgsl"), )), }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("cubes pipeline"), layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), buffers: &[Vertex::desc(), cube::Raw::desc()], compilation_options: wgpu::PipelineCompilationOptions::default(), }, primitive: wgpu::PrimitiveState::default(), depth_stencil: Some(wgpu::DepthStencilState { format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: true, depth_compare: wgpu::CompareFunction::Less, stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false, }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, blend: Some(wgpu::BlendState { color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::SrcAlpha, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add, }, alpha: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::One, operation: wgpu::BlendOperation::Max, }, }), write_mask: wgpu::ColorWrites::ALL, })], compilation_options: wgpu::PipelineCompilationOptions::default(), }), multiview: None, cache: None, }); let depth_pipeline = DepthPipeline::new( device, format, depth_texture.create_view(&wgpu::TextureViewDescriptor::default()), ); Self { pipeline, cubes: cubes_buffer, uniforms, uniform_bind_group, vertices, depth_texture_size: target_size, depth_view, depth_pipeline, } } fn update_depth_texture(&mut self, device: &wgpu::Device, size: Size) { if self.depth_texture_size.height != size.height || self.depth_texture_size.width != size.width { let text = device.create_texture(&wgpu::TextureDescriptor { label: Some("cubes depth texture"), size: wgpu::Extent3d { width: size.width, height: size.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); self.depth_view = text.create_view(&wgpu::TextureViewDescriptor::default()); self.depth_texture_size = size; self.depth_pipeline.update(device, &text); } } pub fn update( &mut self, device: &wgpu::Device, queue: &wgpu::Queue, target_size: Size, uniforms: &Uniforms, num_cubes: usize, cubes: &[cube::Raw], ) { //recreate depth texture if surface texture size has changed self.update_depth_texture(device, target_size); // update uniforms queue.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms)); //resize cubes vertex buffer if cubes amount changed let new_size = num_cubes * std::mem::size_of::(); self.cubes.resize(device, new_size as u64); //always write new cube data since they are constantly rotating queue.write_buffer(&self.cubes.raw, 0, bytemuck::cast_slice(cubes)); } pub fn render( &self, target: &wgpu::TextureView, encoder: &mut wgpu::CommandEncoder, viewport: Rectangle, num_cubes: u32, show_depth: bool, ) { { let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("cubes.pipeline.pass"), color_attachments: &[Some( wgpu::RenderPassColorAttachment { view: target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, }, )], depth_stencil_attachment: Some( wgpu::RenderPassDepthStencilAttachment { view: &self.depth_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Store, }), stencil_ops: None, }, ), timestamp_writes: None, occlusion_query_set: None, }); pass.set_scissor_rect( viewport.x, viewport.y, viewport.width, viewport.height, ); pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &self.uniform_bind_group, &[]); pass.set_vertex_buffer(0, self.vertices.slice(..)); pass.set_vertex_buffer(1, self.cubes.raw.slice(..)); pass.draw(0..36, 0..num_cubes); } if show_depth { self.depth_pipeline.render(encoder, target, viewport); } } } struct DepthPipeline { pipeline: wgpu::RenderPipeline, bind_group_layout: wgpu::BindGroupLayout, bind_group: wgpu::BindGroup, sampler: wgpu::Sampler, depth_view: wgpu::TextureView, } impl DepthPipeline { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, depth_texture: wgpu::TextureView, ) -> Self { let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("cubes.depth_pipeline.sampler"), ..Default::default() }); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("cubes.depth_pipeline.bind_group_layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler( wgpu::SamplerBindingType::NonFiltering, ), count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: false, }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, ], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("cubes.depth_pipeline.bind_group"), layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Sampler(&sampler), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView( &depth_texture, ), }, ], }); let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("cubes.depth_pipeline.layout"), bind_group_layouts: &[&bind_group_layout], push_constant_ranges: &[], }); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("cubes.depth_pipeline.shader"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( include_str!("../shaders/depth.wgsl"), )), }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("cubes.depth_pipeline.pipeline"), layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(), }, primitive: wgpu::PrimitiveState::default(), depth_stencil: Some(wgpu::DepthStencilState { format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: false, depth_compare: wgpu::CompareFunction::Less, stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }), multisample: wgpu::MultisampleState::default(), fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL, })], compilation_options: wgpu::PipelineCompilationOptions::default(), }), multiview: None, cache: None, }); Self { pipeline, bind_group_layout, bind_group, sampler, depth_view: depth_texture, } } pub fn update( &mut self, device: &wgpu::Device, depth_texture: &wgpu::Texture, ) { self.depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); self.bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("cubes.depth_pipeline.bind_group"), layout: &self.bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Sampler(&self.sampler), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView( &self.depth_view, ), }, ], }); } pub fn render( &self, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, viewport: Rectangle, ) { let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("cubes.pipeline.depth_pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some( wgpu::RenderPassDepthStencilAttachment { view: &self.depth_view, depth_ops: None, stencil_ops: None, }, ), timestamp_writes: None, occlusion_query_set: None, }); pass.set_scissor_rect( viewport.x, viewport.y, viewport.width, viewport.height, ); pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &self.bind_group, &[]); pass.draw(0..6, 0..1); } } fn load_skybox_data() -> Vec { let pos_x: &[u8] = include_bytes!("../../textures/skybox/pos_x.jpg"); let neg_x: &[u8] = include_bytes!("../../textures/skybox/neg_x.jpg"); let pos_y: &[u8] = include_bytes!("../../textures/skybox/pos_y.jpg"); let neg_y: &[u8] = include_bytes!("../../textures/skybox/neg_y.jpg"); let pos_z: &[u8] = include_bytes!("../../textures/skybox/pos_z.jpg"); let neg_z: &[u8] = include_bytes!("../../textures/skybox/neg_z.jpg"); let data: [&[u8]; 6] = [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z]; data.iter().fold(vec![], |mut acc, bytes| { let i = image::load_from_memory_with_format( bytes, image::ImageFormat::Jpeg, ) .unwrap() .to_rgba8() .into_raw(); acc.extend(i); acc }) } fn load_normal_map_data() -> Vec { let bytes: &[u8] = include_bytes!("../../textures/ice_cube_normal_map.png"); image::load_from_memory_with_format(bytes, image::ImageFormat::Png) .unwrap() .to_rgba8() .into_raw() }