render: create a transform pass for sending transforms to the GPU
CI / build (push) Has been cancelled
Details
CI / build (push) Has been cancelled
Details
This commit is contained in:
parent
7ae0eae6ac
commit
4ff7e40624
|
@ -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"
|
|
@ -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()));
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"rdocCaptureSettings": 1,
|
||||
"settings": {
|
||||
"autoStart": false,
|
||||
"commandLine": "",
|
||||
"environment": [
|
||||
],
|
||||
"executable": "/media/data_drive/Development/Rust/lyra-engine/target/debug/lua-scripting",
|
||||
"inject": false,
|
||||
"numQueuedFrames": 2,
|
||||
"options": {
|
||||
"allowFullscreen": true,
|
||||
"allowVSync": true,
|
||||
"apiValidation": false,
|
||||
"captureAllCmdLists": false,
|
||||
"captureCallstacks": false,
|
||||
"captureCallstacksOnlyDraws": false,
|
||||
"debugOutputMute": true,
|
||||
"delayForDebugger": 0,
|
||||
"hookIntoChildren": false,
|
||||
"refAllResources": false,
|
||||
"softMemoryLimit": 0,
|
||||
"verifyBufferAccess": false
|
||||
},
|
||||
"queuedFrameCap": 5,
|
||||
"workingDir": "/media/data_drive/Development/Rust/lyra-engine/examples/lua-scripting"
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
));
|
||||
|
||||
{
|
||||
|
|
|
@ -12,6 +12,8 @@ pub mod winit;
|
|||
pub mod as_any;
|
||||
pub mod plugin;
|
||||
|
||||
pub mod sprite;
|
||||
|
||||
mod event;
|
||||
pub use event::*;
|
||||
|
||||
|
|
|
@ -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::{
|
||||
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},
|
||||
transform_buffer_storage::TransformIndex,
|
||||
vertex::Vertex,
|
||||
},
|
||||
DeltaTime,
|
||||
};
|
||||
|
||||
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(
|
||||
|
|
|
@ -27,3 +27,6 @@ pub use shadows::*;
|
|||
|
||||
mod mesh_prepare;
|
||||
pub use mesh_prepare::*;
|
||||
|
||||
mod transform;
|
||||
pub use transform::*;
|
|
@ -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,
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
const ALPHA_CUTOFF = 0.1;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
@location(1) world_position: vec3<f32>,
|
||||
}
|
||||
|
||||
struct TransformData {
|
||||
transform: mat4x4<f32>,
|
||||
normal_matrix: mat4x4<f32>,
|
||||
}
|
||||
|
||||
struct CameraUniform {
|
||||
view: mat4x4<f32>,
|
||||
inverse_projection: mat4x4<f32>,
|
||||
view_projection: mat4x4<f32>,
|
||||
projection: mat4x4<f32>,
|
||||
position: vec3<f32>,
|
||||
tile_debug: u32,
|
||||
}
|
||||
|
||||
@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 {
|
||||
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;
|
||||
return out;
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
|
||||
@group(0) @binding(1)
|
||||
var s_diffuse: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
if (object_color.a < ALPHA_CUTOFF) {
|
||||
discard;
|
||||
}
|
||||
|
||||
return object_color;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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> {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue