324 lines
10 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|
|
|