render: implement sprite pivot, fix sprite centering in ortho projection
This commit is contained in:
parent
d1aee610cc
commit
a06c065337
|
@ -17,20 +17,18 @@ use crate::{
|
|||
render::{
|
||||
graph::{Node, NodeDesc, NodeType, SlotAttribute},
|
||||
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
|
||||
transform_buffer_storage::{TransformBuffers, TransformIndex},
|
||||
vertex::Vertex2D,
|
||||
},
|
||||
sprite::{AtlasSprite, Sprite},
|
||||
};
|
||||
|
||||
use super::{BasePassSlots, RenderAssets};
|
||||
use super::{BasePassSlots, InterpTransform, RenderAssets};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RenderJob {
|
||||
pub entity: Entity,
|
||||
pub shader_id: u64,
|
||||
pub asset_uuid: uuid::Uuid,
|
||||
pub transform_id: TransformIndex,
|
||||
pub atlas_frame_id: u64,
|
||||
}
|
||||
|
||||
|
@ -64,11 +62,8 @@ pub struct SpritePass {
|
|||
pipeline: Option<RenderPipeline>,
|
||||
texture_bgl: Option<Arc<wgpu::BindGroupLayout>>,
|
||||
jobs: VecDeque<RenderJob>,
|
||||
/// Buffer that stores a `Rect` with `min` and `max` set to zero.
|
||||
/// This can be used for sprites that are not from an atlas.
|
||||
atlas_frames_buf: Option<Arc<wgpu::Buffer>>,
|
||||
sprite_instances_buf: Option<Arc<wgpu::Buffer>>,
|
||||
|
||||
transform_buffers: Option<ResourceData>,
|
||||
texture_store: Option<ResourceData>,
|
||||
buffer_store: Option<ResourceData>,
|
||||
}
|
||||
|
@ -175,7 +170,7 @@ impl SpritePass {
|
|||
},
|
||||
);
|
||||
|
||||
let frames = self.atlas_frames_buf.as_ref().unwrap();
|
||||
let sprite_instances = self.sprite_instances_buf.as_ref().unwrap();
|
||||
let bgl = self.texture_bgl.as_ref().unwrap();
|
||||
let tex_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some(&format!("sprite_texture_bg_{}", uuid_str)),
|
||||
|
@ -192,7 +187,7 @@ impl SpritePass {
|
|||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &frames,
|
||||
buffer: &sprite_instances,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
|
@ -211,16 +206,13 @@ impl Node for SpritePass {
|
|||
) -> crate::render::graph::NodeDesc {
|
||||
let device = &graph.device;
|
||||
|
||||
let atlas_frames = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("default_sprite_atlas_frame"),
|
||||
size: std::mem::size_of::<URect>() as u64 * 1000,
|
||||
let sprite_instances = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("sprite_instances"),
|
||||
size: std::mem::size_of::<SpriteInstance>() as u64 * 1000,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
// write the rect for sprites that aren't part of the texture atlas.
|
||||
graph
|
||||
.queue
|
||||
.write_buffer(&atlas_frames, 0, bytemuck::bytes_of(&URect::ZERO));
|
||||
self.sprite_instances_buf = Some(Arc::new(sprite_instances));
|
||||
|
||||
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("bgl_sprite_main"),
|
||||
|
@ -243,7 +235,7 @@ impl Node for SpritePass {
|
|||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
|
@ -254,7 +246,6 @@ impl Node for SpritePass {
|
|||
],
|
||||
});
|
||||
self.texture_bgl = Some(Arc::new(bgl));
|
||||
self.atlas_frames_buf = Some(Arc::new(atlas_frames));
|
||||
|
||||
let mut desc = NodeDesc::new(NodeType::Render, None, vec![]);
|
||||
|
||||
|
@ -282,16 +273,12 @@ impl Node for SpritePass {
|
|||
|
||||
let diffuse_bgl = self.texture_bgl.clone().unwrap();
|
||||
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera).clone();
|
||||
let transforms = world
|
||||
.get_resource::<TransformBuffers>()
|
||||
.expect("Missing transform buffers");
|
||||
let transform_bgl = transforms.bindgroup_layout.clone();
|
||||
|
||||
self.pipeline = Some(RenderPipeline::create(
|
||||
device,
|
||||
&RenderPipelineDescriptor {
|
||||
label: Some("sprite_pass".into()),
|
||||
layouts: vec![diffuse_bgl, transform_bgl, camera_bgl],
|
||||
layouts: vec![diffuse_bgl, camera_bgl],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: VertexState {
|
||||
module: shader.clone(),
|
||||
|
@ -317,7 +304,6 @@ impl Node for SpritePass {
|
|||
},
|
||||
));
|
||||
|
||||
drop(transforms);
|
||||
world.add_resource_default_if_absent::<RenderAssets<SpriteTexture>>();
|
||||
let texture_store = world
|
||||
.get_resource_data::<RenderAssets<SpriteTexture>>().unwrap();
|
||||
|
@ -327,22 +313,17 @@ impl Node for SpritePass {
|
|||
let buffer_store = world
|
||||
.get_resource_data::<FxHashMap<Entity, SpriteBuffers>>().unwrap();
|
||||
self.buffer_store = Some(buffer_store.clone());
|
||||
|
||||
let transforms = world
|
||||
.get_resource_data::<TransformBuffers>()
|
||||
.expect("Missing transform buffers");
|
||||
self.transform_buffers = Some(transforms.clone());
|
||||
}
|
||||
|
||||
let mut v = Vec::with_capacity(500);
|
||||
let mut sprite_instances = Vec::with_capacity(500);
|
||||
|
||||
let world_tick = world.current_tick();
|
||||
let queue = &graph.queue;
|
||||
for (entity, (sprite, atlas_sprite), transform_idx, mut texture_store, mut buffer_store) in world
|
||||
for (entity, (sprite, atlas_sprite), interp_transform, mut texture_store, mut buffer_store) in world
|
||||
.view::<(
|
||||
Entities,
|
||||
Or<&Sprite, (&AtlasSprite, TickOf<AtlasSprite>)>,
|
||||
&TransformIndex,
|
||||
&InterpTransform,
|
||||
ResMut<RenderAssets<SpriteTexture>>,
|
||||
ResMut<FxHashMap<Entity, SpriteBuffers>>,
|
||||
)>()
|
||||
|
@ -374,13 +355,14 @@ impl Node for SpritePass {
|
|||
}
|
||||
}
|
||||
|
||||
if !buffer_store.contains_key(&entity) {
|
||||
let dim = rect.unwrap_or_else(|| {
|
||||
let dim = rect
|
||||
.map(|r| r.dimensions())
|
||||
.unwrap_or_else(|| {
|
||||
let i = image.dimensions();
|
||||
URect::new(0, 0, i.0, i.1)
|
||||
UVec2::new(i.0, i.1)
|
||||
});
|
||||
debug!("storing rect: {dim:?}");
|
||||
let dim = dim.dimensions();
|
||||
|
||||
if !buffer_store.contains_key(&entity) {
|
||||
let (vertex, index) = self.create_vertex_index_buffers(device, dim);
|
||||
buffer_store.insert(entity, SpriteBuffers { vertex_buffers: vertex, index_buffers: index });
|
||||
} else if let Some((ats, tick)) = &atlas_sprite {
|
||||
|
@ -393,29 +375,36 @@ impl Node for SpritePass {
|
|||
}
|
||||
}
|
||||
|
||||
let frame_id = match rect {
|
||||
Some(r) => {
|
||||
v.push(r);
|
||||
// No -1 here since the gpu buffer is already offset by 1
|
||||
// to store the default rect at the front
|
||||
v.len() as u64
|
||||
}
|
||||
None => 0
|
||||
let pivot = atlas_sprite.map(|ats| ats.0.pivot)
|
||||
// unwrap is safe since its either AtlasSprite or Sprite.
|
||||
.unwrap_or_else(|| sprite.unwrap().pivot)
|
||||
.as_vec();
|
||||
|
||||
let pivot_pos = dim.as_vec2() * (pivot - Vec2::splat(0.5));
|
||||
let transform = interp_transform.last_transform
|
||||
+ lyra_math::Transform::from_translation(Vec3::new(pivot_pos.x, pivot_pos.y, 0.0));
|
||||
|
||||
let inst = SpriteInstance {
|
||||
atlas_frame: rect.unwrap_or(URect::ZERO),
|
||||
transform: transform.calculate_mat4(),
|
||||
pivot,
|
||||
_padding: [0; 2],
|
||||
};
|
||||
sprite_instances.push(inst);
|
||||
let inst_id = sprite_instances.len() as u64 - 1;
|
||||
|
||||
self.jobs.push_back(RenderJob {
|
||||
entity,
|
||||
shader_id: 0,
|
||||
asset_uuid: texture_uuid,
|
||||
transform_id: *transform_idx,
|
||||
atlas_frame_id: frame_id,
|
||||
atlas_frame_id: inst_id,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
let buf = self.atlas_frames_buf.as_ref().unwrap();
|
||||
let buf = self.sprite_instances_buf.as_ref().unwrap();
|
||||
// skip default rect
|
||||
queue.write_buffer(buf, std::mem::size_of::<URect>() as _, bytemuck::cast_slice(&v));
|
||||
queue.write_buffer(buf, 0, bytemuck::cast_slice(&sprite_instances));
|
||||
}
|
||||
|
||||
fn execute(
|
||||
|
@ -430,8 +419,6 @@ impl Node for SpritePass {
|
|||
let texture_store: AtomicRef<RenderAssets<SpriteTexture>> = texture_store.get();
|
||||
let buffer_store = self.buffer_store.clone().unwrap();
|
||||
let buffer_store: AtomicRef<FxHashMap<Entity, SpriteBuffers>> = buffer_store.get();
|
||||
let transforms = self.transform_buffers.clone().unwrap();
|
||||
let transforms: AtomicRef<TransformBuffers> = transforms.get();
|
||||
|
||||
let vt = graph.view_target();
|
||||
let view = vt.render_view();
|
||||
|
@ -468,12 +455,7 @@ impl Node for SpritePass {
|
|||
.expect("failed to find SpriteTexture for job asset_uuid");
|
||||
pass.set_bind_group(0, &tex.texture_bg, &[]);
|
||||
|
||||
// Get the bindgroup for job's transform and bind to it using an offset.
|
||||
let bindgroup = transforms.bind_group(job.transform_id);
|
||||
let offset = transforms.buffer_offset(job.transform_id);
|
||||
pass.set_bind_group(1, bindgroup, &[offset]);
|
||||
|
||||
pass.set_bind_group(2, camera_bg, &[]);
|
||||
pass.set_bind_group(1, camera_bg, &[]);
|
||||
|
||||
// set vertex and index buffers
|
||||
let bufs = buffer_store
|
||||
|
@ -489,3 +471,12 @@ impl Node for SpritePass {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct SpriteInstance {
|
||||
atlas_frame: URect,
|
||||
transform: glam::Mat4,
|
||||
pivot: glam::Vec2,
|
||||
_padding: [u32; 2],
|
||||
}
|
|
@ -25,8 +25,8 @@ use crate::{
|
|||
/// transform is updated less often than rendering.
|
||||
#[derive(Clone, Debug, Component)]
|
||||
pub struct InterpTransform {
|
||||
last_transform: Transform,
|
||||
alpha: f32,
|
||||
pub last_transform: Transform,
|
||||
pub alpha: f32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||
|
|
|
@ -23,6 +23,12 @@ struct URect {
|
|||
max: vec2<u32>,
|
||||
}
|
||||
|
||||
struct SpriteInstance {
|
||||
atlas_frame: URect,
|
||||
transform: mat4x4<f32>,
|
||||
pivot: vec2<f32>,
|
||||
}
|
||||
|
||||
struct CameraUniform {
|
||||
view: mat4x4<f32>,
|
||||
inverse_projection: mat4x4<f32>,
|
||||
|
@ -33,21 +39,21 @@ struct CameraUniform {
|
|||
}
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<uniform> u_model_transform_data: TransformData;
|
||||
|
||||
@group(2) @binding(0)
|
||||
var<uniform> u_camera: CameraUniform;
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
in: VertexInput,
|
||||
) -> VertexOutput {
|
||||
let transform = u_sprite_instances[in.instance_index].transform;
|
||||
var world_position: vec4<f32> = transform * vec4<f32>(in.position, 1.0);
|
||||
|
||||
var out: VertexOutput;
|
||||
var world_position: vec4<f32> = u_model_transform_data.transform * vec4<f32>(in.position, 1.0);
|
||||
out.world_position = world_position.xyz;
|
||||
out.tex_coords = in.tex_coords;
|
||||
out.clip_position = u_camera.view_projection * world_position;
|
||||
out.instance_index = in.instance_index;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
@ -58,11 +64,12 @@ var t_diffuse: texture_2d<f32>;
|
|||
var s_diffuse: sampler;
|
||||
|
||||
@group(0) @binding(2)
|
||||
var<storage, read> u_sprite_atlas_frames: array<URect>;
|
||||
var<storage, read> u_sprite_instances: array<SpriteInstance>;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let frame = u_sprite_atlas_frames[in.instance_index];
|
||||
let sprite = u_sprite_instances[in.instance_index];
|
||||
let frame = sprite.atlas_frame;
|
||||
var region_coords = in.tex_coords;
|
||||
|
||||
if (frame.min.x != 0 || frame.min.y != 0 || frame.max.x != 0 || frame.max.y != 0) {
|
||||
|
|
|
@ -34,6 +34,7 @@ impl Default for ScaleMode {
|
|||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
|
||||
pub struct OrthographicProjection {
|
||||
pub viewport_origin: Vec2,
|
||||
pub scale_mode: ScaleMode,
|
||||
pub scale: f32,
|
||||
pub znear: f32,
|
||||
|
@ -43,6 +44,7 @@ pub struct OrthographicProjection {
|
|||
impl Default for OrthographicProjection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
viewport_origin: Vec2::new(0.5, 0.5),
|
||||
scale_mode: Default::default(),
|
||||
scale: 1.0,
|
||||
znear: 0.0,
|
||||
|
@ -53,52 +55,44 @@ impl Default for OrthographicProjection {
|
|||
|
||||
impl OrthographicProjection {
|
||||
fn get_rect(&self, viewport_size: Vec2) -> Rect {
|
||||
let origin;
|
||||
//let origin;
|
||||
let size;
|
||||
|
||||
match self.scale_mode {
|
||||
ScaleMode::Viewport => {
|
||||
origin = viewport_size * 0.5;
|
||||
size = viewport_size;
|
||||
},
|
||||
ScaleMode::Width(width) => {
|
||||
let aspect = viewport_size.x / viewport_size.y;
|
||||
let origin_x = width * 0.5;
|
||||
let scaled_height = width / aspect;
|
||||
let origin_y = scaled_height * 0.5;
|
||||
|
||||
origin = Vec2::new(origin_x, origin_y);
|
||||
size = Vec2::new(width, scaled_height);
|
||||
},
|
||||
ScaleMode::Height(height) => {
|
||||
let aspect = viewport_size.x / viewport_size.y;
|
||||
let origin_y = height * 0.5;
|
||||
let scaled_width = height * aspect;
|
||||
let origin_x = scaled_width * 0.5;
|
||||
|
||||
origin = Vec2::new(origin_x, origin_y);
|
||||
size = Vec2::new(scaled_width, height);
|
||||
},
|
||||
ScaleMode::Size(s) => {
|
||||
origin = s * 0.5;
|
||||
size = s;
|
||||
},
|
||||
ScaleMode::MaxSize(s) => {
|
||||
let clamped = s.min(viewport_size);
|
||||
origin = clamped * 0.5;
|
||||
size = clamped;
|
||||
},
|
||||
ScaleMode::MinSize(s) => {
|
||||
let clamped = s.max(viewport_size);
|
||||
origin = clamped * 0.5;
|
||||
size = clamped;
|
||||
}
|
||||
}
|
||||
|
||||
let origin = size * self.viewport_origin;
|
||||
|
||||
Rect::new(self.scale * -origin.x,
|
||||
self.scale * -origin.y,
|
||||
self.scale * size.x - origin.x,
|
||||
self.scale * size.y - origin.y)
|
||||
self.scale * (size.x - origin.x),
|
||||
self.scale * (size.y - origin.y))
|
||||
}
|
||||
|
||||
pub fn to_mat(&self, viewport_size: Vec2) -> Mat4 {
|
||||
|
|
|
@ -12,7 +12,7 @@ use tracing::error;
|
|||
|
||||
use crate::DeltaTime;
|
||||
|
||||
use super::{AtlasSprite, TextureAtlas};
|
||||
use super::{AtlasSprite, Pivot, TextureAtlas};
|
||||
|
||||
/// A struct describing an animation of a Sprite.
|
||||
///
|
||||
|
@ -266,6 +266,7 @@ fn system_animation_entity_impl(
|
|||
let sprite = AtlasSprite {
|
||||
atlas: animations.atlas.clone(),
|
||||
sprite: rect,
|
||||
pivot: Pivot::default(),
|
||||
};
|
||||
|
||||
commands.insert(en, sprite);
|
||||
|
@ -286,6 +287,7 @@ fn system_animation_entity_impl(
|
|||
let new_sprite = AtlasSprite {
|
||||
atlas: animations.atlas.clone(),
|
||||
sprite: rect,
|
||||
pivot: Pivot::default(),
|
||||
};
|
||||
|
||||
let sprite = sprite.as_mut().unwrap();
|
||||
|
|
|
@ -24,8 +24,9 @@ pub enum Pivot {
|
|||
BottomLeft,
|
||||
BottomRight,
|
||||
BottomCenter,
|
||||
/// A custom anchor point relative to top left.
|
||||
/// Top left is `(0.0, 0.0)`.
|
||||
/// A custom anchor point.
|
||||
///
|
||||
/// Top left is (-0.5, 0.5), center is (0.0, 0.0).
|
||||
Custom(Vec2)
|
||||
}
|
||||
|
||||
|
@ -35,15 +36,15 @@ impl Pivot {
|
|||
/// The point is offset from the top left `(0.0, 0.0)`.
|
||||
pub fn as_vec(&self) -> Vec2 {
|
||||
match self {
|
||||
Pivot::Center => Vec2::new(0.5, 0.5),
|
||||
Pivot::CenterLeft => Vec2::new(0.0, 0.5),
|
||||
Pivot::CenterRight => Vec2::new(1.0, 0.5),
|
||||
Pivot::TopLeft => Vec2::ZERO,
|
||||
Pivot::TopRight => Vec2::new(1.0, 0.0),
|
||||
Pivot::Center => Vec2::ZERO,
|
||||
Pivot::CenterLeft => Vec2::new(-0.5, 0.0),
|
||||
Pivot::CenterRight => Vec2::new(0.5, 0.0),
|
||||
Pivot::TopLeft => Vec2::new(-0.5, 0.5),
|
||||
Pivot::TopRight => Vec2::new(0.5, 0.5),
|
||||
Pivot::TopCenter => Vec2::new(0.0, 0.5),
|
||||
Pivot::BottomLeft => Vec2::new(0.0, 1.0),
|
||||
Pivot::BottomRight => Vec2::new(1.0, 1.0),
|
||||
Pivot::BottomCenter => Vec2::new(0.5, 1.0),
|
||||
Pivot::BottomLeft => Vec2::new(-0.5, -0.5),
|
||||
Pivot::BottomRight => Vec2::new(0.5, -0.5),
|
||||
Pivot::BottomCenter => Vec2::new(0.0, -0.5),
|
||||
Pivot::Custom(v) => *v,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ use lyra_math::URect;
|
|||
use lyra_reflect::Reflect;
|
||||
use lyra_resource::ResHandle;
|
||||
|
||||
use super::Pivot;
|
||||
|
||||
/// A texture atlas of multiple sprites.
|
||||
#[derive(Clone, Component, Reflect)]
|
||||
pub struct TextureAtlas {
|
||||
|
@ -60,17 +62,19 @@ impl lyra_resource::ResourceData for TextureAtlas {
|
|||
pub struct AtlasSprite {
|
||||
pub atlas: ResHandle<TextureAtlas>,
|
||||
pub sprite: URect,
|
||||
pub pivot: Pivot,
|
||||
}
|
||||
|
||||
impl AtlasSprite {
|
||||
#[inline(always)]
|
||||
pub fn from_atlas_index(atlas: ResHandle<TextureAtlas>, i: u32) -> Self {
|
||||
let a = atlas.data_ref().unwrap();
|
||||
let rect = a.frames.get(i as usize).cloned().unwrap(); //index_rect(i);
|
||||
let rect = a.frames.get(i as usize).cloned().unwrap();
|
||||
|
||||
Self {
|
||||
atlas: atlas.clone(),
|
||||
sprite: rect,
|
||||
pivot: Pivot::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,3 +137,25 @@ impl std::ops::Add for Transform {
|
|||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for Transform {
|
||||
type Output = Transform;
|
||||
|
||||
fn sub(mut self, rhs: Self) -> Self::Output {
|
||||
self.translation -= rhs.translation;
|
||||
self.rotation *= rhs.rotation;
|
||||
self.scale *= rhs.scale;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul for Transform {
|
||||
type Output = Transform;
|
||||
|
||||
fn mul(mut self, rhs: Self) -> Self::Output {
|
||||
self.translation *= rhs.translation;
|
||||
self.rotation *= rhs.rotation;
|
||||
self.scale *= rhs.scale;
|
||||
self
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue