render: create a transform pass for sending transforms to the GPU
CI / build (push) Failing after 3m33s Details

This commit is contained in:
SeanOMik 2024-11-01 11:05:51 -04:00
parent 7ae0eae6ac
commit f02d3c6b2f
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
13 changed files with 572 additions and 189 deletions

10
examples/2d/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "simple_scene"
version = "0.1.0"
edition = "2021"
[dependencies]
lyra-engine = { path = "../../", features = ["tracy"] }
anyhow = "1.0.75"
async-std = "1.12.0"
tracing = "0.1.37"

142
examples/2d/src/main.rs Normal file
View File

@ -0,0 +1,142 @@
use lyra_engine::{
assets::{gltf::Gltf, ResourceManager}, ecs::query::View, game::App, input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId,
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
}, winit::WindowOptions
};
#[async_std::main]
async fn main() {
let action_handler_plugin = |app: &mut App| {
let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0))
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
.add_action("Debug", Action::new(ActionKind::Button))
.add_mapping(
ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
.bind(
ACTLBL_MOVE_FORWARD_BACKWARD,
&[
ActionSource::Keyboard(KeyCode::KeyW).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::KeyS).into_binding_modifier(-1.0),
],
)
.bind(
ACTLBL_MOVE_LEFT_RIGHT,
&[
ActionSource::Keyboard(KeyCode::KeyA).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::KeyD).into_binding_modifier(1.0),
],
)
.bind(
ACTLBL_MOVE_UP_DOWN,
&[
ActionSource::Keyboard(KeyCode::KeyC).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::KeyZ).into_binding_modifier(-1.0),
],
)
.bind(
ACTLBL_LOOK_LEFT_RIGHT,
&[
//ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
ActionSource::Keyboard(KeyCode::ArrowLeft).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowRight).into_binding_modifier(1.0),
],
)
.bind(
ACTLBL_LOOK_UP_DOWN,
&[
//ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
ActionSource::Keyboard(KeyCode::ArrowUp).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowDown).into_binding_modifier(1.0),
],
)
.bind(
ACTLBL_LOOK_ROLL,
&[
ActionSource::Keyboard(KeyCode::KeyE).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::KeyQ).into_binding_modifier(1.0),
],
)
.bind(
"Debug",
&[ActionSource::Keyboard(KeyCode::KeyB).into_binding()],
)
.finish(),
)
.finish();
//let world = app.world;
app.add_resource(action_handler);
app.with_plugin(InputActionPlugin);
};
let mut a = App::new();
a.with_plugin(lyra_engine::DefaultPlugins)
.with_plugin(setup_scene_plugin)
.with_plugin(action_handler_plugin)
//.with_plugin(camera_debug_plugin)
.with_plugin(FreeFlyCameraPlugin);
a.run();
}
fn setup_scene_plugin(app: &mut App) {
let world = &mut app.world;
let resman = world.get_resource_mut::<ResourceManager>().unwrap();
/* let camera_gltf = resman
.request::<Gltf>("../assets/AntiqueCamera.glb")
.unwrap();
camera_gltf.wait_recurse_dependencies_load();
let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0];
drop(resman);
world.spawn((
camera_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(0.0, -5.0, -2.0),
)); */
let cube_gltf = resman
.request::<Gltf>("../assets/cube-texture-embedded.gltf")
.unwrap();
cube_gltf.wait_recurse_dependencies_load();
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
drop(resman);
world.spawn((
cube_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(0.0, 0.0, -2.0),
));
{
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
light_tran.rotate_x(math::Angle::Degrees(-45.0));
light_tran.rotate_y(math::Angle::Degrees(25.0));
world.spawn((
DirectionalLight {
enabled: true,
color: Vec3::ONE,
intensity: 0.15, //..Default::default()
},
light_tran,
));
}
let mut camera = CameraComponent::new_2d();
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
world.spawn((camera, FreeFlyCamera::default()));
}

View File

@ -3,9 +3,7 @@ use lyra_engine::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId,
}, math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
system_update_world_transforms, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN
}, winit::WindowOptions
};
@ -85,7 +83,8 @@ async fn main() {
.with_plugin(setup_scene_plugin)
.with_plugin(action_handler_plugin)
//.with_plugin(camera_debug_plugin)
.with_plugin(FreeFlyCameraPlugin);
.with_plugin(FreeFlyCameraPlugin)
.with_system("system_update_world_transforms", system_update_world_transforms, &[]);
a.run();
}
@ -118,7 +117,7 @@ fn setup_scene_plugin(app: &mut App) {
world.spawn((
cube_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(0.0, 0.0, -2.0),
Transform::from_xyz(0.0, 0.0, -20.0),
));
{

View File

@ -12,6 +12,8 @@ pub mod winit;
pub mod as_any;
pub mod plugin;
pub mod sprite;
mod event;
pub use event::*;

View File

@ -8,32 +8,24 @@ use glam::{UVec2, Vec3};
use image::GenericImageView;
use itertools::izip;
use lyra_ecs::{
query::{
filter::{Has, Not, Or},
Entities, Res, ResMut, TickOf,
},
relation::{ChildOf, RelationOriginComponent},
Component, Entity, ResourceObject, World,
query::{filter::Or, Entities, ResMut, TickOf},
Entity, ResourceObject, World,
};
use lyra_game_derive::RenderGraphLabel;
use lyra_math::Transform;
use lyra_resource::{gltf::Mesh, ResHandle};
use lyra_scene::{SceneGraph, WorldTransform};
use lyra_scene::SceneGraph;
use rustc_hash::FxHashMap;
use tracing::{debug, instrument};
use uuid::Uuid;
use wgpu::util::DeviceExt;
use crate::{
render::{
graph::{Node, NodeDesc, NodeType},
render_buffer::BufferStorage,
render_job::RenderJob,
texture::{res_filter_to_wgpu, res_wrap_to_wgpu},
transform_buffer_storage::{TransformBuffers, TransformGroup},
vertex::Vertex,
},
DeltaTime,
use crate::render::{
graph::{Node, NodeDesc, NodeType},
render_buffer::BufferStorage,
render_job::RenderJob,
texture::{res_filter_to_wgpu, res_wrap_to_wgpu},
transform_buffer_storage::TransformIndex,
vertex::Vertex,
};
type MeshHandle = ResHandle<Mesh>;
@ -48,12 +40,6 @@ pub struct MeshBufferStorage {
pub material: Option<Arc<GpuMaterial>>,
}
#[derive(Clone, Debug, Component)]
struct InterpTransform {
last_transform: Transform,
alpha: f32,
}
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
pub struct MeshPrepNodeLabel;
@ -270,35 +256,27 @@ impl Node for MeshPrepNode {
) {
let device = &context.device;
let queue = &context.queue;
let render_limits = device.limits();
let last_epoch = world.current_tick();
let mut alive_entities = HashSet::new();
{
// prepare the world with resources
if !world.has_resource::<TransformBuffers>() {
let buffers = TransformBuffers::new(device);
world.add_resource(buffers);
}
Self::try_init_resource::<RenderMeshes>(world);
Self::try_init_resource::<RenderAssets<MeshBufferStorage>>(world);
Self::try_init_resource::<RenderAssets<Arc<GpuMaterial>>>(world);
Self::try_init_resource::<FxHashMap<Entity, uuid::Uuid>>(world);
let mut render_meshes = world.get_resource_mut::<RenderMeshes>()
let mut render_meshes = world
.get_resource_mut::<RenderMeshes>()
.expect("world missing RenderMeshes resource");
render_meshes.clear();
}
let view = world.view_iter::<(
Entities,
&Transform,
TickOf<Transform>,
&TransformIndex,
Or<(&MeshHandle, TickOf<MeshHandle>), (&SceneHandle, TickOf<SceneHandle>)>,
Option<&mut InterpTransform>,
Res<DeltaTime>,
ResMut<TransformBuffers>,
ResMut<RenderMeshes>,
ResMut<RenderAssets<MeshBufferStorage>>,
ResMut<RenderAssets<Arc<GpuMaterial>>>,
@ -306,16 +284,10 @@ impl Node for MeshPrepNode {
)>();
// used to store InterpTransform components to add to entities later
let mut component_queue: Vec<(Entity, InterpTransform)> = vec![];
for (
entity,
transform,
_transform_epoch,
transform_index,
(mesh_pair, scene_pair),
interp_tran,
delta_time,
mut transforms,
mut render_meshes,
mut mesh_buffers,
mut material_buffers,
@ -324,40 +296,6 @@ impl Node for MeshPrepNode {
{
alive_entities.insert(entity);
// Interpolate the transform for this entity using a component.
// If the entity does not have the component then it will be queued to be added
// to it after all the entities are prepared for rendering.
let interp_transform = match interp_tran {
Some(mut interp_transform) => {
// found in https://youtu.be/YJB1QnEmlTs?t=472
interp_transform.alpha = 1.0 - interp_transform.alpha.powf(**delta_time);
interp_transform.last_transform = interp_transform
.last_transform
.lerp(*transform, interp_transform.alpha);
interp_transform.last_transform
}
None => {
let interp = InterpTransform {
last_transform: *transform,
alpha: 0.5,
};
component_queue.push((entity, interp));
*transform
}
};
{
// expand the transform buffers if they need to be.
// this is done in its own scope to avoid multiple mutable references to self at
// once; aka, make the borrow checker happy
if transforms.needs_expand() {
debug!("Expanding transform buffers");
transforms.expand_buffers(device);
}
}
if let Some((mesh_han, mesh_epoch)) = mesh_pair {
if let Some(mesh) = mesh_han.data_ref() {
// if process mesh did not just create a new mesh, and the epoch
@ -377,41 +315,19 @@ impl Node for MeshPrepNode {
self.check_mesh_buffers(device, queue, &mut mesh_buffers, &mesh_han);
}
let group = TransformGroup::EntityRes(entity, mesh_han.uuid());
let transform_id = transforms.update_or_push(
device,
queue,
&render_limits,
group,
interp_transform.calculate_mat4(),
glam::Mat3::from_quat(interp_transform.rotation),
);
let material = mesh.material.as_ref().unwrap().data_ref().unwrap();
let shader = material.shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id);
let job = RenderJob::new(entity, shader, mesh_han.uuid(), *transform_index);
render_meshes.push_back(job);
}
}
if let Some((scene_han, scene_epoch)) = scene_pair {
if let Some(scene) = scene_han.data_ref() {
if scene_epoch == last_epoch {
let view = scene.world().view::<(
Entities,
&mut WorldTransform,
&Transform,
Not<Has<RelationOriginComponent<ChildOf>>>,
)>();
lyra_scene::system_update_world_transforms(scene.world(), view).unwrap();
}
for (mesh_han, pos) in
scene.world().view_iter::<(&MeshHandle, &WorldTransform)>()
for (mesh_han, transform_index) in
scene.world().view_iter::<(&MeshHandle, &TransformIndex)>()
{
if let Some(mesh) = mesh_han.data_ref() {
let mesh_interpo = interp_transform + **pos;
// if process mesh did not just create a new mesh, and the epoch
// shows that the scene has changed, verify that the mesh buffers
// dont need to be resent to the gpu.
@ -434,35 +350,16 @@ impl Node for MeshPrepNode {
);
}
let scene_mesh_group =
TransformGroup::Res(scene_han.uuid(), mesh_han.uuid());
let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into());
let transform_id = transforms.update_or_push(
device,
queue,
&render_limits,
group,
mesh_interpo.calculate_mat4(),
glam::Mat3::from_quat(mesh_interpo.rotation),
);
let material = mesh.material.as_ref().unwrap().data_ref().unwrap();
let shader = material.shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id);
let job =
RenderJob::new(entity, shader, mesh_han.uuid(), *transform_index);
render_meshes.push_back(job);
}
}
}
}
}
for (en, interp) in component_queue {
world.insert(en, interp);
}
let mut transforms = world.get_resource_mut::<TransformBuffers>()
.expect("world missing TransformBuffers resource");
transforms.send_to_gpu(queue);
}
fn execute(

View File

@ -27,3 +27,6 @@ pub use shadows::*;
mod mesh_prepare;
pub use mesh_prepare::*;
mod transform;
pub use transform::*;

View File

@ -0,0 +1,226 @@
use lyra_ecs::{
query::{
filter::{Has, Not, Or},
Entities, TickOf,
},
relation::{ChildOf, RelationOriginComponent},
Component, Entity,
};
use lyra_game_derive::RenderGraphLabel;
use lyra_math::Transform;
use lyra_resource::ResHandle;
use lyra_scene::{SceneGraph, WorldTransform};
use tracing::debug;
use crate::{
render::{
graph::{Node, NodeDesc, NodeType},
transform_buffer_storage::{TransformBuffers, TransformIndex},
},
DeltaTime,
};
/// An interpolated transform.
///
/// This transform is interpolated between frames to make movement appear smoother when the
/// transform is updated less often than rendering.
#[derive(Clone, Debug, Component)]
pub struct InterpTransform {
last_transform: Transform,
alpha: f32,
}
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
pub struct TransformsNodeLabel;
#[derive(Debug)]
pub struct TransformsNode {}
impl TransformsNode {
pub fn new() -> Self {
Self {}
}
}
fn process_component_queue(world: &mut lyra_ecs::World, component_queue: Vec<(Entity, Option<InterpTransform>, Option<TransformIndex>)>) {
for (en, interp, index) in component_queue {
println!("writing index {:?} for entity {}", index, en.id().0);
match (interp, index) {
(None, None) => unreachable!(),
(None, Some(index)) => world.insert(en, index),
(Some(interp), None) => world.insert(en, interp),
(Some(interp), Some(index)) => world.insert(en, (interp, index)),
}
}
}
fn update_transforms(
device: &wgpu::Device,
queue: &wgpu::Queue,
limits: &wgpu::Limits,
world: &mut lyra_ecs::World,
delta_time: DeltaTime,
buffers: &mut TransformBuffers,
parent_transform: Transform,
) {
let current_tick = world.current_tick();
let mut component_queue = vec![];
let view = world.view_iter::<(
Entities,
Or<&WorldTransform, &Transform>,
Option<&mut InterpTransform>,
Option<&TransformIndex>,
Option<(&ResHandle<SceneGraph>, TickOf<ResHandle<SceneGraph>>)>,
)>();
for (entity, transform, interp_tran, transform_index, scene_graph) in view {
// expand the transform buffers if they need to be.
if buffers.needs_expand() {
debug!("Expanding transform buffers");
buffers.expand_buffers(device);
}
// Get the world transform of the entity, else fall back to the transform
let transform = match transform {
(None, None) => unreachable!(),
(None, Some(t)) => *t,
(Some(wt), None) => **wt,
// Assume world transform since it *should* be updated by world systems
(Some(wt), Some(_)) => **wt,
};
// offset this transform by its parent
let transform = transform + parent_transform;
// Interpolate the transform for this entity using a component.
// If the entity does not have the component then it will be queued to be added
// to it after all the entities are prepared for rendering.
let transform = match interp_tran {
Some(mut interp_transform) => {
// found in https://youtu.be/YJB1QnEmlTs?t=472
interp_transform.alpha = 1.0 - interp_transform.alpha.powf(*delta_time);
interp_transform.last_transform = interp_transform
.last_transform
.lerp(transform, interp_transform.alpha);
interp_transform.last_transform
}
None => {
let interp = InterpTransform {
last_transform: transform,
alpha: 0.5,
};
component_queue.push((entity, Some(interp), None));
transform
}
};
// Get the TransformIndex from the entity, or reserve a new one if the entity doesn't have
// the component.
let index = match transform_index {
Some(i) => *i,
None => {
let i = buffers.reserve_transform(&device);
debug!(
"Reserved transform index {:?} for entity {}",
i,
entity.id().0
);
component_queue.push((entity, None, Some(i)));
i
}
};
// TODO: only update if the transform changed.
buffers.update(
&queue,
index,
transform.calculate_mat4(),
glam::Mat3::from_quat(transform.rotation),
);
if let Some((scene, scene_tick)) = scene_graph {
if let Some(mut scene) = scene.data_mut() {
if *scene_tick + 1 >= *current_tick {
// Must manually call the world transform update system for scenes
// TODO: Separate gltf from lyra-resource so lyra-scene can depend on resource to
// query for scenes in the system.
let view = scene.world().view::<(
Entities,
&mut WorldTransform,
&Transform,
Not<Has<RelationOriginComponent<ChildOf>>>,
)>();
lyra_scene::system_update_world_transforms(scene.world(), view).unwrap();
}
update_transforms(
device,
queue,
limits,
scene.world_mut(),
delta_time,
buffers,
transform,
);
}
}
}
process_component_queue(world, component_queue);
}
impl Node for TransformsNode {
fn desc(
&mut self,
_: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::NodeDesc {
NodeDesc::new(NodeType::Node, None, vec![])
}
fn prepare(
&mut self,
_: &mut crate::render::graph::RenderGraph,
world: &mut lyra_ecs::World,
context: &mut crate::render::graph::RenderGraphContext,
) {
let device = &context.device;
let queue = &context.queue;
let render_limits = device.limits();
// prepare the world with resources
if !world.has_resource::<TransformBuffers>() {
let buffers = TransformBuffers::new(device);
world.add_resource(buffers);
}
// I have to do this weird garbage to borrow the `TransformBuffers`
// without running into a borrow checker error from passing `world` as mutable.
// This is safe since I know that the recursive function isn't accessing this
// TransformBuffers, or any other ones in other worlds.
let buffers = world.get_resource_data::<TransformBuffers>()
.map(|r| r.clone()).unwrap();
let mut buffers = buffers.get_mut();
let dt = world.get_resource::<DeltaTime>().unwrap().clone();
update_transforms(
&device,
&queue,
&render_limits,
world,
dt,
&mut buffers,
Transform::default(),
);
}
fn execute(
&mut self,
_: &mut crate::render::graph::RenderGraph,
_: &crate::render::graph::NodeDesc,
_: &mut crate::render::graph::RenderGraphContext,
) {
}
}

View File

@ -9,7 +9,7 @@ use lyra_game_derive::RenderGraphLabel;
use tracing::{debug, instrument, warn};
use winit::window::Window;
use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshPrepNode, MeshPrepNodeLabel, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, ShadowMapsPass, ShadowMapsPassLabel, SubGraphNode, ViewTarget};
use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshPrepNode, MeshPrepNodeLabel, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, ShadowMapsPass, ShadowMapsPassLabel, SubGraphNode, TransformsNode, TransformsNodeLabel, ViewTarget};
use super::graph::RenderGraph;
use super::{resource::RenderPipeline, render_job::RenderJob};
@ -83,7 +83,7 @@ impl BasicRenderer {
let adapter = instance.request_adapter(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
},
@ -149,14 +149,19 @@ impl BasicRenderer {
debug!("Adding light cull compute pass");
forward_plus_graph.add_node(LightCullComputePassLabel, LightCullComputePass::new(size));
debug!("Adding mesh pass");
debug!("Adding Transforms node");
forward_plus_graph.add_node(TransformsNodeLabel, TransformsNode::new());
debug!("Adding shadow maps pass");
forward_plus_graph.add_node(ShadowMapsPassLabel, ShadowMapsPass::new(&device));
debug!("Adding mesh prep node");
let mesh_prep = MeshPrepNode::new(&device);
let material_bgl = mesh_prep.material_bgl.clone();
forward_plus_graph.add_node(MeshPrepNodeLabel, mesh_prep);
debug!("Adding mesh pass");
forward_plus_graph.add_node(MeshesPassLabel, MeshPass::new(material_bgl));
forward_plus_graph.add_edge(TransformsNodeLabel, MeshPrepNodeLabel);
forward_plus_graph.add_edge(LightBasePassLabel, LightCullComputePassLabel);
forward_plus_graph.add_edge(LightCullComputePassLabel, MeshesPassLabel);

View File

@ -1,14 +1,11 @@
use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64, sync::Arc};
use lyra_ecs::Entity;
use lyra_ecs::{Component, Entity};
use tracing::instrument;
use uuid::Uuid;
use wgpu::Limits;
use std::mem;
use crate::render::avec::AVec;
/// A group id created from a [`TransformGroup`].
///
/// This is mainly created so that [`TransformGroup::OwnedGroup`] can use another group inside of it.
@ -57,7 +54,7 @@ pub enum TransformGroup {
}
/// The index of a specific Transform inside of the buffers.
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Component)]
pub struct TransformIndex {
/// The index of the entry in the buffer chain.
entry_index: usize,
@ -70,9 +67,6 @@ struct BufferEntry {
pub len: usize,
pub bindgroup: wgpu::BindGroup,
pub buffer: wgpu::Buffer,
transforms: AVec<TransformNormalMatPair>,
//pub normal_buffer: wgpu::Buffer,
}
/// A HashMap that caches values for reuse.
@ -166,8 +160,6 @@ impl<K: Hash + Eq + PartialEq + Clone, V: Clone, S: BuildHasher> CachedValMap<K,
/// update, and retrieve the transforms.
pub struct TransformBuffers {
pub bindgroup_layout: Arc<wgpu::BindGroupLayout>,
//groups: CachedValMap<TransformGroupId, TransformIndex>,
//groups: SlotMap<TransformGroupId, TransformIndex>,
entries: Vec<BufferEntry>,
limits: wgpu::Limits,
max_transform_count: usize,
@ -208,40 +200,14 @@ impl TransformBuffers {
s
}
/// Write the transform buffers to the gpu.
/// Reserve a transform index.
///
/// This uses [`wgpu::Queue::write_buffer`], so the write is not immediately submitted,
/// and instead enqueued internally to happen at the start of the next submit() call.
#[instrument(skip(self, queue))]
pub fn send_to_gpu(&mut self, queue: &wgpu::Queue) {
self.next_index = 0;
for entry in &mut self.entries {
entry.len = 0;
let p = entry.transforms.as_ptr();
let bytes = unsafe { std::slice::from_raw_parts(p, entry.transforms.len() * entry.transforms.align()) };
queue.write_buffer(&entry.buffer, 0, bytes);
}
}
/// Update an existing transform group or if its not existing yet, pushes it to the buffer.
///
/// Returns: the index that the transform is at in the buffers.
#[instrument(skip(self, device, queue, limits, group, transform, normal_matrix))]
#[inline(always)]
pub fn update_or_push(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, limits: &Limits, group: TransformGroup, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformIndex
{
// maybe will be used at some point again
let _ = (queue, limits, group);
let normal_matrix = glam::Mat4::from_mat3(normal_matrix);
/// The buffer chain may expand if its required
pub fn reserve_transform(&mut self, device: &wgpu::Device) -> TransformIndex {
let index = self.next_index;
self.next_index += 1;
// the index of the entry to put the transform into
// the index of the transform buffer
let entry_index = index / self.max_transform_count;
// the index of the transform in the buffer
let transform_index = index % self.max_transform_count;
@ -251,20 +217,28 @@ impl TransformBuffers {
}
let entry = self.entries.get_mut(entry_index).unwrap();
// write the transform and normal to the end of the transform
entry.transforms.set_at(transform_index, TransformNormalMatPair {
transform,
normal_mat: normal_matrix,
});
entry.len += 1;
TransformIndex {
entry_index: 0,
transform_index: index,
entry_index,
transform_index,
}
}
/// Updates a Transform at `index`.
#[instrument(skip(self, queue, index, transform, normal_matrix))]
#[inline(always)]
pub fn update(&mut self, queue: &wgpu::Queue, index: TransformIndex, transform: glam::Mat4, normal_matrix: glam::Mat3) {
let pair = TransformNormalMatPair {
transform,
normal_mat: glam::Mat4::from_mat3(normal_matrix),
};
let entry = self.entries.get(index.entry_index).expect("invalid entry index, no entry!");
let offset = self.buffer_offset(index);
queue.write_buffer(&entry.buffer, offset as _, bytemuck::bytes_of(&pair));
}
/// Expand the Transform buffers by adding another uniform buffer binding.
///
/// This object has a chain of uniform buffers, when the buffers are expanded, a new
@ -302,18 +276,10 @@ impl TransformBuffers {
label: Some("BG_Transforms"),
});
let mut transforms = AVec::new(limits.min_uniform_buffer_offset_alignment as _);
transforms.resize(self.max_transform_count, TransformNormalMatPair {
transform: glam::Mat4::IDENTITY,
normal_mat: glam::Mat4::IDENTITY,
});
let entry = BufferEntry {
bindgroup,
buffer: transform_buffer,
len: 0,
transforms,
};
self.entries.push(entry);
}

View File

@ -6,7 +6,6 @@ pub struct Vertex {
pub position: glam::Vec3,
pub tex_coords: glam::Vec2,
pub normals: glam::Vec3,
//pub color: [f32; 3], // TODO: add color again
}
impl Vertex {
@ -59,3 +58,37 @@ impl DescVertexBufferLayout for Vertex {
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex2D {
pub position: glam::Vec3,
pub tex_coords: glam::Vec2,
}
impl Vertex2D {
pub fn new(position: glam::Vec3, tex_coords: glam::Vec2) -> Self {
Self {
position, tex_coords
}
}
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3, // Vec3
},
wgpu::VertexAttribute {
offset: std::mem::size_of::<glam::Vec3>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x2, // Vec2
},
]
}
}
}

View File

@ -0,0 +1,51 @@
use lyra_ecs::Component;
use lyra_reflect::Reflect;
use lyra_resource::ResHandle;
use lyra_math::{Vec3, Vec2};
/// How the sprite is positioned and rotated relative to its [`Transform`].
///
/// Default pivot is `Pivot::Center`, this makes it easier to rotate the sprites.
#[derive(Debug, Copy, Clone, PartialEq, Default, Component, Reflect)]
pub enum Pivot {
#[default]
Center,
CenterLeft,
CenterRight,
TopLeft,
TopRight,
TopCenter,
BottomLeft,
BottomRight,
BottomCenter,
/// A custom anchor point relative to top left.
/// Top left is `(0.0, 0.0)`.
Custom(Vec2)
}
impl Pivot {
/// Get the pivot point as a Vec2.
///
/// 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::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::Custom(v) => *v,
}
}
}
#[derive(Clone, Component, Reflect)]
pub struct Sprite {
pub texture: ResHandle<lyra_resource::Texture>,
pub color: Vec3,
pub pivot: Pivot,
}

