#[allow(unused_imports)] use log::{debug, error, info, trace, warn}; //--Internal imports-------------------------------------------------------------------------------- use super::{Sprite, Center}; use crate::{ texture::TextureHandle, renderer::{ utils::Mesh, WgpuRenderer, ModelMatrix, }, utils::{Size, Position, Pixel}, }; //--External imports-------------------------------------------------------------------------------- use wgpu; use std::cell::RefCell; thread_local!(static PIPELINE : RefCell> = RefCell::new(None)); //--TextureSprite struct---------------------------------------------------------------------------- pub struct TextureSprite { matrix: ModelMatrix, texture: TextureHandle, inner_size: Size, //TODO move to f32 mesh: Mesh, offset: Position, scale: f32, vertice_update_needed: bool, } impl TextureSprite { const INDICES: [u16; 6] = [ 0, 1, 2, 0, 2, 3, ]; pub fn new(texture: TextureHandle, size: Size, renderer: &WgpuRenderer) -> Self { // initialize pipeline if needed PIPELINE.with(|cell| { if cell.borrow().is_none() { cell.replace(Some(initialize_pipeline(renderer))); } }); let mut sprite = Self { matrix: ModelMatrix::default(renderer), texture, inner_size: size, mesh: Mesh::new(&renderer.device), offset: Position::origin(), scale: 1.0, vertice_update_needed: false, }; sprite.mesh.set_indices(renderer, &TextureSprite::INDICES); sprite.update_vertices(renderer); sprite } pub fn set_texture(&mut self, texture: TextureHandle, offset: Option, scale: f32) { self.texture = texture; self.offset = offset.unwrap_or(Position::origin()); self.scale = scale; self.vertice_update_needed = true; } pub fn set_pixel(&mut self, pos: Position, pix: Pixel) { //TODO check pos ? self.texture.set_pixel(self.offset + pos, pix); } pub fn for_each(&mut self, func: F) { //TODO check pos ? //TODO take scale into account self.texture.for_each_in_area(func, self.offset, self.inner_size); } pub fn fill(&mut self, pixel: Pixel) { self.texture.fill(pixel); } pub fn get_size(&self) -> Size { self.texture.get_size() } fn update_vertices(&mut self, renderer: &WgpuRenderer) { debug!("Updating vertices..."); let size = self.texture.get_size(); // compute normalized coordinates let x_size = self.inner_size.w as f32 / size.w as f32 * self.scale; let y_size = self.inner_size.h as f32 / size.h as f32 * self.scale; let x_offset = self.offset.x as f32 / size.w as f32; let y_offset = self.offset.y as f32 / size.h as f32; // compute mesh size //divide by 2 since the quad's coords go from -0.5 to 0.5 let w = self.inner_size.w as f32 / 2.0; let h = self.inner_size.h as f32 / 2.0; let mesh = [ TextureVertex { position: [-w, -h], tex_coords: [x_offset , y_offset + y_size], }, TextureVertex { position: [ w, -h], tex_coords: [x_offset + x_size, y_offset + y_size], }, TextureVertex { position: [ w, h], tex_coords: [x_offset + x_size, y_offset ], }, TextureVertex { position: [-w, h], tex_coords: [x_offset , y_offset ], }, ]; self.mesh.set_vertices(renderer, &mesh); self.vertice_update_needed = false; info!("Vertices updated!"); } } impl Sprite for TextureSprite { fn set_position(&mut self, position: Position) { self.matrix.set_position(position); } fn set_center(&mut self, center: Center) { let center_pos = match center { Center::Geometric => Position {x: 0, y: 0}, Center::TopLeft => Position { x: -(self.inner_size.w as i32) / 2, y: (self.inner_size.h as i32) / 2 }, Center::TopRight => Position { x: (self.inner_size.w as i32) / 2, y: (self.inner_size.h as i32) / 2 }, Center::BotLeft => Position { x: -(self.inner_size.w as i32) / 2, y: -(self.inner_size.h as i32) / 2 }, Center::BotRight => Position { x: (self.inner_size.w as i32) / 2, y: -(self.inner_size.h as i32) / 2 }, Center::Custom(pos) => pos, }; self.matrix.set_center(center_pos); } fn set_rotation(&mut self, rotation: f32) { self.matrix.set_rotation(rotation); } fn set_alpha(&mut self, _alpha: u8) { todo!(); } fn set_scale(&mut self, scale: f32) { self.matrix.set_scale(scale); } fn render(&mut self, renderer: &mut WgpuRenderer) { // update mesh if necessary if self.vertice_update_needed == true { self.update_vertices(renderer); } // update texture if necessary let texture = self.texture.texture(); if texture.borrow().is_synced == false { texture.borrow_mut().update(&mut renderer.queue); } // create command encoder let mut encoder = renderer.device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder"), }); // write render pass PIPELINE.with(|pipeline| { let pipeline = pipeline.borrow(); let texture_bind_group = &texture.borrow().bind_group; let view = renderer.create_texture_view(); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: true, }, })], depth_stencil_attachment: None, }); render_pass.set_pipeline(pipeline.as_ref().unwrap()); render_pass.set_vertex_buffer(0, self.mesh.get_vertex_buffer_slice()); render_pass.set_index_buffer(self.mesh.get_index_buffer_slice(), wgpu::IndexFormat::Uint16); render_pass.set_bind_group(0, self.matrix.get_uniform().get_bind_group(&renderer.queue), &[]); render_pass.set_bind_group(1, texture_bind_group, &[]); render_pass.draw_indexed(0..6, 0, 0..1); drop(render_pass); }); renderer.queue.submit(std::iter::once(encoder.finish())); } } //--Pipeline initialization------------------------------------------------------------------------- fn initialize_pipeline(renderer: &WgpuRenderer) -> wgpu::RenderPipeline { let shader = renderer.device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("texture shader"), source: wgpu::ShaderSource::Wgsl(include_str!("shaders/texture.wgsl").into()), }); let render_pipeline_layout = renderer.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("texture render pipeline layout"), bind_group_layouts: &[ &renderer.matrix_layout, &renderer.texture_layout, ], push_constant_ranges: &[], }); renderer.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("texture render pipeline"), layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: "vs_main", buffers: &[ TextureVertex::layout(), ], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { format: renderer.config.format, blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: None,//Some(wgpu::Face::Back), // Setting this to anything other than Fill requires // Features::NON_FILL_POLYGON_MODE polygon_mode: wgpu::PolygonMode::Fill, // Requires Features::DEPTH_CLIP_CONTROL unclipped_depth: false, // Requires Features::CONSERVATIVE_RASTERIZATION conservative: false, }, depth_stencil: None, // multisampling, we don't need it multisample: wgpu::MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false, }, multiview: None, }) } //--TextureVertex struct---------------------------------------------------------------------------- #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct TextureVertex { pub position: [f32; 2], pub tex_coords: [f32; 2], } impl TextureVertex { const ATTRIBS: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2]; pub fn layout<'a>() -> wgpu::VertexBufferLayout<'a> { wgpu::VertexBufferLayout { array_stride: std::mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &Self::ATTRIBS, } } }