Merge pull request 'Implement a Render Graph' (#16) from feature/render-graph into main
ci/woodpecker/push/debug Pipeline failed
Details
ci/woodpecker/push/debug Pipeline failed
Details
Reviewed-on: #16
This commit is contained in:
commit
acb58a15ff
|
@ -0,0 +1,29 @@
|
|||
# The run config is used for both run mode and debug mode
|
||||
|
||||
[[configs]]
|
||||
# the name of this task
|
||||
name = "Example 'simple_scene'"
|
||||
|
||||
# the type of the debugger. If not set, it can't be debugged but can still be run
|
||||
type = "lldb"
|
||||
|
||||
# the program to run
|
||||
program = "../../target/debug/simple_scene"
|
||||
|
||||
# the program arguments, e.g. args = ["arg1", "arg2"], optional
|
||||
# args = []
|
||||
|
||||
# current working directory, optional
|
||||
cwd = "${workspace}/examples/simple_scene"
|
||||
|
||||
# environment variables, optional
|
||||
# [configs.env]
|
||||
# VAR1 = "VAL1"
|
||||
# VAR2 = "VAL2"
|
||||
|
||||
# task to run before the run/debug session is started, optional
|
||||
[configs.prelaunch]
|
||||
program = "cargo"
|
||||
args = [
|
||||
"build",
|
||||
]
|
|
@ -22,6 +22,24 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}/examples/testbed"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug example simple_scene",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--manifest-path", "${workspaceFolder}/examples/simple_scene/Cargo.toml"
|
||||
//"--bin=testbed",
|
||||
],
|
||||
"filter": {
|
||||
"name": "simple_scene",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/examples/simple_scene"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
|
|
@ -384,6 +384,12 @@ version = "1.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bind_match"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "171f0236f66c7be99f32060539c2bade94033ded356ecf4c9dc9b1e6198326cd"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
|
@ -959,6 +965,12 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.28"
|
||||
|
@ -1853,6 +1865,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"bind_match",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"gilrs-core",
|
||||
|
@ -1861,10 +1874,12 @@ dependencies = [
|
|||
"instant",
|
||||
"itertools 0.11.0",
|
||||
"lyra-ecs",
|
||||
"lyra-game-derive",
|
||||
"lyra-math",
|
||||
"lyra-reflect",
|
||||
"lyra-resource",
|
||||
"lyra-scene",
|
||||
"petgraph",
|
||||
"quote",
|
||||
"rustc-hash",
|
||||
"syn 2.0.51",
|
||||
|
@ -1880,6 +1895,15 @@ dependencies = [
|
|||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyra-game-derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyra-math"
|
||||
version = "0.1.0"
|
||||
|
@ -2507,6 +2531,16 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
|
@ -3065,6 +3099,16 @@ version = "0.3.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "simple_scene"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"lyra-engine",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
|
|
@ -16,7 +16,8 @@ members = [
|
|||
|
||||
"examples/many-lights",
|
||||
"examples/fixed-timestep-rotating-model",
|
||||
"examples/lua-scripting"
|
||||
"examples/lua-scripting",
|
||||
"examples/simple_scene"
|
||||
]
|
||||
|
||||
[features]
|
||||
|
@ -27,3 +28,9 @@ tracy = ["lyra-game/tracy"]
|
|||
[dependencies]
|
||||
lyra-game = { path = "lyra-game" }
|
||||
lyra-scripting = { path = "lyra-scripting", optional = true }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
|
@ -10,13 +10,3 @@ async-std = "1.12.0"
|
|||
tracing = "0.1.37"
|
||||
rand = "0.8.5"
|
||||
fps_counter = "3.0.0"
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "/usr/bin/clang"
|
||||
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
|
@ -10,13 +10,3 @@ async-std = "1.12.0"
|
|||
tracing = "0.1.37"
|
||||
rand = "0.8.5"
|
||||
fps_counter = "3.0.0"
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "/usr/bin/clang"
|
||||
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
|
@ -10,13 +10,3 @@ async-std = "1.12.0"
|
|||
tracing = "0.1.37"
|
||||
rand = "0.8.5"
|
||||
fps_counter = "3.0.0"
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "/usr/bin/clang"
|
||||
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
|
@ -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,59 @@
|
|||
---Return the userdata's name from its metatable
|
||||
---@param val userdata
|
||||
---@return string
|
||||
function udname(val)
|
||||
return getmetatable(val).__name
|
||||
end
|
||||
|
||||
function on_init()
|
||||
local cube = world:request_res("../assets/cube-texture-embedded.gltf")
|
||||
print("Loaded textured cube (" .. udname(cube) .. ")")
|
||||
|
||||
cube:wait_until_loaded()
|
||||
local scenes = cube:scenes()
|
||||
local cube_scene = scenes[1]
|
||||
|
||||
local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
|
||||
|
||||
local e = world:spawn(pos, cube_scene)
|
||||
print("spawned entity " .. tostring(e))
|
||||
end
|
||||
|
||||
--[[ function on_first()
|
||||
print("Lua's first function was called")
|
||||
end
|
||||
|
||||
function on_pre_update()
|
||||
print("Lua's pre-update function was called")
|
||||
end ]]
|
||||
|
||||
function on_update()
|
||||
--[[ ---@type number
|
||||
local dt = world:resource(DeltaTime)
|
||||
local act = world:resource(ActionHandler)
|
||||
---@type number
|
||||
local move_objs = act:get_axis("ObjectsMoveUpDown")
|
||||
|
||||
world:view(function (t)
|
||||
if move_objs ~= nil then
|
||||
t:translate(0, move_objs * 0.35 * dt, 0)
|
||||
return t
|
||||
end
|
||||
end, Transform) ]]
|
||||
|
||||
---@type number
|
||||
local dt = world:resource(DeltaTime)
|
||||
|
||||
world:view(function (t)
|
||||
t:translate(0, 0.15 * dt, 0)
|
||||
return t
|
||||
end, Transform)
|
||||
end
|
||||
|
||||
--[[ function on_post_update()
|
||||
print("Lua's post-update function was called")
|
||||
end
|
||||
|
||||
function on_last()
|
||||
print("Lua's last function was called")
|
||||
end ]]
|
|
@ -0,0 +1,149 @@
|
|||
use lyra_engine::{
|
||||
assets::{gltf::Gltf, ResourceManager},
|
||||
game::Game,
|
||||
input::{
|
||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||
},
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
let action_handler_plugin = |game: &mut Game| {
|
||||
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::W).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_ROLL,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
"Debug",
|
||||
&[ActionSource::Keyboard(KeyCode::B).into_binding()],
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let world = game.world_mut();
|
||||
world.add_resource(action_handler);
|
||||
game.with_plugin(InputActionPlugin);
|
||||
};
|
||||
|
||||
Game::initialize()
|
||||
.await
|
||||
.with_plugin(lyra_engine::DefaultPlugins)
|
||||
.with_plugin(setup_scene_plugin)
|
||||
.with_plugin(action_handler_plugin)
|
||||
//.with_plugin(camera_debug_plugin)
|
||||
.with_plugin(FreeFlyCameraPlugin)
|
||||
.run()
|
||||
.await;
|
||||
}
|
||||
|
||||
fn setup_scene_plugin(game: &mut Game) {
|
||||
let world = game.world_mut();
|
||||
let resman = world.get_resource_mut::<ResourceManager>();
|
||||
|
||||
/* 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_3d();
|
||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
||||
world.spawn((camera, FreeFlyCamera::default()));
|
||||
}
|
|
@ -4,6 +4,7 @@ version = "0.0.1"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lyra-game-derive = { path = "./lyra-game-derive" }
|
||||
lyra-resource = { path = "../lyra-resource" }
|
||||
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
||||
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
|
||||
|
@ -35,6 +36,8 @@ itertools = "0.11.0"
|
|||
thiserror = "1.0.56"
|
||||
unique = "0.9.1"
|
||||
rustc-hash = "1.1.0"
|
||||
petgraph = { version = "0.6.5", features = ["matrix_graph"] }
|
||||
bind_match = "0.1.2"
|
||||
|
||||
[features]
|
||||
tracy = ["dep:tracing-tracy"]
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "lyra-game-derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.70"
|
||||
quote = "1.0.33"
|
||||
syn = "2.0.41"
|
|
@ -0,0 +1,35 @@
|
|||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
#[proc_macro_derive(RenderGraphLabel)]
|
||||
pub fn derive_render_graph_label(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
let type_ident = &input.ident;
|
||||
|
||||
proc_macro::TokenStream::from(quote! {
|
||||
impl #impl_generics crate::render::graph::RenderGraphLabel for #type_ident #ty_generics #where_clause {
|
||||
fn rc_clone(&self) -> std::rc::Rc<dyn crate::render::graph::RenderGraphLabel> {
|
||||
std::rc::Rc::new(self.clone())
|
||||
}
|
||||
|
||||
/* fn as_dyn(&self) -> &dyn crate::render::graph::RenderGraphLabel {
|
||||
&self
|
||||
}
|
||||
|
||||
fn as_partial_eq(&self) -> &dyn PartialEq<dyn crate::render::graph::RenderGraphLabel> {
|
||||
self
|
||||
} */
|
||||
|
||||
fn as_label_hash(&self) -> u64 {
|
||||
let tyid = ::std::any::TypeId::of::<Self>();
|
||||
|
||||
let mut s = ::std::hash::DefaultHasher::new();
|
||||
::std::hash::Hash::hash(&tyid, &mut s);
|
||||
::std::hash::Hash::hash(self, &mut s);
|
||||
::std::hash::Hasher::finish(&s)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -57,10 +57,12 @@ struct GameLoop {
|
|||
}
|
||||
|
||||
impl GameLoop {
|
||||
pub async fn new(window: Arc<Window>, world: World, staged_exec: StagedExecutor) -> GameLoop {
|
||||
pub async fn new(window: Arc<Window>, mut world: World, staged_exec: StagedExecutor) -> Self {
|
||||
let renderer = BasicRenderer::create_with_window(&mut world, window.clone()).await;
|
||||
|
||||
Self {
|
||||
window: Arc::clone(&window),
|
||||
renderer: Box::new(BasicRenderer::create_with_window(window).await),
|
||||
renderer: Box::new(renderer),
|
||||
|
||||
world,
|
||||
staged_exec,
|
||||
|
@ -68,7 +70,7 @@ impl GameLoop {
|
|||
}
|
||||
|
||||
pub async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
self.renderer.on_resize(new_size);
|
||||
self.renderer.on_resize(&mut self.world, new_size);
|
||||
}
|
||||
|
||||
pub async fn on_init(&mut self) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#![feature(hash_extract_if)]
|
||||
#![feature(lint_reasons)]
|
||||
#![feature(trait_alias)]
|
||||
#![feature(map_many_mut)]
|
||||
|
||||
extern crate self as lyra_engine;
|
||||
|
||||
|
|
|
@ -0,0 +1,549 @@
|
|||
mod node;
|
||||
use std::{
|
||||
cell::{Ref, RefCell}, collections::{HashMap, VecDeque}, fmt::Debug, hash::Hash, rc::Rc, sync::Arc
|
||||
};
|
||||
|
||||
use lyra_ecs::World;
|
||||
pub use node::*;
|
||||
|
||||
mod passes;
|
||||
pub use passes::*;
|
||||
|
||||
mod slot_desc;
|
||||
pub use slot_desc::*;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::{debug_span, instrument, trace, warn};
|
||||
use wgpu::ComputePass;
|
||||
|
||||
use super::resource::{ComputePipeline, Pipeline, RenderPipeline};
|
||||
|
||||
pub trait RenderGraphLabel: Debug + 'static {
|
||||
fn rc_clone(&self) -> Rc<dyn RenderGraphLabel>;
|
||||
//fn as_dyn(&self) -> &dyn RenderGraphLabel;
|
||||
//fn as_partial_eq(&self) -> &dyn PartialEq<dyn RenderGraphLabel>;
|
||||
fn as_label_hash(&self) -> u64;
|
||||
|
||||
fn label_eq_rc(&self, other: &Rc<dyn RenderGraphLabel>) -> bool {
|
||||
self.as_label_hash() == other.as_label_hash()
|
||||
}
|
||||
|
||||
fn label_eq(&self, other: &dyn RenderGraphLabel) -> bool {
|
||||
self.as_label_hash() == other.as_label_hash()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RenderGraphHash(u64);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RenderGraphLabelValue(Rc<dyn RenderGraphLabel>);
|
||||
|
||||
impl Debug for RenderGraphLabelValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: RenderGraphLabel> From<L> for RenderGraphLabelValue {
|
||||
fn from(value: L) -> Self {
|
||||
Self(Rc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<dyn RenderGraphLabel>> for RenderGraphLabelValue {
|
||||
fn from(value: Rc<dyn RenderGraphLabel>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Rc<dyn RenderGraphLabel>> for RenderGraphLabelValue {
|
||||
fn from(value: &Rc<dyn RenderGraphLabel>) -> Self {
|
||||
Self(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for RenderGraphLabelValue {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.0.as_label_hash());
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for RenderGraphLabelValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.label_eq_rc(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for RenderGraphLabelValue {}
|
||||
|
||||
struct PassEntry {
|
||||
inner: Arc<RefCell<dyn Node>>,
|
||||
desc: Rc<RefCell<NodeDesc>>,
|
||||
/// The index of the pass in the execution graph
|
||||
graph_index: petgraph::matrix_graph::NodeIndex<usize>,
|
||||
pipeline: Rc<RefCell<Option<PipelineResource>>>,
|
||||
}
|
||||
|
||||
pub struct BindGroupEntry {
|
||||
pub label: RenderGraphLabelValue,
|
||||
/// BindGroup
|
||||
pub bg: Rc<wgpu::BindGroup>,
|
||||
/// BindGroupLayout
|
||||
pub layout: Option<Rc<wgpu::BindGroupLayout>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct ResourcedSlot {
|
||||
label: RenderGraphLabelValue,
|
||||
ty: SlotType,
|
||||
value: SlotValue,
|
||||
}
|
||||
|
||||
/// Stores the pipeline and other resources it uses.
|
||||
///
|
||||
/// This stores the bind groups that have been created for it
|
||||
pub struct PipelineResource {
|
||||
pub pipeline: Pipeline,
|
||||
/// Lookup map for bind groups using names
|
||||
pub bg_layout_name_lookup: HashMap<String, u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RenderTarget {
|
||||
pub surface: wgpu::Surface,
|
||||
pub surface_config: wgpu::SurfaceConfiguration,
|
||||
pub current_texture: Option<wgpu::SurfaceTexture>,
|
||||
}
|
||||
|
||||
pub struct RenderGraph {
|
||||
device: Rc<wgpu::Device>,
|
||||
queue: Rc<wgpu::Queue>,
|
||||
slots: FxHashMap<RenderGraphLabelValue, ResourcedSlot>,
|
||||
nodes: FxHashMap<RenderGraphLabelValue, PassEntry>,
|
||||
bind_groups: FxHashMap<RenderGraphLabelValue, BindGroupEntry>,
|
||||
/// A directed graph describing the execution path of the RenderGraph
|
||||
execution_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>,
|
||||
}
|
||||
|
||||
impl RenderGraph {
|
||||
pub fn new(device: Rc<wgpu::Device>, queue: Rc<wgpu::Queue>) -> Self {
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
slots: Default::default(),
|
||||
nodes: Default::default(),
|
||||
bind_groups: Default::default(),
|
||||
execution_graph: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device(&self) -> &wgpu::Device {
|
||||
&*self.device
|
||||
}
|
||||
|
||||
/// Add a [`Node`] to the RenderGraph.
|
||||
///
|
||||
/// When the node is added, its [`Node::desc`] method will be executed.
|
||||
///
|
||||
/// Additionally, all [`Slot`](node::NodeSlot)s of the node will be iterated,
|
||||
/// 1. Ensuring that there are no two slots of the same name, with different value types
|
||||
/// 2. Changing the id of insert slots to match the id of the output slot of the same name.
|
||||
/// * This means that the id of insert slots **ARE NOT STABLE**. **DO NOT** rely on them to
|
||||
/// not change. The IDs of output slots do stay the same.
|
||||
/// 3. Ensuring that no two slots share the same ID when the names do not match.
|
||||
#[instrument(skip(self, pass), level = "debug")]
|
||||
pub fn add_pass<P: Node>(&mut self, label: impl RenderGraphLabel, mut pass: P) {
|
||||
let mut desc = pass.desc(self);
|
||||
|
||||
// collect all the slots of the pass
|
||||
for slot in &mut desc.slots {
|
||||
if let Some(other) = self
|
||||
.slots
|
||||
.get_mut(&slot.label)
|
||||
//.map(|s| (id, s))
|
||||
//.and_then(|id| self.slots.get_mut(id).map(|s| (id, s)))
|
||||
{
|
||||
debug_assert_eq!(
|
||||
slot.ty, other.ty,
|
||||
"slot {:?} in pass {:?} does not match existing slot of same name",
|
||||
slot.label, label
|
||||
);
|
||||
|
||||
/* trace!(
|
||||
"Found existing slot for {:?}, changing id to {}",
|
||||
slot.label,
|
||||
id
|
||||
); */
|
||||
|
||||
// if there is a slot of the same name
|
||||
//slot.id = *id;
|
||||
} else {
|
||||
debug_assert!(!self.slots.contains_key(&slot.label),
|
||||
"Reuse of id detected in render graph! Pass: {:?}, slot: {:?}",
|
||||
label, slot.label,
|
||||
);
|
||||
|
||||
let res_slot = ResourcedSlot {
|
||||
label: slot.label.clone(),
|
||||
ty: slot.ty,
|
||||
value: slot.value.clone().unwrap_or(SlotValue::None),
|
||||
};
|
||||
|
||||
self.slots.insert(slot.label.clone(), res_slot);
|
||||
}
|
||||
}
|
||||
|
||||
// get clones of the bind groups and layouts
|
||||
for (label, bg, bgl) in &desc.bind_groups {
|
||||
self.bind_groups.insert(label.clone(), BindGroupEntry {
|
||||
label: label.clone(),
|
||||
bg: bg.clone(),
|
||||
layout: bgl.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
let label: RenderGraphLabelValue = label.into();
|
||||
let index = self.execution_graph.add_node(label.clone());
|
||||
|
||||
self.nodes.insert(
|
||||
label,
|
||||
PassEntry {
|
||||
inner: Arc::new(RefCell::new(pass)),
|
||||
desc: Rc::new(RefCell::new(desc)),
|
||||
graph_index: index,
|
||||
pipeline: Rc::new(RefCell::new(None)),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates all buffers required for the passes, also creates an internal execution path.
|
||||
///
|
||||
/// This only needs to be ran when the [`Node`]s in the graph change, or they are removed or
|
||||
/// added.
|
||||
#[instrument(skip(self, device))]
|
||||
pub fn setup(&mut self, device: &wgpu::Device) {
|
||||
// For all passes, create their pipelines
|
||||
for pass in self.nodes.values_mut() {
|
||||
let desc = (*pass.desc).borrow();
|
||||
if let Some(pipeline_desc) = &desc.pipeline_desc {
|
||||
let pipeline = match desc.ty {
|
||||
NodeType::Render => Pipeline::Render(RenderPipeline::create(
|
||||
device,
|
||||
pipeline_desc
|
||||
.as_render_pipeline_descriptor()
|
||||
.expect("got compute pipeline descriptor in a render pass"),
|
||||
)),
|
||||
NodeType::Compute => Pipeline::Compute(ComputePipeline::create(
|
||||
device,
|
||||
pipeline_desc
|
||||
.as_compute_pipeline_descriptor()
|
||||
.expect("got render pipeline descriptor in a compute pass"),
|
||||
)),
|
||||
NodeType::Presenter | NodeType::Node => {
|
||||
panic!("Present or Node RenderGraph passes should not have a pipeline descriptor!");
|
||||
}
|
||||
};
|
||||
|
||||
drop(desc);
|
||||
let res = PipelineResource {
|
||||
pipeline,
|
||||
bg_layout_name_lookup: Default::default(),
|
||||
};
|
||||
|
||||
let mut pipeline = pass.pipeline.borrow_mut();
|
||||
*pipeline = Some(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, world))]
|
||||
pub fn prepare(&mut self, world: &mut World) {
|
||||
// prepare all passes
|
||||
let mut buffer_writes = VecDeque::<GraphBufferWrite>::new();
|
||||
// reserve some buffer writes. not all nodes write so half the amount of them is probably
|
||||
// fine.
|
||||
buffer_writes.reserve(self.nodes.len() / 2);
|
||||
|
||||
for (label, pass) in &mut self.nodes {
|
||||
let mut context = RenderGraphContext::new(&self.device, &self.queue, None, label.clone());
|
||||
let mut inner = pass.inner.borrow_mut();
|
||||
inner.prepare(world, &mut context);
|
||||
buffer_writes.append(&mut context.buffer_writes);
|
||||
}
|
||||
|
||||
{
|
||||
// Queue all buffer writes to the gpu
|
||||
let s = debug_span!("queue_buffer_writes");
|
||||
let _e = s.enter();
|
||||
|
||||
while let Some(bufwr) = buffer_writes.pop_front() {
|
||||
let slot = self
|
||||
.slots
|
||||
.get(&bufwr.target_slot)
|
||||
.expect(&format!(
|
||||
"Failed to find slot '{:?}' for buffer write",
|
||||
bufwr.target_slot
|
||||
));
|
||||
let buf = slot
|
||||
.value
|
||||
.as_buffer()
|
||||
.expect(&format!("Slot '{:?}' is not a buffer", bufwr.target_slot));
|
||||
|
||||
self.queue.write_buffer(buf, bufwr.offset, &bufwr.bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub fn render(&mut self) {
|
||||
let mut sorted: VecDeque<RenderGraphLabelValue> = petgraph::algo::toposort(&self.execution_graph, None)
|
||||
.expect("RenderGraph had cycled!")
|
||||
.iter()
|
||||
.map(|i| self.execution_graph[i.clone()].clone())
|
||||
.collect();
|
||||
//debug!("Render graph execution order: {:?}", sorted);
|
||||
|
||||
let mut encoders = Vec::with_capacity(self.nodes.len() / 2);
|
||||
while let Some(pass_label) = sorted.pop_front() {
|
||||
let pass = self.nodes.get(&pass_label).unwrap();
|
||||
let pass_inn = pass.inner.clone();
|
||||
|
||||
let pass_desc = pass.desc.clone();
|
||||
let pass_desc = (*pass_desc).borrow();
|
||||
|
||||
let label = format!("{:?} Encoder", pass_label.0);
|
||||
|
||||
// encoders are not needed for presenter nodes.
|
||||
let encoder = if pass_desc.ty.should_have_pipeline() {
|
||||
Some(
|
||||
self.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some(&label),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// clone of the Rc's is required to appease the borrow checker
|
||||
let device = self.device.clone();
|
||||
let queue = self.queue.clone();
|
||||
let mut context = RenderGraphContext::new(&device, &queue, encoder, pass_label.clone());
|
||||
|
||||
// all encoders need to be submitted before a presenter node is executed.
|
||||
if pass_desc.ty == NodeType::Presenter {
|
||||
trace!("Submitting {} encoderd before presenting", encoders.len());
|
||||
self.queue.submit(encoders.drain(..));
|
||||
}
|
||||
|
||||
trace!("Executing {:?}", pass_label.0);
|
||||
let mut inner = pass_inn.borrow_mut();
|
||||
inner.execute(self, &pass_desc, &mut context);
|
||||
|
||||
if let Some(encoder) = context.encoder {
|
||||
encoders.push(encoder.finish());
|
||||
}
|
||||
}
|
||||
|
||||
if !encoders.is_empty() {
|
||||
warn!(
|
||||
"{} encoders were not submitted in the same render cycle they were created. \
|
||||
Make sure there is a presenting pass at the end. You may still see something, \
|
||||
however it will be delayed a render cycle.",
|
||||
encoders.len()
|
||||
);
|
||||
self.queue.submit(encoders.into_iter());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slot_value<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&SlotValue> {
|
||||
self.slots.get(&label.into()).map(|s| &s.value)
|
||||
}
|
||||
|
||||
pub fn slot_value_mut<L: Into<RenderGraphLabelValue>>(&mut self, label: L) -> Option<&mut SlotValue> {
|
||||
self.slots.get_mut(&label.into()).map(|s| &mut s.value)
|
||||
}
|
||||
|
||||
pub fn node_desc<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<Ref<NodeDesc>> {
|
||||
self.nodes.get(&label.into()).map(|s| (*s.desc).borrow())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn pipeline<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<Ref<Pipeline>> {
|
||||
self.nodes.get(&label.into())
|
||||
.and_then(|p| {
|
||||
let v = p.pipeline.borrow();
|
||||
|
||||
match &*v {
|
||||
Some(_) => Some(Ref::map(v, |p| &p.as_ref().unwrap().pipeline)),
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn try_bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Rc<wgpu::BindGroup>> {
|
||||
self.bind_groups.get(&label.into()).map(|e| &e.bg)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Rc<wgpu::BindGroup> {
|
||||
self.try_bind_group(label).expect("Unknown id for bind group")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn try_bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Rc<wgpu::BindGroupLayout>> {
|
||||
self.bind_groups.get(&label.into()).and_then(|e| e.layout.as_ref())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Rc<wgpu::BindGroupLayout> {
|
||||
self.try_bind_group_layout(label)
|
||||
.expect("Unknown id for bind group layout")
|
||||
}
|
||||
|
||||
pub fn add_edge(&mut self, from: impl RenderGraphLabel, to: impl RenderGraphLabel)
|
||||
{
|
||||
let from = RenderGraphLabelValue::from(from);
|
||||
let to = RenderGraphLabelValue::from(to);
|
||||
|
||||
let from_idx = self
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|p| *p.0 == from)
|
||||
.map(|p| p.1.graph_index)
|
||||
.expect("Failed to find from pass");
|
||||
let to_idx = self
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|p| *p.0 == to)
|
||||
.map(|p| p.1.graph_index)
|
||||
.expect("Failed to find to pass");
|
||||
|
||||
debug_assert_ne!(from_idx, to_idx, "cannot add edges between the same node");
|
||||
|
||||
self.execution_graph.add_edge(from_idx, to_idx, ());
|
||||
}
|
||||
|
||||
/// Utility method for setting the bind groups for a pass.
|
||||
///
|
||||
/// The parameter `bind_groups` can be used to specify the labels of a bind group, and the
|
||||
/// index of the bind group in the pipeline for the pass. If a bind group of the provided
|
||||
/// name is not found in the graph, a panic will occur.
|
||||
///
|
||||
/// # Example:
|
||||
/// ```rust,nobuild
|
||||
/// graph.set_bind_groups(
|
||||
/// &mut pass,
|
||||
/// &[
|
||||
/// // retrieves the `BasePassSlots::DepthTexture` bind group and sets the index 0 in the
|
||||
/// // pass to it.
|
||||
/// (&BasePassSlots::DepthTexture, 0),
|
||||
/// (&BasePassSlots::Camera, 1),
|
||||
/// (&LightBasePassSlots::Lights, 2),
|
||||
/// (&LightCullComputePassSlots::LightIndicesGridGroup, 3),
|
||||
/// (&BasePassSlots::ScreenSize, 4),
|
||||
/// ],
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a bind group of a provided name is not found.
|
||||
pub fn set_bind_groups<'a>(
|
||||
&'a self,
|
||||
pass: &mut ComputePass<'a>,
|
||||
bind_groups: &[(&dyn RenderGraphLabel, u32)],
|
||||
) {
|
||||
for (label, index) in bind_groups {
|
||||
let bg = self
|
||||
.bind_group(label.rc_clone());
|
||||
//.expect(&format!("Could not find bind group '{:?}'", label));
|
||||
|
||||
pass.set_bind_group(*index, bg, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A queued write to a GPU buffer targeting a graph slot.
|
||||
pub(crate) struct GraphBufferWrite {
|
||||
/// The name of the slot that has the resource that will be written
|
||||
target_slot: RenderGraphLabelValue,
|
||||
offset: u64,
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct RenderGraphContext<'a> {
|
||||
/// The [`wgpu::CommandEncoder`] used to encode GPU operations.
|
||||
///
|
||||
/// This is `None` during the `prepare` stage.
|
||||
pub encoder: Option<wgpu::CommandEncoder>,
|
||||
/// The gpu device that is being used.
|
||||
pub device: &'a wgpu::Device,
|
||||
pub queue: &'a wgpu::Queue,
|
||||
pub(crate) buffer_writes: VecDeque<GraphBufferWrite>,
|
||||
renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a, 'a>>,
|
||||
/// The label of this Node.
|
||||
pub label: RenderGraphLabelValue,
|
||||
}
|
||||
|
||||
impl<'a> RenderGraphContext<'a> {
|
||||
pub(crate) fn new(device: &'a wgpu::Device, queue: &'a wgpu::Queue, encoder: Option<wgpu::CommandEncoder>, label: RenderGraphLabelValue) -> Self {
|
||||
Self {
|
||||
encoder,
|
||||
device,
|
||||
queue,
|
||||
buffer_writes: Default::default(),
|
||||
renderpass_desc: vec![],
|
||||
label,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn begin_render_pass(
|
||||
&'a mut self,
|
||||
desc: wgpu::RenderPassDescriptor<'a, 'a>,
|
||||
) -> wgpu::RenderPass {
|
||||
self.encoder
|
||||
.as_mut()
|
||||
.expect(
|
||||
"RenderGraphContext is missing a command encoder. This is likely \
|
||||
because you are trying to run render commands in the prepare stage.",
|
||||
)
|
||||
.begin_render_pass(&desc)
|
||||
}
|
||||
|
||||
pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass {
|
||||
self.encoder
|
||||
.as_mut()
|
||||
.expect(
|
||||
"RenderGraphContext is missing a command encoder. This is likely \
|
||||
because you are trying to run render commands in the prepare stage.",
|
||||
)
|
||||
.begin_compute_pass(desc)
|
||||
}
|
||||
|
||||
/// Queue a data write to a buffer at that is contained in `target_slot`.
|
||||
///
|
||||
/// This does not submit the data to the GPU immediately, or add it to the `wgpu::Queue`. The
|
||||
/// data will be submitted to the GPU queue right after the prepare stage for all passes
|
||||
/// is ran.
|
||||
#[instrument(skip(self, bytes), level="trace", fields(size = bytes.len()))]
|
||||
pub fn queue_buffer_write(&mut self, target_slot: impl RenderGraphLabel, offset: u64, bytes: &[u8]) {
|
||||
self.buffer_writes.push_back(GraphBufferWrite {
|
||||
target_slot: target_slot.into(),
|
||||
offset,
|
||||
bytes: bytes.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Queue a data write of a type that to a buffer at that is contained in `target_slot`.
|
||||
#[instrument(skip(self, bytes), level="trace", fields(size = std::mem::size_of::<T>()))]
|
||||
pub fn queue_buffer_write_with<T: bytemuck::NoUninit>(
|
||||
&mut self,
|
||||
target_slot: impl RenderGraphLabel,
|
||||
offset: u64,
|
||||
bytes: T,
|
||||
) {
|
||||
self.queue_buffer_write(target_slot, offset, bytemuck::bytes_of(&bytes));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,365 @@
|
|||
use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc};
|
||||
|
||||
use bind_match::bind_match;
|
||||
use lyra_ecs::World;
|
||||
|
||||
use crate::render::resource::PipelineDescriptor;
|
||||
|
||||
use super::{RenderGraph, RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderTarget};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||
pub enum NodeType {
|
||||
/// A node doesn't render, compute, or present anything. This likely means it injects data into the graph.
|
||||
#[default]
|
||||
Node,
|
||||
/// A Compute pass node type.
|
||||
Compute,
|
||||
/// A render pass node type.
|
||||
Render,
|
||||
/// A node that presents render results to a render target.
|
||||
Presenter,
|
||||
}
|
||||
|
||||
impl NodeType {
|
||||
/// Returns a boolean indicating if the node should have a [`Pipeline`](crate::render::resource::Pipeline).
|
||||
pub fn should_have_pipeline(&self) -> bool {
|
||||
match self {
|
||||
NodeType::Node => false,
|
||||
NodeType::Compute => true,
|
||||
NodeType::Render => true,
|
||||
NodeType::Presenter => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of data that is stored in a [`Node`] slot.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SlotType {
|
||||
TextureView,
|
||||
Sampler,
|
||||
Texture,
|
||||
Buffer,
|
||||
RenderTarget,
|
||||
}
|
||||
|
||||
/// The value of a slot in a [`Node`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SlotValue {
|
||||
/// This slot doesn't have any value
|
||||
None,
|
||||
/// The value will be set during a later phase of the render graph. To see the type of value
|
||||
/// this will be set to, see the slots type.
|
||||
Lazy,
|
||||
TextureView(Rc<wgpu::TextureView>),
|
||||
Sampler(Rc<wgpu::Sampler>),
|
||||
Texture(Rc<wgpu::Texture>),
|
||||
Buffer(Rc<wgpu::Buffer>),
|
||||
RenderTarget(Rc<RefCell<RenderTarget>>),
|
||||
}
|
||||
|
||||
impl SlotValue {
|
||||
pub fn as_texture_view(&self) -> Option<&Rc<wgpu::TextureView>> {
|
||||
bind_match!(self, Self::TextureView(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_sampler(&self) -> Option<&Rc<wgpu::Sampler>> {
|
||||
bind_match!(self, Self::Sampler(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_texture(&self) -> Option<&Rc<wgpu::Texture>> {
|
||||
bind_match!(self, Self::Texture(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_buffer(&self) -> Option<&Rc<wgpu::Buffer>> {
|
||||
bind_match!(self, Self::Buffer(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_render_target(&self) -> Option<Ref<RenderTarget>> {
|
||||
bind_match!(self, Self::RenderTarget(v) => v.borrow())
|
||||
}
|
||||
|
||||
pub fn as_render_target_mut(&mut self) -> Option<RefMut<RenderTarget>> {
|
||||
bind_match!(self, Self::RenderTarget(v) => v.borrow_mut())
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SlotAttribute {
|
||||
/// This slot inputs a value into the node, expecting another node to `Output` it.
|
||||
Input,
|
||||
/// This slot outputs a value from the node, providing the value to other nodes that
|
||||
/// `Input`it.
|
||||
Output,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NodeSlot {
|
||||
/// The type of the value that this slot inputs/outputs.
|
||||
pub ty: SlotType,
|
||||
/// The way this slot uses the value. Defines if this slot is an output or input.
|
||||
pub attribute: SlotAttribute,
|
||||
/// The identifying label of this slot.
|
||||
pub label: RenderGraphLabelValue,
|
||||
/// The value of the slot.
|
||||
/// This is `None` if the slot is a `SlotAttribute::Input` type.
|
||||
pub value: Option<SlotValue>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PipelineShaderDesc {
|
||||
pub label: Option<String>,
|
||||
pub source: String,
|
||||
pub entry_point: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RenderGraphPipelineInfo {
|
||||
/// Debug label of the pipeline. This will show up in graphics debuggers for easy identification.
|
||||
pub label: Option<String>,
|
||||
/// The layout of bind groups for this pipeline.
|
||||
pub bind_group_layouts: Vec<Rc<wgpu::BindGroupLayout>>,
|
||||
/// The descriptor of the vertex shader.
|
||||
pub vertex: PipelineShaderDesc,
|
||||
/// The properties of the pipeline at the primitive assembly and rasterization level.
|
||||
pub primitive: wgpu::PrimitiveState,
|
||||
/// The effect of draw calls on the depth and stencil aspects of the output target, if any.
|
||||
pub depth_stencil: Option<wgpu::DepthStencilState>,
|
||||
/// The multi-sampling properties of the pipeline.
|
||||
pub multisample: wgpu::MultisampleState,
|
||||
/// The compiled fragment stage, its entry point, and the color targets.
|
||||
pub fragment: Option<PipelineShaderDesc>,
|
||||
/// If the pipeline will be used with a multiview render pass, this indicates how many array
|
||||
/// layers the attachments will have.
|
||||
pub multiview: Option<NonZeroU32>,
|
||||
}
|
||||
|
||||
impl RenderGraphPipelineInfo {
|
||||
pub fn new(
|
||||
label: &str,
|
||||
bind_group_layouts: Vec<wgpu::BindGroupLayout>,
|
||||
vertex: PipelineShaderDesc,
|
||||
primitive: wgpu::PrimitiveState,
|
||||
depth_stencil: Option<wgpu::DepthStencilState>,
|
||||
multisample: wgpu::MultisampleState,
|
||||
fragment: Option<PipelineShaderDesc>,
|
||||
multiview: Option<NonZeroU32>,
|
||||
) -> Self {
|
||||
Self {
|
||||
label: Some(label.to_string()),
|
||||
bind_group_layouts: bind_group_layouts
|
||||
.into_iter()
|
||||
.map(|bgl| Rc::new(bgl))
|
||||
.collect(),
|
||||
vertex,
|
||||
primitive,
|
||||
depth_stencil,
|
||||
multisample,
|
||||
fragment,
|
||||
multiview,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Descriptor of a Node in a [`RenderGraph`].
|
||||
pub struct NodeDesc {
|
||||
/// The [`NodeType`] of the node.
|
||||
pub ty: NodeType,
|
||||
/// The slots that the Node uses.
|
||||
/// This defines the resources that the node uses and creates in the graph.
|
||||
pub slots: Vec<NodeSlot>,
|
||||
//slot_label_lookup: HashMap<RenderGraphLabelValue, u64>,
|
||||
/// An optional pipeline descriptor for the Node.
|
||||
/// This is `None` if the Node type is not a node that requires a pipeline
|
||||
/// (see [`NodeType::should_have_pipeline`]).
|
||||
pub pipeline_desc: Option<PipelineDescriptor>,
|
||||
/// The bind groups that this Node creates.
|
||||
/// This makes the bind groups accessible to other Nodes.
|
||||
pub bind_groups: Vec<(
|
||||
RenderGraphLabelValue,
|
||||
Rc<wgpu::BindGroup>,
|
||||
Option<Rc<wgpu::BindGroupLayout>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl NodeDesc {
|
||||
/// Create a new node descriptor.
|
||||
pub fn new(
|
||||
pass_type: NodeType,
|
||||
pipeline_desc: Option<PipelineDescriptor>,
|
||||
bind_groups: Vec<(&dyn RenderGraphLabel, Rc<wgpu::BindGroup>, Option<Rc<wgpu::BindGroupLayout>>)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ty: pass_type,
|
||||
slots: vec![],
|
||||
pipeline_desc,
|
||||
bind_groups: bind_groups
|
||||
.into_iter()
|
||||
.map(|bg| (bg.0.rc_clone().into(), bg.1, bg.2))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a slot to the descriptor.
|
||||
///
|
||||
/// In debug builds, there is an assert that triggers if the slot is an input slot and has
|
||||
/// a value set.
|
||||
pub fn add_slot(&mut self, slot: NodeSlot) {
|
||||
debug_assert!(
|
||||
!(slot.attribute == SlotAttribute::Input && slot.value.is_some()),
|
||||
"input slots should not have values"
|
||||
);
|
||||
|
||||
self.slots.push(slot);
|
||||
}
|
||||
|
||||
/// Add a buffer slot to the descriptor.
|
||||
///
|
||||
/// In debug builds, there is an assert that triggers if the slot is an input slot and has
|
||||
/// a value set. There is also an assert that is triggered if this slot value is not `None`,
|
||||
/// `SlotValue::Lazy` or a `Buffer`.
|
||||
#[inline(always)]
|
||||
pub fn add_buffer_slot<L: RenderGraphLabel>(
|
||||
&mut self,
|
||||
label: L,
|
||||
attribute: SlotAttribute,
|
||||
value: Option<SlotValue>,
|
||||
) {
|
||||
debug_assert!(
|
||||
matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::Buffer(_))),
|
||||
"slot value is not a buffer"
|
||||
);
|
||||
|
||||
let slot = NodeSlot {
|
||||
label: label.into(),
|
||||
ty: SlotType::Buffer,
|
||||
attribute,
|
||||
value,
|
||||
};
|
||||
self.add_slot(slot);
|
||||
}
|
||||
|
||||
/// Add a slot that stores a [`wgpu::Texture`] to the descriptor.
|
||||
///
|
||||
/// In debug builds, there is an assert that triggers if the slot is an input slot and has
|
||||
/// a value set. There is also an assert that is triggered if this slot value is not `None`,
|
||||
/// `SlotValue::Lazy` or a `SlotValue::Texture`.
|
||||
#[inline(always)]
|
||||
pub fn add_texture_slot<L: RenderGraphLabel>(
|
||||
&mut self,
|
||||
label: L,
|
||||
attribute: SlotAttribute,
|
||||
value: Option<SlotValue>,
|
||||
) {
|
||||
debug_assert!(
|
||||
matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::Texture(_))),
|
||||
"slot value is not a texture"
|
||||
);
|
||||
|
||||
let slot = NodeSlot {
|
||||
label: label.into(),
|
||||
ty: SlotType::Texture,
|
||||
attribute,
|
||||
value,
|
||||
};
|
||||
self.add_slot(slot);
|
||||
}
|
||||
|
||||
/// Add a slot that stores a [`wgpu::TextureView`] to the descriptor.
|
||||
///
|
||||
/// In debug builds, there is an assert that triggers if the slot is an input slot and has
|
||||
/// a value set. There is also an assert that is triggered if this slot value is not `None`,
|
||||
/// `SlotValue::Lazy` or a `SlotValue::TextureView`.
|
||||
#[inline(always)]
|
||||
pub fn add_texture_view_slot<L: RenderGraphLabel>(
|
||||
&mut self,
|
||||
label: L,
|
||||
attribute: SlotAttribute,
|
||||
value: Option<SlotValue>,
|
||||
) {
|
||||
debug_assert!(
|
||||
matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::TextureView(_))),
|
||||
"slot value is not a texture view"
|
||||
);
|
||||
|
||||
let slot = NodeSlot {
|
||||
label: label.into(),
|
||||
ty: SlotType::TextureView,
|
||||
attribute,
|
||||
value,
|
||||
};
|
||||
self.add_slot(slot);
|
||||
}
|
||||
|
||||
/// Add a slot that stores a [`wgpu::Sampler`] to the descriptor.
|
||||
///
|
||||
/// In debug builds, there is an assert that triggers if the slot is an input slot and has
|
||||
/// a value set. There is also an assert that is triggered if this slot value is not `None`,
|
||||
/// `SlotValue::Lazy` or a `SlotValue::Sampler`.
|
||||
#[inline(always)]
|
||||
pub fn add_sampler_slot<L: RenderGraphLabel>(
|
||||
&mut self,
|
||||
label: L,
|
||||
attribute: SlotAttribute,
|
||||
value: Option<SlotValue>,
|
||||
) {
|
||||
debug_assert!(
|
||||
matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::Sampler(_))),
|
||||
"slot value is not a sampler"
|
||||
);
|
||||
|
||||
let slot = NodeSlot {
|
||||
label: label.into(),
|
||||
ty: SlotType::Sampler,
|
||||
attribute,
|
||||
value,
|
||||
};
|
||||
self.add_slot(slot);
|
||||
}
|
||||
|
||||
/// Returns all input slots that the descriptor defines.
|
||||
pub fn input_slots(&self) -> Vec<&NodeSlot> {
|
||||
self.slots
|
||||
.iter()
|
||||
.filter(|s| s.attribute == SlotAttribute::Input)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns all output slots that the descriptor defines.
|
||||
pub fn output_slots(&self) -> Vec<&NodeSlot> {
|
||||
self.slots
|
||||
.iter()
|
||||
.filter(|s| s.attribute == SlotAttribute::Output)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A node that can be executed and scheduled in a [`RenderGraph`].
|
||||
///
|
||||
/// A node can be used for rendering, computing data on the GPU, collecting data from the main
|
||||
/// world and writing it to GPU buffers, or presenting renders to a surface.
|
||||
///
|
||||
/// The [`RenderGraph`] is ran in phases. The first phase is `prepare`, then `execute`. When a node
|
||||
/// is first added to a RenderGraph, its [`Node::desc`] function will be ran. The descriptor
|
||||
/// describes all resources the node requires for execution during the `execute` phase.
|
||||
pub trait Node: 'static {
|
||||
/// Retrieve a descriptor of the Node.
|
||||
fn desc<'a, 'b>(&'a mut self, graph: &'b mut RenderGraph) -> NodeDesc;
|
||||
|
||||
/// Prepare the node for rendering.
|
||||
///
|
||||
/// This phase runs before `execute` and is meant to be used to collect data from the World
|
||||
/// and write to GPU buffers.
|
||||
fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext);
|
||||
|
||||
/// Execute the node.
|
||||
///
|
||||
/// Parameters:
|
||||
/// * `graph` - The RenderGraph that this node is a part of. Can be used to get bind groups and bind to them.
|
||||
/// * `desc` - The descriptor of this node.
|
||||
/// * `context` - The rendering graph context.
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut RenderGraph,
|
||||
desc: &NodeDesc,
|
||||
context: &mut RenderGraphContext,
|
||||
);
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use glam::UVec2;
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use tracing::warn;
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
use crate::{
|
||||
render::{
|
||||
camera::{CameraUniform, RenderCamera},
|
||||
graph::{
|
||||
RenderGraphContext, Node, NodeDesc, NodeSlot,
|
||||
NodeType, RenderTarget, SlotAttribute, SlotType, SlotValue,
|
||||
},
|
||||
render_buffer::BufferWrapper, texture::RenderTexture,
|
||||
},
|
||||
scene::CameraComponent,
|
||||
};
|
||||
|
||||
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
|
||||
pub struct BasePassLabel;
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
|
||||
pub enum BasePassSlots {
|
||||
DepthTexture,
|
||||
ScreenSize,
|
||||
Camera,
|
||||
MainRenderTarget,
|
||||
WindowTextureView,
|
||||
DepthTextureView,
|
||||
}
|
||||
|
||||
/// Supplies some basic things other passes needs.
|
||||
///
|
||||
/// screen size buffer, camera buffer,
|
||||
#[derive(Default)]
|
||||
pub struct BasePass {
|
||||
/// Temporary storage for the main render target
|
||||
///
|
||||
/// This should be Some when the pass is first created then after its added to
|
||||
/// the render graph it will be None and stay None.
|
||||
temp_render_target: Option<RenderTarget>,
|
||||
screen_size: glam::UVec2,
|
||||
}
|
||||
|
||||
impl BasePass {
|
||||
pub fn new(surface: wgpu::Surface, surface_config: wgpu::SurfaceConfiguration) -> Self {
|
||||
let size = glam::UVec2::new(surface_config.width, surface_config.height);
|
||||
|
||||
Self {
|
||||
temp_render_target: Some(RenderTarget {
|
||||
surface,
|
||||
surface_config,
|
||||
current_texture: None,
|
||||
}),
|
||||
screen_size: size,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for BasePass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let render_target = self.temp_render_target.take().unwrap();
|
||||
self.screen_size = UVec2::new(
|
||||
render_target.surface_config.width,
|
||||
render_target.surface_config.height,
|
||||
);
|
||||
|
||||
let (screen_size_bgl, screen_size_bg, screen_size_buf, _) = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
.label_prefix("ScreenSize")
|
||||
.visibility(wgpu::ShaderStages::COMPUTE)
|
||||
.buffer_dynamic_offset(false)
|
||||
.contents(&[self.screen_size])
|
||||
.finish_parts(&graph.device());
|
||||
let screen_size_bgl = Rc::new(screen_size_bgl);
|
||||
let screen_size_bg = Rc::new(screen_size_bg);
|
||||
|
||||
let (camera_bgl, camera_bg, camera_buf, _) = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
.label_prefix("camera")
|
||||
.visibility(wgpu::ShaderStages::all())
|
||||
.buffer_dynamic_offset(false)
|
||||
.contents(&[CameraUniform::default()])
|
||||
.finish_parts(&graph.device());
|
||||
let camera_bgl = Rc::new(camera_bgl);
|
||||
let camera_bg = Rc::new(camera_bg);
|
||||
|
||||
// create the depth texture using the utility struct, then take all the required fields
|
||||
let mut depth_texture = RenderTexture::create_depth_texture(&graph.device(), &render_target.surface_config, "depth_texture");
|
||||
depth_texture.create_bind_group(&graph.device);
|
||||
|
||||
let dt_bg_pair = depth_texture.bindgroup_pair.unwrap();
|
||||
let depth_texture_bg = Rc::new(dt_bg_pair.bindgroup);
|
||||
let depth_texture_bgl = dt_bg_pair.layout;
|
||||
let depth_texture_view = Rc::new(depth_texture.view);
|
||||
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Node,
|
||||
None,
|
||||
vec![
|
||||
// TODO: Make this a trait maybe?
|
||||
// Could impl it for (RenderGraphLabel, wgpu::BindGroup) and also
|
||||
// (RenderGraphLabel, wgpu::BindGroup, wgpu::BindGroupLabel) AND
|
||||
// (RenderGraphLabel, wgpu::BindGroup, Option<wgpu::BindGroupLabel>)
|
||||
//
|
||||
// This could make it slightly easier to create this
|
||||
(&BasePassSlots::DepthTexture, depth_texture_bg, Some(depth_texture_bgl)),
|
||||
(&BasePassSlots::ScreenSize, screen_size_bg, Some(screen_size_bgl)),
|
||||
(&BasePassSlots::Camera, camera_bg, Some(camera_bgl)),
|
||||
],
|
||||
);
|
||||
|
||||
desc.add_slot(NodeSlot {
|
||||
ty: SlotType::RenderTarget,
|
||||
attribute: SlotAttribute::Output,
|
||||
label: BasePassSlots::MainRenderTarget.into(),
|
||||
value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new(
|
||||
render_target,
|
||||
)))),
|
||||
});
|
||||
desc.add_texture_view_slot(
|
||||
BasePassSlots::WindowTextureView,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Lazy),
|
||||
);
|
||||
desc.add_texture_view_slot(
|
||||
BasePassSlots::DepthTextureView,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::TextureView(depth_texture_view)),
|
||||
);
|
||||
desc.add_buffer_slot(
|
||||
BasePassSlots::ScreenSize,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Rc::new(screen_size_buf))),
|
||||
);
|
||||
desc.add_buffer_slot(
|
||||
BasePassSlots::Camera,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Rc::new(camera_buf))),
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
if let Some(camera) = world.view_iter::<&mut CameraComponent>().next() {
|
||||
let mut render_cam =
|
||||
RenderCamera::new(PhysicalSize::new(self.screen_size.x, self.screen_size.y));
|
||||
let uniform = render_cam.calc_view_projection(&camera);
|
||||
|
||||
context.queue_buffer_write_with(BasePassSlots::Camera, 0, uniform)
|
||||
} else {
|
||||
warn!("Missing camera!");
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
_desc: &crate::render::graph::NodeDesc,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let tv_slot = graph
|
||||
.slot_value_mut(BasePassSlots::MainRenderTarget)
|
||||
.expect("somehow the main render target slot is missing");
|
||||
let mut rt = tv_slot.as_render_target_mut().unwrap();
|
||||
debug_assert!(
|
||||
!rt.current_texture.is_some(),
|
||||
"main render target surface was not presented!"
|
||||
);
|
||||
|
||||
// update the screen size buffer if the size changed.
|
||||
if rt.surface_config.width != self.screen_size.x
|
||||
|| rt.surface_config.height != self.screen_size.y
|
||||
{
|
||||
self.screen_size = UVec2::new(rt.surface_config.width, rt.surface_config.height);
|
||||
context.queue_buffer_write_with(BasePassSlots::ScreenSize, 0, self.screen_size)
|
||||
}
|
||||
|
||||
let surface_tex = rt.surface.get_current_texture().unwrap();
|
||||
let view = surface_tex
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
rt.current_texture = Some(surface_tex);
|
||||
drop(rt); // must be manually dropped for borrow checker when getting texture view slot
|
||||
|
||||
// store the surface texture to the slot
|
||||
let tv_slot = graph
|
||||
.slot_value_mut(BasePassSlots::WindowTextureView)
|
||||
.expect("somehow the window texture view slot is missing");
|
||||
*tv_slot = SlotValue::TextureView(Rc::new(view));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::{
|
||||
graph::{
|
||||
RenderGraphContext, Node, NodeDesc, NodeType, SlotAttribute,
|
||||
SlotValue,
|
||||
},
|
||||
light::LightUniformBuffers,
|
||||
};
|
||||
|
||||
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
|
||||
pub struct LightBasePassLabel;
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
|
||||
pub enum LightBasePassSlots {
|
||||
Lights
|
||||
}
|
||||
|
||||
/// Supplies some basic things other passes needs.
|
||||
///
|
||||
/// screen size buffer, camera buffer,
|
||||
#[derive(Default)]
|
||||
pub struct LightBasePass {
|
||||
light_buffers: Option<LightUniformBuffers>,
|
||||
}
|
||||
|
||||
impl LightBasePass {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for LightBasePass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let device = &graph.device;
|
||||
self.light_buffers = Some(LightUniformBuffers::new(device));
|
||||
let light_buffers = self.light_buffers.as_ref().unwrap();
|
||||
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Node,
|
||||
None,
|
||||
vec![(
|
||||
&LightBasePassSlots::Lights,
|
||||
light_buffers.bind_group.clone(),
|
||||
Some(light_buffers.bind_group_layout.clone()),
|
||||
)],
|
||||
);
|
||||
|
||||
desc.add_buffer_slot(
|
||||
LightBasePassSlots::Lights,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(light_buffers.buffer.clone())),
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
let tick = world.current_tick();
|
||||
let lights = self.light_buffers.as_mut().unwrap();
|
||||
lights.update_lights(context.queue, tick, world);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
_graph: &mut crate::render::graph::RenderGraph,
|
||||
_desc: &crate::render::graph::NodeDesc,
|
||||
_context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
use std::{mem, rc::Rc};
|
||||
|
||||
use lyra_ecs::World;
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::render::{
|
||||
graph::{
|
||||
RenderGraphContext, Node, NodeDesc, NodeType, SlotAttribute,
|
||||
SlotValue,
|
||||
},
|
||||
resource::{ComputePipelineDescriptor, PipelineDescriptor, Shader},
|
||||
};
|
||||
|
||||
use super::{BasePassSlots, LightBasePassSlots};
|
||||
|
||||
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
|
||||
pub struct LightCullComputePassLabel;
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
|
||||
pub enum LightCullComputePassSlots {
|
||||
LightGridTexture,
|
||||
LightGridTextureView,
|
||||
IndexCounterBuffer,
|
||||
LightIndicesGridGroup,
|
||||
}
|
||||
|
||||
pub struct LightCullComputePass {
|
||||
workgroup_size: glam::UVec2,
|
||||
}
|
||||
|
||||
impl LightCullComputePass {
|
||||
pub fn new(screen_size: winit::dpi::PhysicalSize<u32>) -> Self {
|
||||
Self {
|
||||
workgroup_size: glam::UVec2::new(screen_size.width, screen_size.height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for LightCullComputePass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some("light_cull_comp_shader".into()),
|
||||
source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(),
|
||||
});
|
||||
|
||||
// get the size of the work group for the grid
|
||||
let main_rt = graph
|
||||
.slot_value(BasePassSlots::MainRenderTarget)
|
||||
.and_then(|s| s.as_render_target())
|
||||
.expect("missing main render target");
|
||||
self.workgroup_size =
|
||||
glam::UVec2::new(main_rt.surface_config.width, main_rt.surface_config.height);
|
||||
|
||||
// initialize some buffers with empty data
|
||||
let mut contents = Vec::<u8>::new();
|
||||
let contents_len =
|
||||
self.workgroup_size.x * self.workgroup_size.y * mem::size_of::<u32>() as u32;
|
||||
contents.resize(contents_len as _, 0);
|
||||
|
||||
let device = graph.device();
|
||||
let light_indices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("light_indices_buffer"),
|
||||
contents: &contents,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let light_index_counter_buffer =
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("light_index_counter_buffer"),
|
||||
contents: &bytemuck::cast_slice(&[0]),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let light_indices_bg_layout = Rc::new(device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: false },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::StorageTexture {
|
||||
access: wgpu::StorageTextureAccess::ReadWrite,
|
||||
format: wgpu::TextureFormat::Rg32Uint, // vec2<uint>
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: false },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("light_indices_grid_bgl"),
|
||||
},
|
||||
));
|
||||
|
||||
let size = wgpu::Extent3d {
|
||||
width: self.workgroup_size.x,
|
||||
height: self.workgroup_size.y,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
let grid_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("light_grid_tex"),
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rg32Uint, // vec2<uint>
|
||||
usage: wgpu::TextureUsages::STORAGE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let grid_texture_view = grid_texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
label: Some("light_grid_texview"),
|
||||
format: Some(wgpu::TextureFormat::Rg32Uint), // vec2<uint>
|
||||
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
base_mip_level: 0,
|
||||
mip_level_count: None,
|
||||
base_array_layer: 0,
|
||||
array_layer_count: None,
|
||||
});
|
||||
|
||||
let light_indices_bg = Rc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &light_indices_bg_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &light_indices_buffer,
|
||||
offset: 0,
|
||||
size: None, // the entire light buffer is needed
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(&grid_texture_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &light_index_counter_buffer,
|
||||
offset: 0,
|
||||
size: None, // the entire light buffer is needed
|
||||
}),
|
||||
},
|
||||
],
|
||||
label: Some("light_indices_grid_bind_group"),
|
||||
}));
|
||||
|
||||
drop(main_rt);
|
||||
|
||||
let depth_tex_bgl = graph.bind_group_layout(BasePassSlots::DepthTexture);
|
||||
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera);
|
||||
let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights);
|
||||
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize);
|
||||
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Compute,
|
||||
Some(PipelineDescriptor::Compute(ComputePipelineDescriptor {
|
||||
label: Some("light_cull_pipeline".into()),
|
||||
push_constant_ranges: vec![],
|
||||
layouts: vec![
|
||||
depth_tex_bgl.clone(),
|
||||
camera_bgl.clone(),
|
||||
lights_bgl.clone(),
|
||||
light_indices_bg_layout.clone(),
|
||||
screen_size_bgl.clone(),
|
||||
],
|
||||
shader,
|
||||
shader_entry_point: "cs_main".into(),
|
||||
})),
|
||||
vec![(
|
||||
&LightCullComputePassSlots::LightIndicesGridGroup,
|
||||
light_indices_bg,
|
||||
Some(light_indices_bg_layout),
|
||||
)],
|
||||
);
|
||||
|
||||
desc.add_texture_view_slot(
|
||||
BasePassSlots::WindowTextureView,
|
||||
SlotAttribute::Input,
|
||||
None,
|
||||
);
|
||||
desc.add_buffer_slot(
|
||||
BasePassSlots::ScreenSize,
|
||||
SlotAttribute::Input,
|
||||
None,
|
||||
);
|
||||
desc.add_buffer_slot(BasePassSlots::Camera, SlotAttribute::Input, None);
|
||||
desc.add_buffer_slot(
|
||||
LightCullComputePassSlots::IndexCounterBuffer,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Rc::new(light_index_counter_buffer))),
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _world: &mut World, _context: &mut RenderGraphContext) {}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
context: &mut RenderGraphContext,
|
||||
) {
|
||||
let label = context.label.clone();
|
||||
|
||||
let pipeline = graph.pipeline(label)
|
||||
.expect("Failed to find Pipeline for LightCullComputePass");
|
||||
let pipeline = pipeline.as_compute();
|
||||
|
||||
let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
||||
label: Some("light_cull_pass"),
|
||||
});
|
||||
|
||||
pass.set_pipeline(pipeline);
|
||||
|
||||
graph.set_bind_groups(
|
||||
&mut pass,
|
||||
&[
|
||||
(&BasePassSlots::DepthTexture, 0),
|
||||
(&BasePassSlots::Camera, 1),
|
||||
(&LightBasePassSlots::Lights, 2),
|
||||
(&LightCullComputePassSlots::LightIndicesGridGroup, 3),
|
||||
(&BasePassSlots::ScreenSize, 4),
|
||||
],
|
||||
);
|
||||
|
||||
pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,556 @@
|
|||
use std::{collections::{HashSet, VecDeque}, rc::Rc};
|
||||
|
||||
use glam::Vec3;
|
||||
use itertools::izip;
|
||||
use lyra_ecs::{query::{filter::{Has, Not, Or}, Entities, Res, TickOf}, relation::{ChildOf, RelationOriginComponent}, Component, Entity};
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use lyra_math::Transform;
|
||||
use lyra_resource::{gltf::Mesh, ResHandle};
|
||||
use lyra_scene::{SceneGraph, WorldTransform};
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::{debug, instrument, warn};
|
||||
use uuid::Uuid;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::{
|
||||
render::{
|
||||
desc_buf_lay::DescVertexBufferLayout, graph::{
|
||||
RenderGraphContext, Node, NodeDesc,
|
||||
NodeType,
|
||||
}, material::{Material, MaterialUniform}, render_buffer::{BufferStorage, BufferWrapper}, render_job::RenderJob, resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, texture::RenderTexture, transform_buffer_storage::{TransformBuffers, TransformGroup}, vertex::Vertex
|
||||
},
|
||||
DeltaTime,
|
||||
};
|
||||
|
||||
use super::{BasePassSlots, LightBasePassSlots, LightCullComputePassSlots};
|
||||
|
||||
type MeshHandle = ResHandle<Mesh>;
|
||||
type SceneHandle = ResHandle<SceneGraph>;
|
||||
|
||||
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
|
||||
pub struct MeshesPassLabel;
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
|
||||
pub enum MeshesPassSlots {
|
||||
Material
|
||||
}
|
||||
|
||||
struct MeshBufferStorage {
|
||||
buffer_vertex: BufferStorage,
|
||||
buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>,
|
||||
|
||||
// maybe this should just be a Uuid and the material can be retrieved though
|
||||
// MeshPass's `material_buffers` field?
|
||||
material: Option<Rc<Material>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Component)]
|
||||
struct InterpTransform {
|
||||
last_transform: Transform,
|
||||
alpha: f32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MeshPass {
|
||||
transforms: Option<TransformBuffers>,
|
||||
mesh_buffers: FxHashMap<uuid::Uuid, MeshBufferStorage>,
|
||||
render_jobs: VecDeque<RenderJob>,
|
||||
|
||||
texture_bind_group_layout: Option<Rc<wgpu::BindGroupLayout>>,
|
||||
material_buffer: Option<wgpu::Buffer>,
|
||||
material_buffers: FxHashMap<uuid::Uuid, Rc<Material>>,
|
||||
entity_meshes: FxHashMap<Entity, uuid::Uuid>,
|
||||
|
||||
default_texture: Option<RenderTexture>,
|
||||
}
|
||||
|
||||
impl MeshPass {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Checks if the mesh buffers in the GPU need to be updated.
|
||||
#[instrument(skip(self, device, queue, mesh_han))]
|
||||
fn check_mesh_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, mesh_han: &ResHandle<Mesh>) {
|
||||
let mesh_uuid = mesh_han.uuid();
|
||||
|
||||
if let (Some(mesh), Some(buffers)) = (mesh_han.data_ref(), self.mesh_buffers.get_mut(&mesh_uuid)) {
|
||||
// check if the buffer sizes dont match. If they dont, completely remake the buffers
|
||||
let vertices = mesh.position().unwrap();
|
||||
if buffers.buffer_vertex.count() != vertices.len() {
|
||||
debug!("Recreating buffers for mesh {}", mesh_uuid.to_string());
|
||||
let (vert, idx) = self.create_vertex_index_buffers(device, &mesh);
|
||||
|
||||
// have to re-get buffers because of borrow checker
|
||||
let buffers = self.mesh_buffers.get_mut(&mesh_uuid).unwrap();
|
||||
buffers.buffer_indices = idx;
|
||||
buffers.buffer_vertex = vert;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// update vertices
|
||||
let vertex_buffer = buffers.buffer_vertex.buffer();
|
||||
let vertices = vertices.as_slice();
|
||||
// align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT)
|
||||
let (_, vertices, _) = bytemuck::pod_align_to::<Vec3, u32>(vertices);
|
||||
queue.write_buffer(vertex_buffer, 0, bytemuck::cast_slice(vertices));
|
||||
|
||||
// update the indices if they're given
|
||||
if let Some(index_buffer) = buffers.buffer_indices.as_ref() {
|
||||
let aligned_indices = match mesh.indices.as_ref().unwrap() {
|
||||
// U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size.
|
||||
lyra_resource::gltf::MeshIndices::U16(v) => bytemuck::pod_align_to::<u16, u32>(v).1,
|
||||
lyra_resource::gltf::MeshIndices::U32(v) => bytemuck::pod_align_to::<u32, u32>(v).1,
|
||||
};
|
||||
|
||||
let index_buffer = index_buffer.1.buffer();
|
||||
queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(aligned_indices));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, device, mesh))]
|
||||
fn create_vertex_index_buffers(&mut self, device: &wgpu::Device, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) {
|
||||
let positions = mesh.position().unwrap();
|
||||
let tex_coords: Vec<glam::Vec2> = mesh.tex_coords().cloned()
|
||||
.unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]);
|
||||
let normals = mesh.normals().unwrap();
|
||||
|
||||
assert!(positions.len() == tex_coords.len() && positions.len() == normals.len());
|
||||
|
||||
let mut vertex_inputs = vec![];
|
||||
for (v, t, n) in izip!(positions.iter(), tex_coords.iter(), normals.iter()) {
|
||||
vertex_inputs.push(Vertex::new(*v, *t, *n));
|
||||
}
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(vertex_inputs.as_slice()),//vertex_combined.as_slice(),
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST,
|
||||
}
|
||||
);
|
||||
let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len());
|
||||
|
||||
let indices = match mesh.indices.as_ref() {
|
||||
Some(indices) => {
|
||||
let (idx_type, len, contents) = match indices {
|
||||
lyra_resource::gltf::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)),
|
||||
lyra_resource::gltf::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v)),
|
||||
};
|
||||
|
||||
let index_buffer = device.create_buffer_init(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Index Buffer"),
|
||||
contents,
|
||||
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages:: COPY_DST,
|
||||
}
|
||||
);
|
||||
|
||||
let buffer_indices = BufferStorage::new(index_buffer, 0, len);
|
||||
|
||||
Some((idx_type, buffer_indices))
|
||||
},
|
||||
None => {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
( vertex_buffer, indices )
|
||||
}
|
||||
|
||||
#[instrument(skip(self, device, queue, mesh))]
|
||||
fn create_mesh_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, mesh: &Mesh) -> MeshBufferStorage {
|
||||
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(device, mesh);
|
||||
|
||||
let material = mesh.material.as_ref()
|
||||
.expect("Material resource not loaded yet");
|
||||
let material_ref = material.data_ref()
|
||||
.unwrap();
|
||||
|
||||
let material = self.material_buffers.entry(material.uuid())
|
||||
.or_insert_with(|| {
|
||||
debug!(uuid=material.uuid().to_string(), "Sending material to gpu");
|
||||
Rc::new(Material::from_resource(&device, &queue, self.texture_bind_group_layout.clone().unwrap(), &material_ref))
|
||||
});
|
||||
|
||||
// TODO: support material uniforms from multiple uniforms
|
||||
let uni = MaterialUniform::from(&**material);
|
||||
queue.write_buffer(&self.material_buffer.as_ref().unwrap(), 0, bytemuck::bytes_of(&uni));
|
||||
|
||||
MeshBufferStorage {
|
||||
buffer_vertex: vertex_buffer,
|
||||
buffer_indices,
|
||||
material: Some(material.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed.
|
||||
#[instrument(skip(self, device, queue, mesh, entity))]
|
||||
fn process_mesh(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, entity: Entity, mesh: &Mesh, mesh_uuid: Uuid) -> bool {
|
||||
#[allow(clippy::map_entry)]
|
||||
if !self.mesh_buffers.contains_key(&mesh_uuid) {
|
||||
// create the mesh's buffers
|
||||
let buffers = self.create_mesh_buffers(device, queue, mesh);
|
||||
self.mesh_buffers.insert(mesh_uuid, buffers);
|
||||
self.entity_meshes.insert(entity, mesh_uuid);
|
||||
|
||||
true
|
||||
} else { false }
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for MeshPass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
|
||||
let device = graph.device();
|
||||
|
||||
let transforms = TransformBuffers::new(device);
|
||||
let transform_bgl = transforms.bindgroup_layout.clone();
|
||||
self.transforms = Some(transforms);
|
||||
|
||||
let texture_bind_group_layout = Rc::new(RenderTexture::create_layout(&device));
|
||||
self.texture_bind_group_layout = Some(texture_bind_group_layout.clone());
|
||||
|
||||
let (material_bgl, material_bg, material_buf, _) = BufferWrapper::builder()
|
||||
.label_prefix("material")
|
||||
.visibility(wgpu::ShaderStages::FRAGMENT)
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
.contents(&[MaterialUniform::default()])
|
||||
.finish_parts(device);
|
||||
let material_bgl = Rc::new(material_bgl);
|
||||
let material_bg = Rc::new(material_bg);
|
||||
|
||||
self.material_buffer = Some(material_buf);
|
||||
|
||||
// load the default texture
|
||||
let bytes = include_bytes!("../../default_texture.png");
|
||||
self.default_texture = Some(RenderTexture::from_bytes(&device, &graph.queue, texture_bind_group_layout.clone(), bytes, "default_texture").unwrap());
|
||||
|
||||
// get surface config format
|
||||
let main_rt = graph.slot_value(BasePassSlots::MainRenderTarget)
|
||||
.and_then(|s| s.as_render_target())
|
||||
.expect("missing main render target");
|
||||
let surface_config_format = main_rt.surface_config.format;
|
||||
drop(main_rt);
|
||||
|
||||
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera);
|
||||
let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights);
|
||||
let light_grid_bgl = graph
|
||||
.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some("base_shader".into()),
|
||||
source: include_str!("../../shaders/base.wgsl").to_string(),
|
||||
});
|
||||
|
||||
let desc = NodeDesc::new(
|
||||
NodeType::Render,
|
||||
Some(PipelineDescriptor::Render(RenderPipelineDescriptor {
|
||||
label: Some("meshes".into()),
|
||||
layouts: vec![
|
||||
texture_bind_group_layout.clone(),
|
||||
transform_bgl,
|
||||
camera_bgl.clone(),
|
||||
lights_bgl.clone(),
|
||||
material_bgl.clone(),
|
||||
texture_bind_group_layout,
|
||||
light_grid_bgl.clone(),
|
||||
],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: VertexState {
|
||||
module: shader.clone(),
|
||||
entry_point: "vs_main".into(),
|
||||
buffers: vec![
|
||||
Vertex::desc().into(),
|
||||
],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
module: shader,
|
||||
entry_point: "fs_main".into(),
|
||||
targets: vec![Some(wgpu::ColorTargetState {
|
||||
format: surface_config_format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: RenderTexture::DEPTH_FORMAT,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(), // TODO: stencil buffer
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
})),
|
||||
vec![
|
||||
(&MeshesPassSlots::Material, material_bg, Some(material_bgl)),
|
||||
],
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
#[instrument(skip(self, world, context))]
|
||||
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
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();
|
||||
|
||||
let view = world.view_iter::<(
|
||||
Entities,
|
||||
&Transform,
|
||||
TickOf<Transform>,
|
||||
Or<
|
||||
(&MeshHandle, TickOf<MeshHandle>),
|
||||
(&SceneHandle, TickOf<SceneHandle>)
|
||||
>,
|
||||
Option<&mut InterpTransform>,
|
||||
Res<DeltaTime>,
|
||||
)>();
|
||||
|
||||
// used to store InterpTransform components to add to entities later
|
||||
let mut component_queue: Vec<(Entity, InterpTransform)> = vec![];
|
||||
|
||||
for (
|
||||
entity,
|
||||
transform,
|
||||
_transform_epoch,
|
||||
(
|
||||
mesh_pair,
|
||||
scene_pair
|
||||
),
|
||||
interp_tran,
|
||||
delta_time,
|
||||
) in view
|
||||
{
|
||||
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
|
||||
let transforms = self.transforms.as_mut().unwrap();
|
||||
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
|
||||
// shows that the scene has changed, verify that the mesh buffers
|
||||
// dont need to be resent to the gpu.
|
||||
if !self.process_mesh(device, queue, entity, &*mesh, mesh_han.uuid())
|
||||
&& mesh_epoch == last_epoch {
|
||||
self.check_mesh_buffers(device, queue, &mesh_han);
|
||||
}
|
||||
|
||||
let transforms = self.transforms.as_mut().unwrap();
|
||||
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);
|
||||
self.render_jobs.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)>() {
|
||||
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.
|
||||
if !self.process_mesh(device, queue, entity, &*mesh, mesh_han.uuid())
|
||||
&& scene_epoch == last_epoch {
|
||||
self.check_mesh_buffers(device, queue, &mesh_han);
|
||||
}
|
||||
|
||||
let transforms = self.transforms.as_mut().unwrap();
|
||||
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);
|
||||
self.render_jobs.push_back(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (en, interp) in component_queue {
|
||||
world.insert(en, interp);
|
||||
}
|
||||
|
||||
let transforms = self.transforms.as_mut().unwrap();
|
||||
transforms.send_to_gpu(queue);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let encoder = context.encoder.as_mut().unwrap();
|
||||
|
||||
let view = graph
|
||||
.slot_value(BasePassSlots::WindowTextureView)
|
||||
.unwrap()
|
||||
.as_texture_view()
|
||||
.expect("BasePassSlots::WindowTextureView was not a TextureView slot");
|
||||
|
||||
let depth_view = graph
|
||||
.slot_value(BasePassSlots::DepthTextureView)
|
||||
.unwrap()
|
||||
.as_texture_view()
|
||||
.expect("BasePassSlots::DepthTextureView was not a TextureView slot");
|
||||
|
||||
let camera_bg = graph
|
||||
.bind_group(BasePassSlots::Camera);
|
||||
|
||||
let lights_bg = graph
|
||||
.bind_group(LightBasePassSlots::Lights);
|
||||
|
||||
let light_grid_bg = graph
|
||||
.bind_group(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||
|
||||
let material_bg = graph
|
||||
.bind_group(MeshesPassSlots::Material);
|
||||
|
||||
let pipeline = graph.pipeline(context.label.clone())
|
||||
.expect("Failed to find pipeline for MeshPass");
|
||||
|
||||
let mut 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::Clear(wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
// enable depth buffer
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
});
|
||||
|
||||
pass.set_pipeline(&pipeline.as_render());
|
||||
|
||||
//let material_buffer_bg = self.material_buffer.as_ref().unwrap().bindgroup();
|
||||
let default_texture = self.default_texture.as_ref().unwrap();
|
||||
let transforms = self.transforms.as_mut().unwrap();
|
||||
|
||||
while let Some(job) = self.render_jobs.pop_front() {
|
||||
// get the mesh (containing vertices) and the buffers from storage
|
||||
let buffers = self.mesh_buffers.get(&job.mesh_uuid);
|
||||
if buffers.is_none() {
|
||||
warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid);
|
||||
continue;
|
||||
}
|
||||
let buffers = buffers.unwrap();
|
||||
|
||||
// Bind the optional texture
|
||||
if let Some(tex) = buffers.material.as_ref()
|
||||
.and_then(|m| m.diffuse_texture.as_ref()) {
|
||||
pass.set_bind_group(0, tex.bind_group(), &[]);
|
||||
} else {
|
||||
pass.set_bind_group(0, default_texture.bind_group(), &[]);
|
||||
}
|
||||
|
||||
if let Some(tex) = buffers.material.as_ref()
|
||||
.and_then(|m| m.specular.as_ref())
|
||||
.and_then(|s| s.texture.as_ref().or(s.color_texture.as_ref())) {
|
||||
pass.set_bind_group(5, tex.bind_group(), &[]);
|
||||
} else {
|
||||
pass.set_bind_group(5, default_texture.bind_group(), &[]);
|
||||
}
|
||||
|
||||
// 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(3, &lights_bg, &[]);
|
||||
pass.set_bind_group(4, &material_bg, &[]);
|
||||
|
||||
pass.set_bind_group(6, &light_grid_bg, &[]);
|
||||
|
||||
// if this mesh uses indices, use them to draw the mesh
|
||||
if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() {
|
||||
let indices_len = indices.count() as u32;
|
||||
|
||||
pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..));
|
||||
pass.set_index_buffer(indices.buffer().slice(..), *idx_type);
|
||||
pass.draw_indexed(0..indices_len, 0, 0..1);
|
||||
} else {
|
||||
let vertex_count = buffers.buffer_vertex.count();
|
||||
|
||||
pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..));
|
||||
pass.draw(0..vertex_count as u32, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
mod light_cull_compute;
|
||||
pub use light_cull_compute::*;
|
||||
|
||||
mod base;
|
||||
pub use base::*;
|
||||
|
||||
mod meshes;
|
||||
pub use meshes::*;
|
||||
|
||||
mod light_base;
|
||||
pub use light_base::*;
|
||||
|
||||
mod present_pass;
|
||||
pub use present_pass::*;
|
|
@ -0,0 +1,64 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::graph::{RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, Node, NodeDesc, NodeSlot, NodeType, SlotAttribute, SlotType};
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
|
||||
pub struct PresentPassLabel(RenderGraphLabelValue);
|
||||
|
||||
impl PresentPassLabel {
|
||||
pub fn new(label: impl RenderGraphLabel) -> Self {
|
||||
Self(label.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Supplies some basic things other passes needs.
|
||||
///
|
||||
/// screen size buffer, camera buffer,
|
||||
pub struct PresentPass {
|
||||
/// Label of this pass
|
||||
pub label: PresentPassLabel,
|
||||
}
|
||||
|
||||
impl PresentPass {
|
||||
pub fn new(render_target_slot: impl RenderGraphLabel) -> Self {
|
||||
Self {
|
||||
//render_target_slot: render_target_slot.rc_clone(),
|
||||
label: PresentPassLabel::new(render_target_slot),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for PresentPass {
|
||||
fn desc(&mut self, _graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::NodeDesc {
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Presenter,
|
||||
None,
|
||||
vec![],
|
||||
);
|
||||
|
||||
desc.add_slot(
|
||||
NodeSlot {
|
||||
ty: SlotType::RenderTarget,
|
||||
attribute: SlotAttribute::Input,
|
||||
label: self.label.0.clone(),
|
||||
value: None,
|
||||
}
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _world: &mut lyra_ecs::World, _context: &mut RenderGraphContext) {
|
||||
|
||||
}
|
||||
|
||||
fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::NodeDesc, _context: &mut crate::render::graph::RenderGraphContext) {
|
||||
let mut slot = graph.slot_value_mut(self.label.0.clone())
|
||||
.expect(&format!("render target slot '{:?}' for PresentPass is missing", self.label.0))
|
||||
.as_render_target_mut().unwrap();
|
||||
let surf_tex = slot.current_texture.take().unwrap();
|
||||
surf_tex.present();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
use std::{mem, num::{NonZeroU32, NonZeroU8}};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TextureViewDescriptor {
|
||||
/// The label of the texture that this view will be created from.
|
||||
pub texture_label: String,
|
||||
/// Format of the texture view. At this time, it must be the same as the underlying format of the texture.
|
||||
pub format: Option<wgpu::TextureFormat>,
|
||||
/// The dimension of the texture view. For 1D textures, this must be `D1`. For 2D textures it must be one of
|
||||
/// `D2`, `D2Array`, `Cube`, and `CubeArray`. For 3D textures it must be `D3`
|
||||
pub dimension: Option<wgpu::TextureViewDimension>,
|
||||
/// Aspect of the texture. Color textures must be [`TextureAspect::All`].
|
||||
pub aspect: wgpu::TextureAspect,
|
||||
/// Base mip level.
|
||||
pub base_mip_level: u32,
|
||||
/// Mip level count.
|
||||
/// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count.
|
||||
/// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total.
|
||||
pub mip_level_count: Option<NonZeroU32>,
|
||||
/// Base array layer.
|
||||
pub base_array_layer: u32,
|
||||
/// Layer count.
|
||||
/// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count.
|
||||
/// If `None`, considered to include the rest of the array layers, but at least 1 in total.
|
||||
pub array_layer_count: Option<NonZeroU32>,
|
||||
}
|
||||
|
||||
impl TextureViewDescriptor {
|
||||
pub fn default_view(texture_label: &str) -> Self {
|
||||
let d = wgpu::TextureViewDescriptor::default();
|
||||
|
||||
Self {
|
||||
texture_label: texture_label.to_string(),
|
||||
format: d.format,
|
||||
dimension: d.dimension,
|
||||
aspect: d.aspect,
|
||||
base_array_layer: d.base_array_layer,
|
||||
base_mip_level: d.base_mip_level,
|
||||
mip_level_count: d.mip_level_count,
|
||||
array_layer_count: d.array_layer_count,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::TextureViewDescriptor<'a> {
|
||||
wgpu::TextureViewDescriptor {
|
||||
label,
|
||||
format: self.format,
|
||||
dimension: self.dimension,
|
||||
aspect: self.aspect,
|
||||
base_mip_level: self.base_mip_level,
|
||||
mip_level_count: self.mip_level_count,
|
||||
base_array_layer: self.base_array_layer,
|
||||
array_layer_count: self.array_layer_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SamplerDescriptor {
|
||||
/// The label of the texture that this view will be created from.
|
||||
pub texture_label: String,
|
||||
/// How to deal with out of bounds accesses in the u (i.e. x) direction
|
||||
pub address_mode_u: wgpu::AddressMode,
|
||||
/// How to deal with out of bounds accesses in the v (i.e. y) direction
|
||||
pub address_mode_v: wgpu::AddressMode,
|
||||
/// How to deal with out of bounds accesses in the w (i.e. z) direction
|
||||
pub address_mode_w: wgpu::AddressMode,
|
||||
/// How to filter the texture when it needs to be magnified (made larger)
|
||||
pub mag_filter: wgpu::FilterMode,
|
||||
/// How to filter the texture when it needs to be minified (made smaller)
|
||||
pub min_filter: wgpu::FilterMode,
|
||||
/// How to filter between mip map levels
|
||||
pub mipmap_filter: wgpu::FilterMode,
|
||||
/// Minimum level of detail (i.e. mip level) to use
|
||||
pub lod_min_clamp: f32,
|
||||
/// Maximum level of detail (i.e. mip level) to use
|
||||
pub lod_max_clamp: f32,
|
||||
/// If this is enabled, this is a comparison sampler using the given comparison function.
|
||||
pub compare: Option<wgpu::CompareFunction>,
|
||||
/// Valid values: 1, 2, 4, 8, and 16.
|
||||
pub anisotropy_clamp: Option<NonZeroU8>,
|
||||
/// Border color to use when address_mode is [`AddressMode::ClampToBorder`]
|
||||
pub border_color: Option<wgpu::SamplerBorderColor>,
|
||||
}
|
||||
|
||||
impl SamplerDescriptor {
|
||||
pub fn default_sampler(texture_label: &str) -> Self {
|
||||
let d = wgpu::SamplerDescriptor::default();
|
||||
|
||||
Self {
|
||||
texture_label: texture_label.to_string(),
|
||||
address_mode_u: d.address_mode_u,
|
||||
address_mode_v: d.address_mode_v,
|
||||
address_mode_w: d.address_mode_w,
|
||||
mag_filter: d.mag_filter,
|
||||
min_filter: d.min_filter,
|
||||
mipmap_filter: d.mipmap_filter,
|
||||
lod_min_clamp: d.lod_min_clamp,
|
||||
lod_max_clamp: d.lod_max_clamp,
|
||||
compare: d.compare,
|
||||
anisotropy_clamp: d.anisotropy_clamp,
|
||||
border_color: d.border_color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TextureDescriptor {
|
||||
/// Size of the texture. All components must be greater than zero. For a
|
||||
/// regular 1D/2D texture, the unused sizes will be 1. For 2DArray textures,
|
||||
/// Z is the number of 2D textures in that array.
|
||||
pub size: wgpu::Extent3d,
|
||||
/// Mip count of texture. For a texture with no extra mips, this must be 1.
|
||||
pub mip_level_count: u32,
|
||||
/// Sample count of texture. If this is not 1, texture must have [`BindingType::Texture::multisampled`] set to true.
|
||||
pub sample_count: u32,
|
||||
/// Dimensions of the texture.
|
||||
pub dimension: wgpu::TextureDimension,
|
||||
/// Format of the texture.
|
||||
pub format: wgpu::TextureFormat,
|
||||
/// Allowed usages of the texture. If used in other ways, the operation will panic.
|
||||
pub usage: wgpu::TextureUsages,
|
||||
/// Specifies what view formats will be allowed when calling create_view() on this texture.
|
||||
///
|
||||
/// View formats of the same format as the texture are always allowed.
|
||||
///
|
||||
/// Note: currently, only the srgb-ness is allowed to change. (ex: Rgba8Unorm texture + Rgba8UnormSrgb view)
|
||||
pub view_formats: Vec<wgpu::TextureFormat>,
|
||||
}
|
||||
|
||||
impl TextureDescriptor {
|
||||
pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::TextureDescriptor<'a> {
|
||||
wgpu::TextureDescriptor {
|
||||
label,
|
||||
size: self.size,
|
||||
mip_level_count: self.mip_level_count,
|
||||
sample_count: self.sample_count,
|
||||
dimension: self.dimension,
|
||||
format: self.format,
|
||||
usage: self.usage,
|
||||
view_formats: &self.view_formats,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct BufferDescriptor {
|
||||
/// Size of a buffer.
|
||||
pub size: wgpu::BufferAddress,
|
||||
/// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation
|
||||
/// will panic.
|
||||
pub usage: wgpu::BufferUsages,
|
||||
/// Allows a buffer to be mapped immediately after they are made. It does not have to be [`BufferUsages::MAP_READ`] or
|
||||
/// [`BufferUsages::MAP_WRITE`], all buffers are allowed to be mapped at creation.
|
||||
///
|
||||
/// If this is `true`, [`size`](#structfield.size) must be a multiple of
|
||||
/// [`COPY_BUFFER_ALIGNMENT`].
|
||||
pub mapped_at_creation: bool,
|
||||
}
|
||||
|
||||
impl BufferDescriptor {
|
||||
pub fn new<T: Sized>(usage: wgpu::BufferUsages, mapped_at_creation: bool) -> Self {
|
||||
Self {
|
||||
size: mem::size_of::<T>() as _,
|
||||
usage,
|
||||
mapped_at_creation,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::BufferDescriptor<'a> {
|
||||
wgpu::BufferDescriptor {
|
||||
label,
|
||||
size: self.size,
|
||||
usage: self.usage,
|
||||
mapped_at_creation: self.mapped_at_creation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct BufferInitDescriptor {
|
||||
/// Debug label of a buffer. This will show up in graphics debuggers for easy identification.
|
||||
pub label: Option<String>,
|
||||
/// Contents of a buffer on creation.
|
||||
pub contents: Vec<u8>,
|
||||
/// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation
|
||||
/// will panic.
|
||||
pub usage: wgpu::BufferUsages,
|
||||
}
|
||||
|
||||
impl BufferInitDescriptor {
|
||||
pub fn new<T: bytemuck::Pod>(label: Option<&str>, data: &T, usage: wgpu::BufferUsages) -> Self {
|
||||
Self {
|
||||
label: label.map(|s| s.to_string()),
|
||||
contents: bytemuck::bytes_of(data).to_vec(),
|
||||
usage,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::util::BufferInitDescriptor<'a> {
|
||||
wgpu::util::BufferInitDescriptor {
|
||||
label,
|
||||
contents: &self.contents,
|
||||
usage: self.usage,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,14 +6,12 @@ use lyra_ecs::{Entity, Tick, World, query::{Entities, TickOf}};
|
|||
pub use point::*;
|
||||
pub use spotlight::*;
|
||||
|
||||
use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem};
|
||||
use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem, rc::Rc};
|
||||
|
||||
use crate::math::Transform;
|
||||
|
||||
use self::directional::DirectionalLight;
|
||||
|
||||
use super::render_buffer::BindGroupPair;
|
||||
|
||||
const MAX_LIGHT_COUNT: usize = 16;
|
||||
|
||||
/// A struct that stores a list of lights in a wgpu::Buffer.
|
||||
|
@ -100,8 +98,10 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
|
|||
}
|
||||
|
||||
pub(crate) struct LightUniformBuffers {
|
||||
pub buffer: wgpu::Buffer,
|
||||
pub bind_group_pair: BindGroupPair,
|
||||
pub buffer: Rc<wgpu::Buffer>,
|
||||
//pub bind_group_pair: BindGroupPair,
|
||||
pub bind_group: Rc<wgpu::BindGroup>,
|
||||
pub bind_group_layout: Rc<wgpu::BindGroupLayout>,
|
||||
pub light_indexes: HashMap<Entity, u32>,
|
||||
dead_indices: VecDeque<u32>,
|
||||
pub current_light_idx: u32,
|
||||
|
@ -158,8 +158,9 @@ impl LightUniformBuffers {
|
|||
});
|
||||
|
||||
Self {
|
||||
buffer,
|
||||
bind_group_pair: BindGroupPair::new(bindgroup, bindgroup_layout),
|
||||
buffer: Rc::new(buffer),
|
||||
bind_group: Rc::new(bindgroup),
|
||||
bind_group_layout: Rc::new(bindgroup_layout),
|
||||
light_indexes: Default::default(),
|
||||
current_light_idx: 0,
|
||||
dead_indices: VecDeque::new(),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
pub mod renderer;
|
||||
pub mod render_pipeline;
|
||||
pub mod resource;
|
||||
pub mod vertex;
|
||||
pub mod desc_buf_lay;
|
||||
pub mod render_buffer;
|
||||
|
@ -12,5 +12,6 @@ pub mod camera;
|
|||
pub mod window;
|
||||
pub mod transform_buffer_storage;
|
||||
pub mod light;
|
||||
pub mod light_cull_compute;
|
||||
//pub mod light_cull_compute;
|
||||
pub mod avec;
|
||||
pub mod graph;
|
|
@ -134,6 +134,15 @@ impl BufferWrapper {
|
|||
"BufferWrapper is missing bindgroup pair! Cannot set bind group on RenderPass!",
|
||||
).bindgroup
|
||||
}
|
||||
|
||||
/// Take the bind group layout, the bind group, and the buffer out of the wrapper.
|
||||
pub fn parts(self) -> (Option<Rc<wgpu::BindGroupLayout>>, Option<wgpu::BindGroup>, wgpu::Buffer) {
|
||||
if let Some(pair) = self.bindgroup_pair {
|
||||
(Some(pair.layout), Some(pair.bindgroup), self.inner_buf)
|
||||
} else {
|
||||
(None, None, self.inner_buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct used for building a BufferWrapper
|
||||
|
@ -221,7 +230,7 @@ impl BufferWrapperBuilder {
|
|||
/// * `contents` - The contents to initialize the buffer with.
|
||||
///
|
||||
/// If a field is missing, a panic will occur.
|
||||
pub fn finish(self, device: &wgpu::Device) -> BufferWrapper {
|
||||
pub fn finish_parts(self, device: &wgpu::Device) -> (wgpu::BindGroupLayout, wgpu::BindGroup, wgpu::Buffer, usize) {
|
||||
let buf_usage = self.buffer_usage.expect("Buffer usage was not set");
|
||||
let buffer = if let Some(contents) = self.contents.as_ref() {
|
||||
device.create_buffer_init(
|
||||
|
@ -293,10 +302,25 @@ impl BufferWrapperBuilder {
|
|||
}
|
||||
};
|
||||
|
||||
BufferWrapper {
|
||||
/* BufferWrapper {
|
||||
bindgroup_pair: Some(bg_pair),
|
||||
inner_buf: buffer,
|
||||
len: Some(self.count.unwrap_or_default() as usize),
|
||||
} */
|
||||
|
||||
(Rc::try_unwrap(bg_pair.layout).unwrap(), bg_pair.bindgroup, buffer, self.count.unwrap_or_default() as usize)
|
||||
}
|
||||
|
||||
pub fn finish(self, device: &wgpu::Device) -> BufferWrapper {
|
||||
let (bgl, bg, buff, len) = self.finish_parts(device);
|
||||
|
||||
BufferWrapper {
|
||||
bindgroup_pair: Some(BindGroupPair {
|
||||
layout: Rc::new(bgl),
|
||||
bindgroup: bg
|
||||
}),
|
||||
inner_buf: buff,
|
||||
len: Some(len),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
use wgpu::{PipelineLayout, RenderPipeline, VertexBufferLayout, BindGroupLayout};
|
||||
|
||||
use super::texture::RenderTexture;
|
||||
|
||||
pub struct FullRenderPipeline {
|
||||
layout: PipelineLayout,
|
||||
wgpu_pipeline: RenderPipeline,
|
||||
}
|
||||
|
||||
impl FullRenderPipeline {
|
||||
pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, buffer_layouts: Vec<VertexBufferLayout>, bind_group_layouts: Vec<&BindGroupLayout>) -> FullRenderPipeline {
|
||||
// Extract the layouts from all the jobs
|
||||
/* let mut buffer_layouts = vec![];
|
||||
let mut bind_group_layouts = vec![];
|
||||
for job in jobs.iter() {
|
||||
// Push layout for the vertex buffer, index buffer doesn't need one
|
||||
buffer_layouts.push(Vertex::desc());
|
||||
|
||||
if let Some(layout) = job.mesh().texture_layout.as_ref() {
|
||||
bind_group_layouts.push(layout);
|
||||
}
|
||||
} */
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Render Pipeline Layout"),
|
||||
bind_group_layouts: &bind_group_layouts,
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Render Pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &buffer_layouts,
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: 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: Some(wgpu::DepthStencilState {
|
||||
format: RenderTexture::DEPTH_FORMAT,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(), // TODO: stencil buffer
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
layout: render_pipeline_layout,
|
||||
wgpu_pipeline: render_pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_layout(&self) -> &PipelineLayout {
|
||||
&self.layout
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_wgpu_pipeline(&self) -> &RenderPipeline {
|
||||
&self.wgpu_pipeline
|
||||
}
|
||||
}
|
|
@ -1,51 +1,41 @@
|
|||
use std::collections::{VecDeque, HashSet};
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use glam::Vec3;
|
||||
use itertools::izip;
|
||||
use lyra_ecs::query::filter::{Has, Not, Or};
|
||||
use lyra_ecs::relation::{ChildOf, RelationOriginComponent};
|
||||
use lyra_ecs::{Component, Entity};
|
||||
use lyra_ecs::query::{Entities, Res, TickOf};
|
||||
use lyra_ecs::World;
|
||||
use lyra_scene::{SceneGraph, WorldTransform};
|
||||
use tracing::{debug, instrument, warn};
|
||||
use uuid::Uuid;
|
||||
use wgpu::{BindGroupLayout, Limits};
|
||||
use wgpu::util::DeviceExt;
|
||||
use winit::window::Window;
|
||||
|
||||
use crate::math::Transform;
|
||||
use crate::render::material::MaterialUniform;
|
||||
use crate::render::render_buffer::BufferWrapperBuilder;
|
||||
use crate::scene::CameraComponent;
|
||||
use crate::DeltaTime;
|
||||
use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshesPassLabel, PresentPass, PresentPassLabel};
|
||||
|
||||
use super::camera::{RenderCamera, CameraUniform};
|
||||
use super::desc_buf_lay::DescVertexBufferLayout;
|
||||
use super::light::LightUniformBuffers;
|
||||
use super::light_cull_compute::LightCullCompute;
|
||||
use super::material::Material;
|
||||
use super::render_buffer::BufferWrapper;
|
||||
use super::texture::RenderTexture;
|
||||
use super::transform_buffer_storage::{TransformBuffers, TransformGroup};
|
||||
use super::vertex::Vertex;
|
||||
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
|
||||
use super::graph::RenderGraph;
|
||||
use super::{resource::RenderPipeline, render_job::RenderJob};
|
||||
|
||||
use lyra_resource::{gltf::Mesh, ResHandle};
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ScreenSize(glam::UVec2);
|
||||
|
||||
type MeshHandle = ResHandle<Mesh>;
|
||||
type SceneHandle = ResHandle<SceneGraph>;
|
||||
impl Deref for ScreenSize {
|
||||
type Target = glam::UVec2;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ScreenSize {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Renderer {
|
||||
fn prepare(&mut self, main_world: &mut World);
|
||||
fn render(&mut self) -> Result<(), wgpu::SurfaceError>;
|
||||
fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>);
|
||||
fn on_resize(&mut self, world: &mut World, new_size: winit::dpi::PhysicalSize<u32>);
|
||||
|
||||
fn surface_size(&self) -> winit::dpi::PhysicalSize<u32>;
|
||||
fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<FullRenderPipeline>);
|
||||
fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<RenderPipeline>);
|
||||
}
|
||||
|
||||
pub trait RenderPass {
|
||||
|
@ -54,65 +44,25 @@ pub trait RenderPass {
|
|||
fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>);
|
||||
}
|
||||
|
||||
struct MeshBufferStorage {
|
||||
buffer_vertex: BufferStorage,
|
||||
buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>,
|
||||
|
||||
//#[allow(dead_code)]
|
||||
//render_texture: Option<RenderTexture>,
|
||||
material: Option<Rc<Material>>,
|
||||
|
||||
// The index of the transform for this entity.
|
||||
// The tuple is structured like this: (transform index, index of transform inside the buffer)
|
||||
//transform_index: TransformBufferIndices,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Component)]
|
||||
pub struct InterpTransform {
|
||||
last_transform: Transform,
|
||||
alpha: f32,
|
||||
}
|
||||
|
||||
pub struct BasicRenderer {
|
||||
pub surface: wgpu::Surface,
|
||||
pub device: Rc<wgpu::Device>, // device does not need to be mutable, no need for refcell
|
||||
pub queue: Rc<wgpu::Queue>,
|
||||
pub config: wgpu::SurfaceConfiguration,
|
||||
pub size: winit::dpi::PhysicalSize<u32>,
|
||||
pub window: Arc<Window>,
|
||||
|
||||
pub clear_color: wgpu::Color,
|
||||
|
||||
pub render_pipelines: rustc_hash::FxHashMap<u64, Arc<FullRenderPipeline>>,
|
||||
pub render_pipelines: rustc_hash::FxHashMap<u64, Arc<RenderPipeline>>,
|
||||
pub render_jobs: VecDeque<RenderJob>,
|
||||
|
||||
mesh_buffers: rustc_hash::FxHashMap<uuid::Uuid, MeshBufferStorage>, // TODO: clean up left over buffers from deleted entities/components
|
||||
material_buffers: rustc_hash::FxHashMap<uuid::Uuid, Rc<Material>>,
|
||||
entity_meshes: rustc_hash::FxHashMap<Entity, uuid::Uuid>,
|
||||
|
||||
transform_buffers: TransformBuffers,
|
||||
|
||||
render_limits: Limits,
|
||||
|
||||
inuse_camera: RenderCamera,
|
||||
camera_buffer: BufferWrapper,
|
||||
//camera_bind_group: wgpu::BindGroup,
|
||||
|
||||
bgl_texture: Rc<BindGroupLayout>,
|
||||
default_texture: RenderTexture,
|
||||
depth_buffer_texture: RenderTexture,
|
||||
|
||||
material_buffer: BufferWrapper,
|
||||
|
||||
light_buffers: LightUniformBuffers,
|
||||
|
||||
light_cull_compute: LightCullCompute,
|
||||
graph: RenderGraph,
|
||||
}
|
||||
|
||||
impl BasicRenderer {
|
||||
#[instrument(skip(window))]
|
||||
pub async fn create_with_window(window: Arc<Window>) -> BasicRenderer {
|
||||
#[instrument(skip(world, window))]
|
||||
pub async fn create_with_window(world: &mut World, window: Arc<Window>) -> BasicRenderer {
|
||||
let size = window.inner_size();
|
||||
world.add_resource(ScreenSize(glam::UVec2::new(size.width, size.height)));
|
||||
|
||||
// Get a GPU handle
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
|
@ -150,11 +100,8 @@ impl BasicRenderer {
|
|||
None,
|
||||
).await.unwrap();
|
||||
|
||||
let render_limits = device.limits();
|
||||
let surface_caps = surface.get_capabilities(&adapter);
|
||||
|
||||
let present_mode = surface_caps.present_modes[0];
|
||||
|
||||
debug!("present mode: {:?}", present_mode);
|
||||
|
||||
let surface_format = surface_caps.formats.iter()
|
||||
|
@ -166,53 +113,48 @@ impl BasicRenderer {
|
|||
format: surface_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Immediate,
|
||||
present_mode: wgpu::PresentMode::default(), //wgpu::PresentMode::Mailbox, // "Fast Vsync"
|
||||
alpha_mode: surface_caps.alpha_modes[0],
|
||||
view_formats: vec![],
|
||||
};
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let bgl_texture = Rc::new(RenderTexture::create_layout(&device));
|
||||
|
||||
let shader_src = include_str!("shaders/base.wgsl");
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(shader_src)),
|
||||
});
|
||||
|
||||
let transform_buffers = TransformBuffers::new(&device);
|
||||
let camera_buffer = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
.contents(&[CameraUniform::default()])
|
||||
.label_prefix("Camera")
|
||||
.visibility(wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE)
|
||||
.buffer_dynamic_offset(false)
|
||||
.finish(&device);
|
||||
|
||||
let mut depth_texture = RenderTexture::create_depth_texture(&device, &config, "Tex_Depth");
|
||||
|
||||
// load the default texture
|
||||
let bytes = include_bytes!("default_texture.png");
|
||||
let default_texture = RenderTexture::from_bytes(&device, &queue, bgl_texture.clone(), bytes, "default_texture").unwrap();
|
||||
|
||||
let light_uniform_buffers = LightUniformBuffers::new(&device);
|
||||
|
||||
let mat_buffer = BufferWrapperBuilder::new()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
.visibility(wgpu::ShaderStages::FRAGMENT)
|
||||
.contents(&[MaterialUniform::default()])
|
||||
.finish(&device);
|
||||
|
||||
let device = Rc::new(device);
|
||||
let queue = Rc::new(queue);
|
||||
let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture);
|
||||
|
||||
let mut s = Self {
|
||||
let mut g = RenderGraph::new(device.clone(), queue.clone());
|
||||
|
||||
debug!("Adding base pass");
|
||||
g.add_pass(BasePassLabel, BasePass::new(surface, config));
|
||||
debug!("Adding light base pass");
|
||||
g.add_pass(LightBasePassLabel, LightBasePass::new());
|
||||
debug!("Adding light cull compute pass");
|
||||
g.add_pass(LightCullComputePassLabel, LightCullComputePass::new(size));
|
||||
//debug!("Adding triangle pass");
|
||||
//g.add_pass(TrianglePass::new());
|
||||
|
||||
debug!("Adding mesh pass");
|
||||
g.add_pass(MeshesPassLabel, MeshPass::new());
|
||||
|
||||
debug!("Adding present pass");
|
||||
let p = PresentPass::new(BasePassSlots::MainRenderTarget);
|
||||
g.add_pass(p.label.clone(), p);
|
||||
|
||||
g.add_edge(BasePassLabel, LightBasePassLabel);
|
||||
g.add_edge(LightBasePassLabel, LightCullComputePassLabel);
|
||||
g.add_edge(BasePassLabel, MeshesPassLabel);
|
||||
|
||||
// make sure that present runs last
|
||||
g.add_edge(BasePassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget));
|
||||
g.add_edge(LightCullComputePassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget));
|
||||
g.add_edge(MeshesPassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget));
|
||||
|
||||
g.setup(&device);
|
||||
|
||||
Self {
|
||||
window,
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
config,
|
||||
size,
|
||||
clear_color: wgpu::Color {
|
||||
r: 0.1,
|
||||
|
@ -222,446 +164,40 @@ impl BasicRenderer {
|
|||
},
|
||||
render_pipelines: Default::default(),
|
||||
render_jobs: Default::default(),
|
||||
mesh_buffers: Default::default(),
|
||||
material_buffers: Default::default(),
|
||||
entity_meshes: Default::default(),
|
||||
|
||||
render_limits,
|
||||
transform_buffers,
|
||||
|
||||
inuse_camera: RenderCamera::new(size),
|
||||
camera_buffer,
|
||||
|
||||
bgl_texture,
|
||||
default_texture,
|
||||
depth_buffer_texture: depth_texture,
|
||||
|
||||
light_buffers: light_uniform_buffers,
|
||||
material_buffer: mat_buffer,
|
||||
light_cull_compute,
|
||||
};
|
||||
|
||||
// create the default pipelines
|
||||
let mut pipelines = rustc_hash::FxHashMap::default();
|
||||
pipelines.insert(0, Arc::new(FullRenderPipeline::new(&s.device, &s.config, &shader,
|
||||
vec![super::vertex::Vertex::desc(),],
|
||||
vec![&s.bgl_texture, &s.transform_buffers.bindgroup_layout,
|
||||
s.camera_buffer.bindgroup_layout().unwrap(),
|
||||
&s.light_buffers.bind_group_pair.layout, &s.material_buffer.bindgroup_pair.as_ref().unwrap().layout,
|
||||
&s.bgl_texture,
|
||||
&s.light_cull_compute.light_indices_grid.bg_pair.layout,
|
||||
])));
|
||||
s.render_pipelines = pipelines;
|
||||
|
||||
s
|
||||
graph: g,
|
||||
}
|
||||
|
||||
/// Checks if the mesh buffers in the GPU need to be updated.
|
||||
#[instrument(skip(self, _entity, meshh))]
|
||||
fn check_mesh_buffers(&mut self, _entity: Entity, meshh: &ResHandle<Mesh>) {
|
||||
let mesh_uuid = meshh.uuid();
|
||||
|
||||
if let (Some(mesh), Some(buffers)) = (meshh.data_ref(), self.mesh_buffers.get_mut(&mesh_uuid)) {
|
||||
// check if the buffer sizes dont match. If they dont, completely remake the buffers
|
||||
let vertices = mesh.position().unwrap();
|
||||
if buffers.buffer_vertex.count() != vertices.len() {
|
||||
debug!("Recreating buffers for mesh {}", mesh_uuid.to_string());
|
||||
let (vert, idx) = self.create_vertex_index_buffers(&mesh);
|
||||
|
||||
// have to re-get buffers because of borrow checker
|
||||
let buffers = self.mesh_buffers.get_mut(&mesh_uuid).unwrap();
|
||||
buffers.buffer_indices = idx;
|
||||
buffers.buffer_vertex = vert;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// update vertices
|
||||
let vertex_buffer = buffers.buffer_vertex.buffer();
|
||||
let vertices = vertices.as_slice();
|
||||
// align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT)
|
||||
let (_, vertices, _) = bytemuck::pod_align_to::<Vec3, u32>(vertices);
|
||||
self.queue.write_buffer(vertex_buffer, 0, bytemuck::cast_slice(vertices));
|
||||
|
||||
// update the indices if they're given
|
||||
if let Some(index_buffer) = buffers.buffer_indices.as_ref() {
|
||||
let aligned_indices = match mesh.indices.as_ref().unwrap() {
|
||||
// U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size.
|
||||
lyra_resource::gltf::MeshIndices::U16(v) => bytemuck::pod_align_to::<u16, u32>(v).1,
|
||||
lyra_resource::gltf::MeshIndices::U32(v) => bytemuck::pod_align_to::<u32, u32>(v).1,
|
||||
};
|
||||
|
||||
let index_buffer = index_buffer.1.buffer();
|
||||
self.queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(aligned_indices));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, mesh))]
|
||||
fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) {
|
||||
let positions = mesh.position().unwrap();
|
||||
let tex_coords: Vec<glam::Vec2> = mesh.tex_coords().cloned()
|
||||
.unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]);
|
||||
let normals = mesh.normals().unwrap();
|
||||
|
||||
assert!(positions.len() == tex_coords.len() && positions.len() == normals.len());
|
||||
|
||||
let mut vertex_inputs = vec![];
|
||||
for (v, t, n) in izip!(positions.iter(), tex_coords.iter(), normals.iter()) {
|
||||
vertex_inputs.push(Vertex::new(*v, *t, *n));
|
||||
}
|
||||
|
||||
let vertex_buffer = self.device.create_buffer_init(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(vertex_inputs.as_slice()),//vertex_combined.as_slice(),
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST,
|
||||
}
|
||||
);
|
||||
let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len());
|
||||
|
||||
let indices = match mesh.indices.as_ref() {
|
||||
Some(indices) => {
|
||||
let (idx_type, len, contents) = match indices {
|
||||
lyra_resource::gltf::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)),
|
||||
lyra_resource::gltf::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v)),
|
||||
};
|
||||
|
||||
let index_buffer = self.device.create_buffer_init(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Index Buffer"),
|
||||
contents,
|
||||
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages:: COPY_DST,
|
||||
}
|
||||
);
|
||||
|
||||
let buffer_indices = BufferStorage::new(index_buffer, 0, len);
|
||||
|
||||
Some((idx_type, buffer_indices))
|
||||
},
|
||||
None => {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
( vertex_buffer, indices )
|
||||
}
|
||||
|
||||
#[instrument(skip(self, mesh))]
|
||||
fn create_mesh_buffers(&mut self, mesh: &Mesh) -> MeshBufferStorage {
|
||||
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh);
|
||||
|
||||
let material = mesh.material.as_ref()
|
||||
.expect("Material resource not loaded yet");
|
||||
let material_ref = material.data_ref()
|
||||
.unwrap();
|
||||
|
||||
let material = self.material_buffers.entry(material.uuid())
|
||||
.or_insert_with(|| {
|
||||
debug!(uuid=material.uuid().to_string(), "Sending material to gpu");
|
||||
Rc::new(Material::from_resource(&self.device, &self.queue, self.bgl_texture.clone(), &material_ref))
|
||||
});
|
||||
|
||||
// TODO: support material uniforms from multiple uniforms
|
||||
let uni = MaterialUniform::from(&**material);
|
||||
self.queue.write_buffer(&self.material_buffer.inner_buf, 0, bytemuck::bytes_of(&uni));
|
||||
|
||||
MeshBufferStorage {
|
||||
buffer_vertex: vertex_buffer,
|
||||
buffer_indices,
|
||||
material: Some(material.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed.
|
||||
#[instrument(skip(self, transform, mesh, entity))]
|
||||
fn process_mesh(&mut self, entity: Entity, transform: Transform, mesh: &Mesh, mesh_uuid: Uuid) -> bool {
|
||||
let _ = transform;
|
||||
/* if self.transform_buffers.should_expand() {
|
||||
self.transform_buffers.expand_buffers(&self.device);
|
||||
}
|
||||
|
||||
self.transform_buffers.update_or_insert(&self.queue, &self.render_limits,
|
||||
entity, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) )); */
|
||||
|
||||
#[allow(clippy::map_entry)]
|
||||
if !self.mesh_buffers.contains_key(&mesh_uuid) {
|
||||
// create the mesh's buffers
|
||||
let buffers = self.create_mesh_buffers(mesh);
|
||||
self.mesh_buffers.insert(mesh_uuid, buffers);
|
||||
self.entity_meshes.insert(entity, mesh_uuid);
|
||||
|
||||
true
|
||||
} else { false }
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderer for BasicRenderer {
|
||||
#[instrument(skip(self, main_world))]
|
||||
fn prepare(&mut self, main_world: &mut World) {
|
||||
let last_epoch = main_world.current_tick();
|
||||
let mut alive_entities = HashSet::new();
|
||||
|
||||
let view = main_world.view_iter::<(
|
||||
Entities,
|
||||
&Transform,
|
||||
TickOf<Transform>,
|
||||
Or<
|
||||
(&MeshHandle, TickOf<MeshHandle>),
|
||||
(&SceneHandle, TickOf<SceneHandle>)
|
||||
>,
|
||||
Option<&mut InterpTransform>,
|
||||
Res<DeltaTime>,
|
||||
)>();
|
||||
|
||||
// used to store InterpTransform components to add to entities later
|
||||
let mut component_queue: Vec<(Entity, InterpTransform)> = vec![];
|
||||
|
||||
for (
|
||||
entity,
|
||||
transform,
|
||||
_transform_epoch,
|
||||
(
|
||||
mesh_pair,
|
||||
scene_pair
|
||||
),
|
||||
interp_tran,
|
||||
delta_time,
|
||||
) in view
|
||||
{
|
||||
alive_entities.insert(entity);
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
// shows that the scene has changed, verify that the mesh buffers
|
||||
// dont need to be resent to the gpu.
|
||||
if !self.process_mesh(entity, interp_transform, &*mesh, mesh_han.uuid())
|
||||
&& mesh_epoch == last_epoch {
|
||||
self.check_mesh_buffers(entity, &mesh_han);
|
||||
}
|
||||
|
||||
if self.transform_buffers.needs_expand() {
|
||||
self.transform_buffers.expand_buffers(&self.device);
|
||||
}
|
||||
|
||||
let group = TransformGroup::EntityRes(entity, mesh_han.uuid());
|
||||
let transform_id = self.transform_buffers.update_or_push(&self.device, &self.queue, &self.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);
|
||||
self.render_jobs.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)>() {
|
||||
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.
|
||||
if !self.process_mesh(entity, mesh_interpo, &*mesh, mesh_han.uuid())
|
||||
&& scene_epoch == last_epoch {
|
||||
self.check_mesh_buffers(entity, &mesh_han);
|
||||
}
|
||||
|
||||
if self.transform_buffers.needs_expand() {
|
||||
self.transform_buffers.expand_buffers(&self.device);
|
||||
}
|
||||
|
||||
let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid());
|
||||
let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into());
|
||||
let transform_id = self.transform_buffers.update_or_push(&self.device, &self.queue, &self.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);
|
||||
self.render_jobs.push_back(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (en, interp) in component_queue {
|
||||
main_world.insert(en, interp);
|
||||
}
|
||||
|
||||
// collect dead entities
|
||||
self.transform_buffers.send_to_gpu(&self.queue);
|
||||
|
||||
// when buffer storage length does not match the amount of iterated entities,
|
||||
// remove all dead entities, and their buffers, if they weren't iterated over
|
||||
if self.mesh_buffers.len() != alive_entities.len() {
|
||||
let removed_entities: Vec<uuid::Uuid> = self.entity_meshes
|
||||
.extract_if(|e, _| !alive_entities.contains(e))
|
||||
.map(|(_, v)| v)
|
||||
.collect();
|
||||
self.mesh_buffers.retain(|u, _| !removed_entities.contains(u));
|
||||
}
|
||||
|
||||
// update camera uniform
|
||||
if let Some(camera) = main_world.view_iter::<&mut CameraComponent>().next() {
|
||||
let uniform = self.inuse_camera.calc_view_projection(&camera);
|
||||
self.camera_buffer.write_buffer(&self.queue, 0, &[uniform]);
|
||||
} else {
|
||||
warn!("Missing camera!");
|
||||
}
|
||||
|
||||
self.light_buffers.update_lights(&self.queue, last_epoch, main_world);
|
||||
self.graph.prepare(main_world);
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||
let output = self.surface.get_current_texture()?;
|
||||
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
self.light_cull_compute.compute(&self.camera_buffer, &self.light_buffers, &self.depth_buffer_texture);
|
||||
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Basic Renderer's Encoder")
|
||||
});
|
||||
|
||||
// Create a new variable scope for the render pass
|
||||
{
|
||||
// There's only one render pass currently
|
||||
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::Clear(self.clear_color),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
// enable depth buffer
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_buffer_texture.view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
});
|
||||
|
||||
// Pop off jobs from the queue as they're being processed
|
||||
while let Some(job) = self.render_jobs.pop_front() {
|
||||
if let Some(pipeline) = self.render_pipelines.get(&job.shader_id) {
|
||||
// specify to use this pipeline
|
||||
render_pass.set_pipeline(pipeline.get_wgpu_pipeline());
|
||||
|
||||
// get the mesh (containing vertices) and the buffers from storage
|
||||
let buffers = self.mesh_buffers.get(&job.mesh_uuid);
|
||||
if buffers.is_none() {
|
||||
warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid);
|
||||
continue;
|
||||
}
|
||||
let buffers = buffers.unwrap();
|
||||
/* let buffers = self.mesh_buffers.get(&job.mesh_uuid)
|
||||
.expect("missing render job mesh"); */
|
||||
|
||||
// Bind the optional texture
|
||||
if let Some(tex) = buffers.material.as_ref()
|
||||
.and_then(|m| m.diffuse_texture.as_ref()) {
|
||||
render_pass.set_bind_group(0, tex.bind_group(), &[]);
|
||||
} else {
|
||||
render_pass.set_bind_group(0, self.default_texture.bind_group(), &[]);
|
||||
}
|
||||
|
||||
if let Some(tex) = buffers.material.as_ref()
|
||||
.and_then(|m| m.specular.as_ref())
|
||||
.and_then(|s| s.texture.as_ref().or(s.color_texture.as_ref())) {
|
||||
render_pass.set_bind_group(5, tex.bind_group(), &[]);
|
||||
} else {
|
||||
render_pass.set_bind_group(5, self.default_texture.bind_group(), &[]);
|
||||
}
|
||||
|
||||
// Get the bindgroup for job's transform and bind to it using an offset.
|
||||
let bindgroup = self.transform_buffers.bind_group(job.transform_id);
|
||||
let offset = self.transform_buffers.buffer_offset(job.transform_id);
|
||||
render_pass.set_bind_group(1, bindgroup, &[ offset, ]);
|
||||
|
||||
render_pass.set_bind_group(2, &self.camera_buffer.bindgroup(), &[]);
|
||||
render_pass.set_bind_group(3, &self.light_buffers.bind_group_pair.bindgroup, &[]);
|
||||
render_pass.set_bind_group(4, &self.material_buffer.bindgroup_pair.as_ref().unwrap().bindgroup, &[]);
|
||||
|
||||
render_pass.set_bind_group(6, &self.light_cull_compute.light_indices_grid.bg_pair.bindgroup, &[]);
|
||||
|
||||
// if this mesh uses indices, use them to draw the mesh
|
||||
if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() {
|
||||
let indices_len = indices.count() as u32;
|
||||
|
||||
render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..));
|
||||
render_pass.set_index_buffer(indices.buffer().slice(..), *idx_type);
|
||||
render_pass.draw_indexed(0..indices_len, 0, 0..1);
|
||||
} else {
|
||||
let vertex_count = buffers.buffer_vertex.count();
|
||||
|
||||
render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..));
|
||||
render_pass.draw(0..vertex_count as u32, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
self.graph.render();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
#[instrument(skip(world, self))]
|
||||
fn on_resize(&mut self, world: &mut World, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
self.size = new_size;
|
||||
self.config.width = new_size.width;
|
||||
self.config.height = new_size.height;
|
||||
|
||||
// tell other things of updated resize
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
// update surface config and the surface
|
||||
let mut rt = self.graph.slot_value_mut(BasePassSlots::MainRenderTarget)
|
||||
.unwrap().as_render_target_mut().unwrap();
|
||||
rt.surface_config.width = new_size.width;
|
||||
rt.surface_config.height = new_size.height;
|
||||
rt.surface.configure(&self.device, &rt.surface_config);
|
||||
|
||||
let create_bindgroup = self.depth_buffer_texture.bindgroup_pair.is_some();
|
||||
self.depth_buffer_texture = RenderTexture::create_depth_texture(&self.device, &self.config, "Depth Buffer Texture");
|
||||
if create_bindgroup {
|
||||
self.depth_buffer_texture.create_bind_group(&self.device);
|
||||
}
|
||||
|
||||
self.inuse_camera.update_aspect_ratio(self.size);
|
||||
self.light_cull_compute.update_screen_size(new_size);
|
||||
// update screen size resource in ecs
|
||||
let mut world_ss = world.get_resource_mut::<ScreenSize>();
|
||||
world_ss.0 = glam::UVec2::new(new_size.width, new_size.height);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -669,7 +205,7 @@ impl Renderer for BasicRenderer {
|
|||
self.size
|
||||
}
|
||||
|
||||
fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<FullRenderPipeline>) {
|
||||
fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<RenderPipeline>) {
|
||||
self.render_pipelines.insert(shader_id, pipeline);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
use std::{ops::Deref, rc::Rc};
|
||||
|
||||
use wgpu::PipelineLayout;
|
||||
|
||||
use super::Shader;
|
||||
|
||||
//#[derive(Debug, Clone)]
|
||||
pub struct ComputePipelineDescriptor {
|
||||
pub label: Option<String>,
|
||||
pub layouts: Vec<Rc<wgpu::BindGroupLayout>>,
|
||||
pub push_constant_ranges: Vec<wgpu::PushConstantRange>,
|
||||
// TODO: make this a ResHandle<Shader>
|
||||
/// The compiled shader module for the stage.
|
||||
pub shader: Rc<Shader>,
|
||||
/// The entry point in the compiled shader.
|
||||
/// There must be a function in the shader with the same name.
|
||||
pub shader_entry_point: String,
|
||||
}
|
||||
|
||||
impl ComputePipelineDescriptor {
|
||||
/// Create the [`wgpu::PipelineLayout`] for this pipeline
|
||||
pub(crate) fn create_layout(&self, device: &wgpu::Device) -> wgpu::PipelineLayout {
|
||||
let bgs = self
|
||||
.layouts
|
||||
.iter()
|
||||
.map(|bg| bg.as_ref())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)),
|
||||
bind_group_layouts: &bgs,
|
||||
push_constant_ranges: &self.push_constant_ranges,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ComputePipeline {
|
||||
layout: Option<PipelineLayout>,
|
||||
wgpu_pipeline: wgpu::ComputePipeline,
|
||||
}
|
||||
|
||||
impl Deref for ComputePipeline {
|
||||
type Target = wgpu::ComputePipeline;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.wgpu_pipeline
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu::ComputePipeline> for ComputePipeline {
|
||||
fn from(value: wgpu::ComputePipeline) -> Self {
|
||||
Self {
|
||||
layout: None,
|
||||
wgpu_pipeline: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComputePipeline {
|
||||
/// Creates a new compute pipeline on the `device`.
|
||||
///
|
||||
/// Parameters:
|
||||
/// * `device` - The device to create the pipeline on.
|
||||
/// * `desc` - The discriptor of the compute pipeline
|
||||
pub fn create(device: &wgpu::Device, desc: &ComputePipelineDescriptor) -> ComputePipeline {
|
||||
// create the layout only if bind groups layouts were specified
|
||||
let layout = if !desc.layouts.is_empty() {
|
||||
Some(desc.create_layout(device))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// an Rc was used here so that this shader could be reused by the fragment stage if
|
||||
// they share the same shader. I tried to do it without an Rc but couldn't get past
|
||||
// the borrow checker
|
||||
let compiled_shader = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: desc.shader.label.as_ref().map(|s| s.as_str()),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
&desc.shader.source,
|
||||
)),
|
||||
}));
|
||||
|
||||
let desc = wgpu::ComputePipelineDescriptor {
|
||||
label: desc.label.as_deref(),
|
||||
layout: layout.as_ref(),
|
||||
module: &compiled_shader,
|
||||
entry_point: &desc.shader_entry_point,
|
||||
};
|
||||
|
||||
let pipeline = device.create_compute_pipeline(&desc);
|
||||
|
||||
Self {
|
||||
layout,
|
||||
wgpu_pipeline: pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn layout(&self) -> Option<&PipelineLayout> {
|
||||
self.layout.as_ref()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn wgpu_pipeline(&self) -> &wgpu::ComputePipeline {
|
||||
&self.wgpu_pipeline
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
mod shader;
|
||||
pub use shader::*;
|
||||
|
||||
mod pipeline;
|
||||
pub use pipeline::*;
|
||||
|
||||
mod compute_pipeline;
|
||||
pub use compute_pipeline::*;
|
||||
|
||||
mod render_pipeline;
|
||||
pub use render_pipeline::*;
|
|
@ -0,0 +1,86 @@
|
|||
use super::{compute_pipeline::ComputePipeline, render_pipeline::RenderPipeline, ComputePipelineDescriptor, RenderPipelineDescriptor};
|
||||
|
||||
pub enum PipelineDescriptor {
|
||||
Render(RenderPipelineDescriptor),
|
||||
Compute(ComputePipelineDescriptor),
|
||||
}
|
||||
|
||||
impl PipelineDescriptor {
|
||||
pub fn as_render_pipeline_descriptor(&self) -> Option<&RenderPipelineDescriptor> {
|
||||
match self {
|
||||
Self::Render(r) => Some(r),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_compute_pipeline_descriptor(&self) -> Option<&ComputePipelineDescriptor> {
|
||||
match self {
|
||||
Self::Compute(c) => Some(c),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Pipeline {
|
||||
Render(RenderPipeline),
|
||||
Compute(ComputePipeline),
|
||||
}
|
||||
|
||||
impl Into<RenderPipeline> for Pipeline {
|
||||
fn into(self) -> RenderPipeline {
|
||||
match self {
|
||||
Self::Render(r) => r,
|
||||
_ => panic!("Pipeline is not a RenderPipeline"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ComputePipeline> for Pipeline {
|
||||
fn into(self) -> ComputePipeline {
|
||||
match self {
|
||||
Self::Compute(c) => c,
|
||||
_ => panic!("Pipeline is not a RenderPipeline"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn bind_group_layout(&self, index: u32) -> wgpu::BindGroupLayout {
|
||||
match self {
|
||||
Pipeline::Render(r) => r.get_bind_group_layout(index),
|
||||
Pipeline::Compute(c) => c.get_bind_group_layout(index),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets self as a render pipeline, panics if self is not a render pipeline.
|
||||
pub fn as_render(&self) -> &RenderPipeline {
|
||||
match self {
|
||||
Self::Render(r) => r,
|
||||
_ => panic!("Pipeline is not a RenderPipeline")
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets self as a render pipeline, returns `None` if self is not a render pipeline.
|
||||
pub fn try_as_render(&self) -> Option<&RenderPipeline> {
|
||||
match self {
|
||||
Self::Render(r) => Some(r),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets self as a compute pipeline, panics if self is not a compute pipeline.
|
||||
pub fn as_compute(&self) -> &ComputePipeline {
|
||||
match self {
|
||||
Self::Compute(r) => r,
|
||||
_ => panic!("Pipeline is not a ComputePipeline")
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets self as a compute pipeline, returns `None` if self is not a compute pipeline.
|
||||
pub fn try_as_compute(&self) -> Option<&ComputePipeline> {
|
||||
match self {
|
||||
Self::Compute(c) => Some(c),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
use std::{num::NonZeroU32, ops::Deref, rc::Rc};
|
||||
|
||||
use wgpu::PipelineLayout;
|
||||
|
||||
use super::{FragmentState, VertexState};
|
||||
|
||||
//#[derive(Debug, Clone)]
|
||||
pub struct RenderPipelineDescriptor {
|
||||
pub label: Option<String>,
|
||||
pub layouts: Vec<Rc<wgpu::BindGroupLayout>>,
|
||||
pub push_constant_ranges: Vec<wgpu::PushConstantRange>,
|
||||
pub vertex: VertexState,
|
||||
pub fragment: Option<FragmentState>,
|
||||
pub primitive: wgpu::PrimitiveState,
|
||||
pub depth_stencil: Option<wgpu::DepthStencilState>,
|
||||
pub multisample: wgpu::MultisampleState,
|
||||
pub multiview: Option<NonZeroU32>,
|
||||
}
|
||||
|
||||
impl RenderPipelineDescriptor {
|
||||
/// Create the [`wgpu::PipelineLayout`] for this pipeline
|
||||
pub(crate) fn create_layout(&self, device: &wgpu::Device) -> wgpu::PipelineLayout {
|
||||
let bgs = self
|
||||
.layouts
|
||||
.iter()
|
||||
.map(|bg| bg.as_ref())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)),
|
||||
bind_group_layouts: &bgs,
|
||||
push_constant_ranges: &self.push_constant_ranges,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RenderPipeline {
|
||||
layout: Option<PipelineLayout>,
|
||||
wgpu_pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl Deref for RenderPipeline {
|
||||
type Target = wgpu::RenderPipeline;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.wgpu_pipeline
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu::RenderPipeline> for RenderPipeline {
|
||||
fn from(value: wgpu::RenderPipeline) -> Self {
|
||||
Self {
|
||||
layout: None,
|
||||
wgpu_pipeline: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderPipeline {
|
||||
/// Creates the default render pipeline
|
||||
///
|
||||
/// Parameters:
|
||||
/// * `device` - The device to create the pipeline on.
|
||||
/// * `config` - The surface config to use to create the pipeline.
|
||||
/// * `label` - The label of the pipeline.
|
||||
/// * `shader` - The compiled shader of the pipeline.
|
||||
/// * `vertex_entry_point` - The entry point name of the vertex shader
|
||||
pub fn create(device: &wgpu::Device, desc: &RenderPipelineDescriptor) -> RenderPipeline {
|
||||
// create the layout only if bind groups layouts were specified
|
||||
let layout = if !desc.layouts.is_empty() {
|
||||
Some(desc.create_layout(device))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let vrtx_buffs = desc
|
||||
.vertex
|
||||
.buffers
|
||||
.iter()
|
||||
.map(|vbl| wgpu::VertexBufferLayout {
|
||||
array_stride: vbl.array_stride,
|
||||
step_mode: vbl.step_mode,
|
||||
attributes: &vbl.attributes,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// an Rc was used here so that this shader could be reused by the fragment stage if
|
||||
// they share the same shader. I tried to do it without an Rc but couldn't get past
|
||||
// the borrow checker
|
||||
let vrtx_shad = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: desc.vertex.module.label.as_ref().map(|s| s.as_str()),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
&desc.vertex.module.source,
|
||||
)),
|
||||
}));
|
||||
let vrtx_state = wgpu::VertexState {
|
||||
module: &*vrtx_shad,
|
||||
entry_point: &desc.vertex.entry_point,
|
||||
buffers: &vrtx_buffs,
|
||||
};
|
||||
|
||||
let frag_module = desc.fragment.as_ref().map(|f| {
|
||||
if f.module == desc.vertex.module {
|
||||
vrtx_shad.clone()
|
||||
} else {
|
||||
Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: f.module.label.as_ref().map(|s| s.as_str()),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)),
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
let fm = frag_module.as_ref();
|
||||
let fstate = desc.fragment.as_ref().map(move |f| wgpu::FragmentState {
|
||||
module: fm.unwrap(),
|
||||
entry_point: &f.entry_point,
|
||||
targets: &f.targets,
|
||||
});
|
||||
|
||||
let render_desc = wgpu::RenderPipelineDescriptor {
|
||||
label: desc.label.as_deref(),
|
||||
layout: layout.as_ref(),
|
||||
vertex: vrtx_state,
|
||||
primitive: desc.primitive,
|
||||
depth_stencil: desc.depth_stencil.clone(),
|
||||
multisample: desc.multisample,
|
||||
fragment: fstate,
|
||||
multiview: desc.multiview,
|
||||
};
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&render_desc);
|
||||
|
||||
Self {
|
||||
layout,
|
||||
wgpu_pipeline: render_pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn layout(&self) -> Option<&PipelineLayout> {
|
||||
self.layout.as_ref()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn wgpu_pipeline(&self) -> &wgpu::RenderPipeline {
|
||||
&self.wgpu_pipeline
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct VertexBufferLayout {
|
||||
pub array_stride: wgpu::BufferAddress,
|
||||
pub step_mode: wgpu::VertexStepMode,
|
||||
pub attributes: Vec<wgpu::VertexAttribute>,
|
||||
}
|
||||
|
||||
impl<'a> From<wgpu::VertexBufferLayout<'a>> for VertexBufferLayout {
|
||||
fn from(value: wgpu::VertexBufferLayout) -> Self {
|
||||
Self {
|
||||
array_stride: value.array_stride,
|
||||
step_mode: value.step_mode,
|
||||
attributes: value.attributes.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the vertex stage in a render pipeline.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VertexState {
|
||||
// TODO: make this a ResHandle<Shader>
|
||||
/// The compiled shader module for the stage.
|
||||
pub module: Rc<Shader>,
|
||||
/// The entry point in the compiled shader.
|
||||
/// There must be a function in the shader with the same name.
|
||||
pub entry_point: String,
|
||||
/// The format of the vertex buffers used with this pipeline.
|
||||
pub buffers: Vec<VertexBufferLayout>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Shader {
|
||||
pub label: Option<String>,
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
/// Describes the fragment stage in the render pipeline.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FragmentState {
|
||||
// TODO: make this a ResHandle<Shader>
|
||||
/// The compiled shader module for the stage.
|
||||
pub module: Rc<Shader>,
|
||||
/// The entry point in the compiled shader.
|
||||
/// There must be a function in the shader with the same name.
|
||||
pub entry_point: String,
|
||||
/// The color state of the render targets.
|
||||
pub targets: Vec<Option<wgpu::ColorTargetState>>,
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> u_triangle_color: vec4<f32>;
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) in_vertex_index: u32,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let x = f32(1 - i32(in_vertex_index)) * 0.5;
|
||||
let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5;
|
||||
out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(u_triangle_color.xyz, 1.0);
|
||||
}
|
|
@ -273,7 +273,10 @@ impl RenderTexture {
|
|||
};
|
||||
let texture = device.create_texture(&desc);
|
||||
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
format: Some(wgpu::TextureFormat::Depth32Float),
|
||||
..Default::default()
|
||||
});
|
||||
let sampler = device.create_sampler(
|
||||
&wgpu::SamplerDescriptor { // 4.
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64};
|
||||
use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64, rc::Rc};
|
||||
|
||||
use lyra_ecs::Entity;
|
||||
use tracing::instrument;
|
||||
|
@ -162,7 +162,7 @@ impl<K: Hash + Eq + PartialEq + Clone, V: Clone, S: BuildHasher> CachedValMap<K,
|
|||
/// [`TransformGroup`]s are used to represent entries in the buffer. They are used to insert,
|
||||
/// update, and retrieve the transforms.
|
||||
pub struct TransformBuffers {
|
||||
pub bindgroup_layout: wgpu::BindGroupLayout,
|
||||
pub bindgroup_layout: Rc<wgpu::BindGroupLayout>,
|
||||
//groups: CachedValMap<TransformGroupId, TransformIndex>,
|
||||
//groups: SlotMap<TransformGroupId, TransformIndex>,
|
||||
entries: Vec<BufferEntry>,
|
||||
|
@ -192,7 +192,7 @@ impl TransformBuffers {
|
|||
});
|
||||
|
||||
let mut s = Self {
|
||||
bindgroup_layout,
|
||||
bindgroup_layout: Rc::new(bindgroup_layout),
|
||||
entries: Default::default(),
|
||||
max_transform_count: (limits.max_uniform_buffer_binding_size) as usize / (limits.min_uniform_buffer_offset_alignment as usize), //(mem::size_of::<glam::Mat4>()),
|
||||
limits,
|
||||
|
@ -209,6 +209,7 @@ impl TransformBuffers {
|
|||
///
|
||||
/// 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;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
||||
lyra-reflect = { path = "../lyra-reflect" }
|
||||
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
|
||||
lyra-math = { path = "../lyra-math" }
|
||||
lyra-scene = { path = "../lyra-scene" }
|
||||
anyhow = "1.0.75"
|
||||
|
|
|
@ -34,10 +34,10 @@ impl From<gltf::material::PbrMetallicRoughness<'_>> for PbrRoughness {
|
|||
#[derive(Clone, Debug, Default, Reflect)]
|
||||
pub struct PbrGlossiness {
|
||||
/// The rgba diffuse color of the material
|
||||
pub diffuse_color: glam::Vec4,
|
||||
pub diffuse_color: lyra_math::Vec4,
|
||||
// The base color texture
|
||||
// pub diffuse_texture // TODO
|
||||
pub specular: glam::Vec3,
|
||||
pub specular: lyra_math::Vec3,
|
||||
/// The glossiness factor of the material.
|
||||
/// From 0.0 (no glossiness) to 1.0 (full glossiness)
|
||||
pub glossiness: f32,
|
||||
|
@ -101,7 +101,7 @@ pub struct Specular {
|
|||
pub factor: f32,
|
||||
|
||||
/// The color of the specular reflection
|
||||
pub color_factor: glam::Vec3,
|
||||
pub color_factor: lyra_math::Vec3,
|
||||
|
||||
/// A texture that defines the strength of the specular reflection,
|
||||
/// stored in the alpha (`A`) channel. This will be multiplied by
|
||||
|
@ -140,7 +140,7 @@ pub struct Material {
|
|||
//pub pbr_roughness: PbrRoughness,
|
||||
/// The RGBA base color of the model. If a texture is supplied with `base_color_texture`, this value
|
||||
/// will tint the texture. If a texture is not provided, this value would be the color of the Material.
|
||||
pub base_color: glam::Vec4,
|
||||
pub base_color: lyra_math::Vec4,
|
||||
/// The metalness of the material
|
||||
/// From 0.0 (non-metal) to 1.0 (metal)
|
||||
pub metallic: f32,
|
||||
|
|
|
@ -50,9 +50,9 @@ impl From<Vec<u32>> for MeshIndices {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, Reflect)]
|
||||
pub enum VertexAttributeData {
|
||||
Vec2(Vec<glam::Vec2>),
|
||||
Vec3(Vec<glam::Vec3>),
|
||||
Vec4(Vec<glam::Vec4>),
|
||||
Vec2(Vec<lyra_math::Vec2>),
|
||||
Vec3(Vec<lyra_math::Vec3>),
|
||||
Vec4(Vec<lyra_math::Vec4>),
|
||||
}
|
||||
|
||||
impl VertexAttributeData {
|
||||
|
|
|
@ -374,7 +374,7 @@ pub(crate) mod tests {
|
|||
|
||||
use instant::Instant;
|
||||
|
||||
use crate::{Image, ResourceData, Texture};
|
||||
use crate::{Image, ResourceData};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -431,10 +431,10 @@ pub(crate) mod tests {
|
|||
#[test]
|
||||
fn ensure_single() {
|
||||
let man = ResourceManager::new();
|
||||
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
|
||||
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap();
|
||||
assert_eq!(Arc::strong_count(&res.handle.res), 3);
|
||||
|
||||
let resagain = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
|
||||
let resagain = man.request::<Image>(&get_image("squiggles.png")).unwrap();
|
||||
assert_eq!(Arc::strong_count(&resagain.handle.res), 4);
|
||||
}
|
||||
|
||||
|
@ -442,7 +442,7 @@ pub(crate) mod tests {
|
|||
#[test]
|
||||
fn ensure_none() {
|
||||
let man = ResourceManager::new();
|
||||
let res = man.request::<Texture>(&get_image("squigglesfff.png")).unwrap();
|
||||
let res = man.request::<Image>(&get_image("squigglesfff.png")).unwrap();
|
||||
//let err = res.err().unwrap();
|
||||
|
||||
// 1 second should be enough to run into an error
|
||||
|
@ -468,7 +468,7 @@ pub(crate) mod tests {
|
|||
#[test]
|
||||
fn reload_image() {
|
||||
let man = ResourceManager::new();
|
||||
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
|
||||
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap();
|
||||
busy_wait_resource(&res, 10.0);
|
||||
let img = res.data_ref();
|
||||
img.unwrap();
|
||||
|
@ -489,7 +489,7 @@ pub(crate) mod tests {
|
|||
std::fs::copy(orig_path, &image_path).unwrap();
|
||||
|
||||
let man = ResourceManager::new();
|
||||
let res = man.request::<Texture>(&image_path).unwrap();
|
||||
let res = man.request::<Image>(&image_path).unwrap();
|
||||
busy_wait_resource(&res, 10.0);
|
||||
let img = res.data_ref();
|
||||
img.unwrap();
|
||||
|
|
Loading…
Reference in New Issue