View File

@ -67,6 +67,39 @@ impl ResourceState {
}
}
pub struct ResourceDataRefMut<'a, T> {
guard: std::sync::RwLockWriteGuard<'a, UntypedResource>,
_marker: PhantomData<T>,
}
impl<'a, T: 'static> std::ops::Deref for ResourceDataRefMut<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match &self.guard.state {
ResourceState::Ready(d) => {
// for some reason, if I didn't use `.as_ref`, the downcast would fail.
let d = d.as_ref().as_any();
d.downcast_ref::<T>().unwrap()
},
_ => unreachable!() // ResHandler::data_ref shouldn't allow this to run
}
}
}
impl<'a, T: 'static> std::ops::DerefMut for ResourceDataRefMut<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match &mut self.guard.state {
ResourceState::Ready(d) => {
// for some reason, if I didn't use `.as_ref`, the downcast would fail.
let d = d.as_mut().as_any_mut();
d.downcast_mut::<T>().unwrap()
},
_ => unreachable!() // ResHandler::data_ref shouldn't allow this to run
}
}
}
pub struct ResourceDataRef<'a, T> {
guard: std::sync::RwLockReadGuard<'a, UntypedResource>,
_marker: PhantomData<T>,
@ -310,6 +343,18 @@ impl<T: ResourceData> ResHandle<T> {
None
}
}
pub fn data_mut<'a>(&'a self) -> Option<ResourceDataRefMut<'a, T>> {
if self.is_loaded() {
let d = self.handle.write();
Some(ResourceDataRefMut {
guard: d,
_marker: PhantomData::<T>
})
} else {
None
}
}
}
impl<T: ResourceData> ResourceStorage for ResHandle<T> {

View File

@ -129,6 +129,10 @@ impl SceneGraph {
pub fn world(&self) -> &World {
&self.world
}
pub fn world_mut(&mut self) -> &mut World {
&mut self.world
}
}
/// Add a node under a parent node.