canvas/src/sprite/texture_sprite.rs

324 lines
10 KiB
Rust

#[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<Option<wgpu::RenderPipeline>> = RefCell::new(None));
//--TextureSprite struct----------------------------------------------------------------------------
pub struct TextureSprite {
matrix: ModelMatrix,
texture: TextureHandle,
inner_size: Size, //TODO move to f32
mesh: Mesh<TextureVertex, 4, 6>,
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<Position>, 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<F: FnMut(&mut Pixel)>(&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::<TextureVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &Self::ATTRIBS,
}
}
}