Compare commits

..

29 Commits

Author SHA1 Message Date
SeanOMik 256025849e Merge pull request 'Implement Shadows' (#24) from feat/shadow-maps into main
CI / build (push) Successful in 3m38s Details
Reviewed-on: #24
2024-08-09 23:10:28 -04:00
SeanOMik 8545e7e27d
render: rewrite PCF for spot lights to somehow fix PCSS directional lights
CI / build (pull_request) Successful in 9m48s Details
2024-08-09 22:01:57 -04:00
SeanOMik a85178eeea Revert "render: shadow maps and PCF for spot lights"
This reverts commit 8c1738334c.
2024-08-09 21:51:56 -04:00
SeanOMik 8c1738334c
render: shadow maps and PCF for spot lights 2024-07-24 20:10:32 -04:00
SeanOMik fefcf58765
render: make shadow depth bias configurable per light source 2024-07-21 21:53:02 -04:00
SeanOMik b0a6d30afc
render: fix directional light shadows 2024-07-21 21:09:29 -04:00
SeanOMik fef709d5f1
render: implement PCF for point lights, support per-light shadow settings 2024-07-21 12:02:35 -04:00
SeanOMik c91ee67961
render: improve shadow settings to make it possible to switch between PCF, PCSS, hardware 2x2 PCF, or disable filtering all together 2024-07-19 17:56:27 -04:00
SeanOMik c961568b96
render: update the shadow filting poisson disc when shadow settings are modified 2024-07-19 16:07:40 -04:00
SeanOMik 54b47c2178
ecs: implement change tracking for world resources 2024-07-19 16:07:03 -04:00
SeanOMik 4449172c2b
render: implement PCSS for directional lights 2024-07-18 23:43:08 -04:00
SeanOMik 4c6c6c4dd5
render: PCF with poisson disc on directional lights 2024-07-14 22:14:08 -04:00
SeanOMik 27bc88c5a7
render: pass shadow settings to gpu 2024-07-14 19:46:15 -04:00
SeanOMik ff06bd55f3
render: simple PCF 2024-07-14 19:06:38 -04:00
SeanOMik d02258224a
render: fix bug with texture atlas not packing textures in last column 2024-07-14 12:24:13 -04:00
SeanOMik b45c2f4fab
render: point light shadows in texture atlas, fix bug with unaligned GpuSlotBuffer 2024-07-13 00:56:09 -04:00
SeanOMik 40fa9c09da
render: fix shadow map atlas packing by writing my own skyline packer 2024-07-12 14:58:18 -04:00
SeanOMik 87aa440691
render: create a GpuSlotBuffer for stable indices in a gpu buffer 2024-07-11 20:00:46 -04:00
SeanOMik cc1c482c40
render: provide shadow texture atlas frame for each shadow casting light 2024-07-11 18:27:26 -04:00
SeanOMik a4ce4cb432
render: implement packed texture atlas for shadow maps 2024-07-10 20:16:21 -04:00
SeanOMik e2b554b4ef
render: implement simple texture atlas for the shadow maps 2024-07-05 17:29:38 -04:00
SeanOMik 6d57b40629
render: cull back faces, code cleanup to fix warnings 2024-07-04 23:28:21 -04:00
SeanOMik fd65f754cf
render: get simple directional shadow maps working 2024-07-04 13:43:36 -04:00
SeanOMik 6c6893149a
render: bind direction light projection matrix to meshes shader 2024-06-30 21:58:08 -04:00
SeanOMik 1c649b2eb6
render: bind the shadow map atlas to the meshes shaders 2024-06-30 21:42:08 -04:00
SeanOMik 7b2d2424a3
render: start moving to a shadow map atlas texture and expose the resources as slots 2024-06-30 20:56:41 -04:00
SeanOMik e8974bbd44
render: create a depth map for the directional light 2024-06-30 19:33:51 -04:00
SeanOMik 3a80c069c9
render: move most of the mesh processing to a MeshPrepare node
Moving that out of the MeshesPass makes the rendering meshes accessible to other passes/nodes. The shadow pass will need access to them which is why this was done now
2024-06-29 22:23:49 -04:00
SeanOMik 7ff67a194b
create an example for testing shadow maps 2024-06-28 16:15:21 -04:00
42 changed files with 4303 additions and 701 deletions

18
.vscode/launch.json vendored
View File

@ -22,6 +22,24 @@
"args": [], "args": [],
"cwd": "${workspaceFolder}/examples/testbed" "cwd": "${workspaceFolder}/examples/testbed"
}, },
{
"type": "lldb",
"request": "launch",
"name": "Debug lyra shadows",
"cargo": {
"args": [
"build",
"--manifest-path", "${workspaceFolder}/examples/shadows/Cargo.toml"
//"--bin=shadows",
],
"filter": {
"name": "shadows",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}/examples/shadows"
},
{ {
"type": "lldb", "type": "lldb",
"request": "launch", "request": "launch",

170
Cargo.lock generated
View File

@ -65,6 +65,24 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "aligned"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923"
dependencies = [
"as-slice",
]
[[package]]
name = "aligned-array"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c92d086290f52938013f6242ac62bf7d401fab8ad36798a609faa65c3fd2c"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.16" version = "0.2.16"
@ -122,6 +140,15 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "as-slice"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
dependencies = [
"stable_deref_trait",
]
[[package]] [[package]]
name = "ash" name = "ash"
version = "0.37.3+1.3.251" version = "0.37.3+1.3.251"
@ -351,6 +378,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "az"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.69" version = "0.3.69"
@ -786,6 +819,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
name = "divrem"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69dde51e8fef5e12c1d65e0929b03d66e4c0c18282bc30ed2ca050ad6f44dd82"
[[package]] [[package]]
name = "dlib" name = "dlib"
version = "0.5.2" version = "0.5.2"
@ -795,6 +834,12 @@ dependencies = [
"libloading 0.8.1", "libloading 0.8.1",
] ]
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "downcast-rs" name = "downcast-rs"
version = "1.2.0" version = "1.2.0"
@ -807,6 +852,12 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "elapsed"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f4e5af126dafd0741c2ad62d47f68b28602550102e5f0dd45c8a97fc8b49c29"
[[package]] [[package]]
name = "elua" name = "elua"
version = "0.1.0" version = "0.1.0"
@ -908,6 +959,18 @@ dependencies = [
"zune-inflate", "zune-inflate",
] ]
[[package]]
name = "fast_poisson"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2472baa9796d2ee497bd61690e3093a26935390d8ce0dd0ddc2db9b47a65898f"
dependencies = [
"kiddo",
"rand 0.8.5",
"rand_distr",
"rand_xoshiro",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.9.0" version = "1.9.0"
@ -953,6 +1016,19 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "fixed"
version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fc715d38bea7b5bf487fcd79bcf8c209f0b58014f3018a7a19c2b855f472048"
dependencies = [
"az",
"bytemuck",
"half",
"num-traits",
"typenum",
]
[[package]] [[package]]
name = "fixed-timestep-rotating-model" name = "fixed-timestep-rotating-model"
version = "0.1.0" version = "0.1.0"
@ -1683,6 +1759,26 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "kiddo"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1c5ea778d68eacd5c33f29537ba0b7b6c2595e74ee013a69cedc20ab4d3177"
dependencies = [
"aligned",
"aligned-array",
"az",
"divrem",
"doc-comment",
"elapsed",
"fixed",
"log",
"min-max-heap",
"num-traits",
"rand 0.8.5",
"rayon",
]
[[package]] [[package]]
name = "kqueue" name = "kqueue"
version = "1.0.8" version = "1.0.8"
@ -1750,6 +1846,12 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.0.2" version = "0.0.2"
@ -1868,6 +1970,7 @@ dependencies = [
"bind_match", "bind_match",
"bytemuck", "bytemuck",
"cfg-if", "cfg-if",
"fast_poisson",
"gilrs-core", "gilrs-core",
"glam", "glam",
"image", "image",
@ -1881,6 +1984,7 @@ dependencies = [
"lyra-scene", "lyra-scene",
"petgraph", "petgraph",
"quote", "quote",
"round_mult",
"rustc-hash", "rustc-hash",
"syn 2.0.51", "syn 2.0.51",
"thiserror", "thiserror",
@ -2078,6 +2182,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "min-max-heap"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2687e6cf9c00f48e9284cf9fd15f2ef341d03cc7743abf9df4c5f07fdee50b18"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.1" version = "0.7.1"
@ -2285,6 +2395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"libm",
] ]
[[package]] [[package]]
@ -2726,6 +2837,25 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "rand_distr"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
dependencies = [
"num-traits",
"rand 0.8.5",
]
[[package]]
name = "rand_xoshiro"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
dependencies = [
"rand_core 0.6.4",
]
[[package]] [[package]]
name = "range-alloc" name = "range-alloc"
version = "0.1.3" version = "0.1.3"
@ -2884,6 +3014,15 @@ dependencies = [
"winreg", "winreg",
] ]
[[package]]
name = "round_mult"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74bc7d5286c4d36f09aa6ae93f76acf6aa068cd62bc02970a9deb24763655dee"
dependencies = [
"rustc_version",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -2896,6 +3035,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.37.27" version = "0.37.27"
@ -3010,6 +3158,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.194" version = "1.0.194"
@ -3075,6 +3229,16 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "shadows"
version = "0.1.0"
dependencies = [
"anyhow",
"async-std",
"lyra-engine",
"tracing",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@ -3191,6 +3355,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"

View File

@ -5,7 +5,6 @@ edition = "2021"
[workspace] [workspace]
members = [ members = [
"examples/testbed",
"lyra-resource", "lyra-resource",
"lyra-ecs", "lyra-ecs",
"lyra-reflect", "lyra-reflect",
@ -14,10 +13,12 @@ members = [
"lyra-math", "lyra-math",
"lyra-scene", "lyra-scene",
"examples/testbed",
"examples/many-lights", "examples/many-lights",
"examples/fixed-timestep-rotating-model", "examples/fixed-timestep-rotating-model",
"examples/lua-scripting", "examples/lua-scripting",
"examples/simple_scene" "examples/simple_scene",
"examples/shadows",
] ]
[features] [features]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,142 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.1.63",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube",
"scale":[
10,
0.25,
10
]
}
],
"materials":[
{
"doubleSided":true,
"name":"Material.001",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Cube.001",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/jpeg",
"name":"wood1",
"uri":"wood1.jpg"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":576,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":840,
"uri":"model.bin"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,10 @@
[package]
name = "shadows"
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"

View File

@ -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 ]]

View File

@ -0,0 +1,249 @@
use lyra_engine::{
assets::{gltf::Gltf, ResourceManager},
game::Game,
input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
},
math::{self, Quat, Transform, Vec3},
render::{
graph::{ShadowCasterSettings, ShadowFilteringMode},
light::{directional::DirectionalLight, PointLight, SpotLight},
},
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];
let palm_tree_platform_gltf = resman
.request::<Gltf>("../assets/shadows-platform-palmtree.glb")
.unwrap();
palm_tree_platform_gltf.wait_recurse_dependencies_load();
let palm_tree_platform_mesh = &palm_tree_platform_gltf.data_ref().unwrap().scenes[0];
drop(resman);
// cube in the air
/* world.spawn((
cube_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(0.0, -2.0, -5.0),
));
// cube really high in the air
world.spawn((
cube_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(-6.0, 0.0, -5.0),
));
// cube on the right, on the ground
world.spawn((
cube_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(3.0, -3.75, -5.0),
));
world.spawn((
platform_mesh.clone(),
WorldTransform::default(),
//Transform::from_xyz(0.0, -5.0, -5.0),
Transform::new(math::vec3(0.0, -5.0, -5.0), math::Quat::IDENTITY, math::vec3(5.0, 1.0, 5.0)),
)); */
world.spawn((
palm_tree_platform_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(5.0, -15.0, 0.0),
//Transform::new(math::vec3(0.0, -5.0, -5.0), math::Quat::IDENTITY, math::vec3(5.0, 1.0, 5.0)),
));
//shadows-platform-palmtree.glb
{
let mut light_tran = Transform::from_xyz(0.0, 0.0, 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(-35.0));
world.spawn((
//cube_mesh.clone(),
DirectionalLight {
enabled: true,
color: Vec3::new(1.0, 0.95, 0.9),
intensity: 0.9,
},
ShadowCasterSettings {
filtering_mode: ShadowFilteringMode::Pcss,
pcf_samples_num: 64,
pcss_blocker_search_samples: 36,
constant_depth_bias_scale: 5.0,
..Default::default()
},
light_tran,
));
/* world.spawn((
cube_mesh.clone(),
PointLight {
enabled: true,
color: Vec3::new(0.133, 0.098, 0.91),
intensity: 2.0,
range: 10.0,
..Default::default()
},
ShadowCasterSettings {
filtering_mode: ShadowFilteringMode::Pcf,
..Default::default()
},
Transform::new(
Vec3::new(4.0 - 1.43, -13.0, 1.53),
Quat::IDENTITY,
Vec3::new(0.5, 0.5, 0.5),
),
)); */
let t = Transform::new(
Vec3::new(4.0 - 1.43, -13.0, 0.0),
//Vec3::new(-5.0, 1.0, -0.28),
//Vec3::new(-10.0, 0.94, -0.28),
Quat::from_euler(math::EulerRot::XYZ, 0.0, math::Angle::Degrees(-45.0).to_radians(), 0.0),
Vec3::new(0.15, 0.15, 0.15),
);
world.spawn((
SpotLight {
enabled: true,
color: Vec3::new(1.0, 0.0, 0.0),
intensity: 3.0,
range: 4.5,
//cutoff: math::Angle::Degrees(45.0),
..Default::default()
},
/* ShadowCasterSettings {
filtering_mode: ShadowFilteringMode::Pcf,
..Default::default()
}, */
WorldTransform::from(t),
t,
//cube_mesh.clone(),
));
}
let mut camera = CameraComponent::new_3d();
camera.transform.translation = math::Vec3::new(-1.0, -10.0, -1.5);
camera.transform.rotate_x(math::Angle::Degrees(-27.0));
camera.transform.rotate_y(math::Angle::Degrees(-90.0));
world.spawn((camera, FreeFlyCamera::default()));
}

View File

@ -2,6 +2,8 @@ use std::{any::{Any, TypeId}, sync::Arc};
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use crate::{Tick, TickTracker};
/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually. /// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually.
pub trait ResourceObject: Send + Sync + Any { pub trait ResourceObject: Send + Sync + Any {
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
@ -23,14 +25,17 @@ impl<T: Send + Sync + Any> ResourceObject for T {
pub struct ResourceData { pub struct ResourceData {
pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>, pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>,
type_id: TypeId, type_id: TypeId,
// use a tick tracker which has interior mutability
pub(crate) tick: TickTracker,
} }
impl ResourceData { impl ResourceData {
pub fn new<T: ResourceObject>(data: T) -> Self { pub fn new<T: ResourceObject>(data: T, tick: Tick) -> Self {
Self { Self {
data: Arc::new(AtomicRefCell::new(data)), data: Arc::new(AtomicRefCell::new(data)),
type_id: TypeId::of::<T>(), type_id: TypeId::of::<T>(),
tick: TickTracker::from(*tick),
} }
} }
@ -80,4 +85,8 @@ impl ResourceData {
.map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap())) .map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap()))
.ok() .ok()
} }
pub fn changed(&self, tick: Tick) -> bool {
self.tick.current() >= tick
}
} }

View File

@ -374,33 +374,65 @@ impl World {
ViewOne::new(self, entity.id, T::Query::new()) ViewOne::new(self, entity.id, T::Query::new())
} }
//pub fn view_one(&self, entity: EntityId) -> /// Add a resource to the world.
///
/// Ticks the world.
pub fn add_resource<T: ResourceObject>(&mut self, data: T) { pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data)); let tick = self.tick();
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, tick));
} }
/// Add the default value of a resource.
///
/// Ticks the world.
///
/// > Note: This will replace existing values.
pub fn add_resource_default<T: ResourceObject + Default>(&mut self) { pub fn add_resource_default<T: ResourceObject + Default>(&mut self) {
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default())); let tick = self.tick();
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), tick));
}
/// Add the default value of a resource if it does not already exist.
///
/// Returns a boolean indicating if the resource was added. Ticks the world if the resource
/// was added.
pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool {
let id = TypeId::of::<T>();
if !self.resources.contains_key(&id) {
let tick = self.tick();
self.resources.insert(id, ResourceData::new(T::default(), tick));
true
} else {
false
}
} }
/// Get a resource from the world, or insert it into the world with the provided /// Get a resource from the world, or insert it into the world with the provided
/// `fn` and return it. /// `fn` and return it.
///
/// Ticks the world.
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T> pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T>
where where
F: Fn() -> T + 'static F: Fn() -> T + 'static
{ {
self.resources.entry(TypeId::of::<T>()) let tick = self.tick();
.or_insert_with(|| ResourceData::new(f())) let res = self.resources.entry(TypeId::of::<T>())
.get_mut() .or_insert_with(|| ResourceData::new(f(), tick));
res.tick.tick_to(&tick);
res.get_mut()
} }
/// Get a resource from the world, or insert it into the world as its default. /// Get a resource from the world, or insert it into the world as its default.
///
/// Ticks the world.
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T> pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T>
{ {
self.resources.entry(TypeId::of::<T>()) let tick = self.tick();
.or_insert_with(|| ResourceData::new(T::default())) let res = self.resources.entry(TypeId::of::<T>())
.get_mut() .or_insert_with(|| ResourceData::new(T::default(), tick));
res.tick.tick_to(&tick);
res.get_mut()
} }
/// Gets a resource from the World. /// Gets a resource from the World.
@ -413,6 +445,22 @@ impl World {
.get() .get()
} }
/// Returns a boolean indicating if the resource changed.
///
/// This will return false if the resource doesn't exist.
pub fn has_resource_changed<T: ResourceObject>(&self) -> bool {
let tick = self.current_tick();
self.resources.get(&TypeId::of::<T>())
.map(|r| r.changed(tick))
.unwrap_or(false)
}
/// Returns the [`Tick`] that the resource was last modified at.
pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.tick.current())
}
/// Returns boolean indicating if the World contains a resource of type `T`. /// Returns boolean indicating if the World contains a resource of type `T`.
pub fn has_resource<T: ResourceObject>(&self) -> bool { pub fn has_resource<T: ResourceObject>(&self) -> bool {
self.resources.contains_key(&TypeId::of::<T>()) self.resources.contains_key(&TypeId::of::<T>())
@ -430,18 +478,32 @@ impl World {
/// ///
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for /// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for
/// a function that returns an option. /// a function that returns an option.
///
/// Ticks the world.
pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> { pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
self.resources.get(&TypeId::of::<T>()) self.try_get_resource_mut::<T>()
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>())) .unwrap_or_else(|| panic!("World is missing resource of type '{}'", std::any::type_name::<T>()))
.get_mut()
} }
/// Attempts to get a mutable borrow of a resource from the World. /// Attempts to get a mutable borrow of a resource from the World.
/// ///
/// Returns `None` if the resource was not found. /// Returns `None` if the resource was not found. Ticks the world if the resource was found.
pub fn try_get_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> { pub fn try_get_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
self.resources.get(&TypeId::of::<T>()) self.resources.get(&TypeId::of::<T>())
.and_then(|r| r.try_get_mut()) .and_then(|r| {
// now that the resource was retrieved, tick the world and the resource
let new_tick = self.tick();
r.tick.tick_to(&new_tick);
r.try_get_mut()
})
}
/// Get the corresponding [`ResourceData`].
///
/// > Note: If you borrow the resource mutably, the world and the resource will not be ticked.
pub fn try_get_resource_data<T: ResourceObject>(&self) -> Option<ResourceData> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.clone())
} }
/// Increments the TickTracker which is used for tracking changes to components. /// Increments the TickTracker which is used for tracking changes to components.
@ -688,4 +750,22 @@ mod tests {
let pos = world.view_one::<&mut Vec2>(second).get().unwrap(); let pos = world.view_one::<&mut Vec2>(second).get().unwrap();
assert_eq!(*pos, Vec2::new(5.0, 5.0)); assert_eq!(*pos, Vec2::new(5.0, 5.0));
} }
/// Tests resource change checks
#[test]
fn resource_changed() {
let mut world = World::new();
world.add_resource(SimpleCounter(50));
assert!(world.has_resource_changed::<SimpleCounter>());
world.spawn(Vec2::new(50.0, 50.0));
assert!(!world.has_resource_changed::<SimpleCounter>());
let mut counter = world.get_resource_mut::<SimpleCounter>();
counter.0 += 100;
assert!(world.has_resource_changed::<SimpleCounter>());
}
} }

View File

@ -38,6 +38,8 @@ unique = "0.9.1"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
petgraph = { version = "0.6.5", features = ["matrix_graph"] } petgraph = { version = "0.6.5", features = ["matrix_graph"] }
bind_match = "0.1.2" bind_match = "0.1.2"
round_mult = "0.1.3"
fast_poisson = { version = "1.0.0", features = ["single_precision"] }
[features] [features]
tracy = ["dep:tracing-tracy"] tracy = ["dep:tracing-tracy"]

View File

@ -95,9 +95,9 @@ struct NodeEntry {
struct BindGroupEntry { struct BindGroupEntry {
label: RenderGraphLabelValue, label: RenderGraphLabelValue,
/// BindGroup /// BindGroup
bg: Rc<wgpu::BindGroup>, bg: Arc<wgpu::BindGroup>,
/// BindGroupLayout /// BindGroupLayout
layout: Option<Rc<wgpu::BindGroupLayout>>, layout: Option<Arc<wgpu::BindGroupLayout>>,
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -368,22 +368,23 @@ impl RenderGraph {
} }
#[inline(always)] #[inline(always)]
pub fn try_bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Rc<wgpu::BindGroup>> { pub fn try_bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Arc<wgpu::BindGroup>> {
self.bind_groups.get(&label.into()).map(|e| &e.bg) self.bind_groups.get(&label.into()).map(|e| &e.bg)
} }
#[inline(always)] #[inline(always)]
pub fn bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Rc<wgpu::BindGroup> { pub fn bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Arc<wgpu::BindGroup> {
self.try_bind_group(label).expect("Unknown id for bind group") let l = label.into();
self.try_bind_group(l.clone()).unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone()))
} }
#[inline(always)] #[inline(always)]
pub fn try_bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Rc<wgpu::BindGroupLayout>> { pub fn try_bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Arc<wgpu::BindGroupLayout>> {
self.bind_groups.get(&label.into()).and_then(|e| e.layout.as_ref()) self.bind_groups.get(&label.into()).and_then(|e| e.layout.as_ref())
} }
#[inline(always)] #[inline(always)]
pub fn bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Rc<wgpu::BindGroupLayout> { pub fn bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Arc<wgpu::BindGroupLayout> {
let l = label.into(); let l = label.into();
self.try_bind_group_layout(l.clone()) self.try_bind_group_layout(l.clone())
.unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone())) .unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone()))

View File

@ -1,4 +1,4 @@
use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc}; use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc, sync::Arc};
use bind_match::bind_match; use bind_match::bind_match;
use lyra_ecs::World; use lyra_ecs::World;
@ -54,16 +54,24 @@ pub enum SlotValue {
/// The value will be set during a later phase of the render graph. To see the type of value /// 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. /// this will be set to, see the slots type.
Lazy, Lazy,
TextureView(Rc<wgpu::TextureView>), TextureView(Arc<wgpu::TextureView>),
Sampler(Rc<wgpu::Sampler>), Sampler(Rc<wgpu::Sampler>),
Texture(Rc<wgpu::Texture>), Texture(Arc<wgpu::Texture>),
Buffer(Rc<wgpu::Buffer>), Buffer(Arc<wgpu::Buffer>),
RenderTarget(Rc<RefCell<RenderTarget>>), RenderTarget(Rc<RefCell<RenderTarget>>),
Frame(Rc<RefCell<Option<Frame>>>), Frame(Rc<RefCell<Option<Frame>>>),
} }
impl SlotValue { impl SlotValue {
pub fn as_texture_view(&self) -> Option<&Rc<wgpu::TextureView>> { pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
pub fn is_lazy(&self) -> bool {
matches!(self, Self::Lazy)
}
pub fn as_texture_view(&self) -> Option<&Arc<wgpu::TextureView>> {
bind_match!(self, Self::TextureView(v) => v) bind_match!(self, Self::TextureView(v) => v)
} }
@ -71,11 +79,11 @@ impl SlotValue {
bind_match!(self, Self::Sampler(v) => v) bind_match!(self, Self::Sampler(v) => v)
} }
pub fn as_texture(&self) -> Option<&Rc<wgpu::Texture>> { pub fn as_texture(&self) -> Option<&Arc<wgpu::Texture>> {
bind_match!(self, Self::Texture(v) => v) bind_match!(self, Self::Texture(v) => v)
} }
pub fn as_buffer(&self) -> Option<&Rc<wgpu::Buffer>> { pub fn as_buffer(&self) -> Option<&Arc<wgpu::Buffer>> {
bind_match!(self, Self::Buffer(v) => v) bind_match!(self, Self::Buffer(v) => v)
} }
@ -189,8 +197,8 @@ pub struct NodeDesc {
/// This makes the bind groups accessible to other Nodes. /// This makes the bind groups accessible to other Nodes.
pub bind_groups: Vec<( pub bind_groups: Vec<(
RenderGraphLabelValue, RenderGraphLabelValue,
Rc<wgpu::BindGroup>, Arc<wgpu::BindGroup>,
Option<Rc<wgpu::BindGroupLayout>>, Option<Arc<wgpu::BindGroupLayout>>,
)>, )>,
} }
@ -199,7 +207,7 @@ impl NodeDesc {
pub fn new( pub fn new(
pass_type: NodeType, pass_type: NodeType,
pipeline_desc: Option<PipelineDescriptor>, pipeline_desc: Option<PipelineDescriptor>,
bind_groups: Vec<(&dyn RenderGraphLabel, Rc<wgpu::BindGroup>, Option<Rc<wgpu::BindGroupLayout>>)>, bind_groups: Vec<(&dyn RenderGraphLabel, Arc<wgpu::BindGroup>, Option<Arc<wgpu::BindGroupLayout>>)>,
) -> Self { ) -> Self {
Self { Self {
ty: pass_type, ty: pass_type,

View File

@ -1,4 +1,4 @@
use std::rc::Rc; use std::sync::Arc;
use glam::UVec2; use glam::UVec2;
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
@ -56,8 +56,8 @@ impl Node for BasePass {
.buffer_dynamic_offset(false) .buffer_dynamic_offset(false)
.contents(&[self.screen_size]) .contents(&[self.screen_size])
.finish_parts(graph.device()); .finish_parts(graph.device());
let screen_size_bgl = Rc::new(screen_size_bgl); let screen_size_bgl = Arc::new(screen_size_bgl);
let screen_size_bg = Rc::new(screen_size_bg); let screen_size_bg = Arc::new(screen_size_bg);
let (camera_bgl, camera_bg, camera_buf, _) = BufferWrapper::builder() let (camera_bgl, camera_bg, camera_buf, _) = BufferWrapper::builder()
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
@ -66,17 +66,17 @@ impl Node for BasePass {
.buffer_dynamic_offset(false) .buffer_dynamic_offset(false)
.contents(&[CameraUniform::default()]) .contents(&[CameraUniform::default()])
.finish_parts(graph.device()); .finish_parts(graph.device());
let camera_bgl = Rc::new(camera_bgl); let camera_bgl = Arc::new(camera_bgl);
let camera_bg = Rc::new(camera_bg); let camera_bg = Arc::new(camera_bg);
// create the depth texture using the utility struct, then take all the required fields // create the depth texture using the utility struct, then take all the required fields
let mut depth_texture = RenderTexture::create_depth_texture(graph.device(), self.screen_size, "depth_texture"); let mut depth_texture = RenderTexture::create_depth_texture(graph.device(), self.screen_size, "depth_texture");
depth_texture.create_bind_group(&graph.device); depth_texture.create_bind_group(&graph.device);
let dt_bg_pair = depth_texture.bindgroup_pair.unwrap(); let dt_bg_pair = depth_texture.bindgroup_pair.unwrap();
let depth_texture_bg = Rc::new(dt_bg_pair.bindgroup); let depth_texture_bg = Arc::new(dt_bg_pair.bindgroup);
let depth_texture_bgl = dt_bg_pair.layout; let depth_texture_bgl = dt_bg_pair.layout;
let depth_texture_view = Rc::new(depth_texture.view); let depth_texture_view = Arc::new(depth_texture.view);
let mut desc = NodeDesc::new( let mut desc = NodeDesc::new(
NodeType::Node, NodeType::Node,
@ -102,12 +102,12 @@ impl Node for BasePass {
desc.add_buffer_slot( desc.add_buffer_slot(
BasePassSlots::ScreenSize, BasePassSlots::ScreenSize,
SlotAttribute::Output, SlotAttribute::Output,
Some(SlotValue::Buffer(Rc::new(screen_size_buf))), Some(SlotValue::Buffer(Arc::new(screen_size_buf))),
); );
desc.add_buffer_slot( desc.add_buffer_slot(
BasePassSlots::Camera, BasePassSlots::Camera,
SlotAttribute::Output, SlotAttribute::Output,
Some(SlotValue::Buffer(Rc::new(camera_buf))), Some(SlotValue::Buffer(Arc::new(camera_buf))),
); );
desc desc

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, rc::Rc}; use std::{collections::HashMap, rc::Rc, sync::Arc};
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
@ -13,7 +13,7 @@ pub struct FxaaPassLabel;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct FxaaPass { pub struct FxaaPass {
target_sampler: Option<wgpu::Sampler>, target_sampler: Option<wgpu::Sampler>,
bgl: Option<Rc<wgpu::BindGroupLayout>>, bgl: Option<Arc<wgpu::BindGroupLayout>>,
/// Store bind groups for the input textures. /// Store bind groups for the input textures.
/// The texture may change due to resizes, or changes to the view target chain /// The texture may change due to resizes, or changes to the view target chain
/// from other nodes. /// from other nodes.
@ -54,7 +54,7 @@ impl Node for FxaaPass {
}, },
], ],
}); });
let bgl = Rc::new(bgl); let bgl = Arc::new(bgl);
self.bgl = Some(bgl.clone()); self.bgl = Some(bgl.clone());
self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor { self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("fxaa sampler"), label: Some("fxaa sampler"),

View File

@ -1,4 +1,4 @@
use std::{mem, rc::Rc}; use std::{mem, rc::Rc, sync::Arc};
use glam::Vec2Swizzles; use glam::Vec2Swizzles;
use lyra_ecs::World; use lyra_ecs::World;
@ -63,7 +63,7 @@ impl Node for LightCullComputePass {
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
}); });
let light_indices_bg_layout = Rc::new(device.create_bind_group_layout( let light_indices_bg_layout = Arc::new(device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor { &wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
@ -128,7 +128,7 @@ impl Node for LightCullComputePass {
array_layer_count: None, array_layer_count: None,
}); });
let light_indices_bg = Rc::new(device.create_bind_group(&wgpu::BindGroupDescriptor { let light_indices_bg = Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &light_indices_bg_layout, layout: &light_indices_bg_layout,
entries: &[ entries: &[
wgpu::BindGroupEntry { wgpu::BindGroupEntry {
@ -194,7 +194,7 @@ impl Node for LightCullComputePass {
desc.add_buffer_slot( desc.add_buffer_slot(
LightCullComputePassSlots::IndexCounterBuffer, LightCullComputePassSlots::IndexCounterBuffer,
SlotAttribute::Output, SlotAttribute::Output,
Some(SlotValue::Buffer(Rc::new(light_index_counter_buffer))), Some(SlotValue::Buffer(Arc::new(light_index_counter_buffer))),
); );
desc desc

View File

@ -0,0 +1,767 @@
use std::{
collections::{HashSet, VecDeque},
ops::{Deref, DerefMut},
sync::Arc,
};
use glam::{UVec2, Vec3};
use image::GenericImageView;
use itertools::izip;
use lyra_ecs::{
query::{
filter::{Has, Not, Or},
Entities, Res, ResMut, TickOf,
},
relation::{ChildOf, RelationOriginComponent},
Component, Entity, ResourceObject, World,
};
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};
use uuid::Uuid;
use wgpu::util::DeviceExt;
use crate::{
render::{
graph::{Node, NodeDesc, NodeType},
render_buffer::BufferStorage,
render_job::RenderJob,
texture::{res_filter_to_wgpu, res_wrap_to_wgpu},
transform_buffer_storage::{TransformBuffers, TransformGroup},
vertex::Vertex,
},
DeltaTime,
};
type MeshHandle = ResHandle<Mesh>;
type SceneHandle = ResHandle<SceneGraph>;
pub struct MeshBufferStorage {
pub buffer_vertex: BufferStorage,
pub 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?
pub material: Option<Arc<GpuMaterial>>,
}
#[derive(Clone, Debug, Component)]
struct InterpTransform {
last_transform: Transform,
alpha: f32,
}
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
pub struct MeshPrepNodeLabel;
#[derive(Debug)]
pub struct MeshPrepNode {
pub material_bgl: Arc<wgpu::BindGroupLayout>,
}
impl MeshPrepNode {
pub fn new(device: &wgpu::Device) -> Self {
let bgl = GpuMaterial::create_bind_group_layout(device);
Self { material_bgl: bgl }
}
/// Checks if the mesh buffers in the GPU need to be updated.
#[instrument(skip(self, device, mesh_buffers, queue, mesh_han))]
fn check_mesh_buffers(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
mesh_buffers: &mut FxHashMap<uuid::Uuid, MeshBufferStorage>,
mesh_han: &ResHandle<Mesh>,
) {
let mesh_uuid = mesh_han.uuid();
if let (Some(mesh), Some(buffers)) = (mesh_han.data_ref(), 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 = 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, material_buffers, mesh))]
fn create_mesh_buffers(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
material_buffers: &mut RenderAssets<Arc<GpuMaterial>>,
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 = material_buffers.entry(material.uuid()).or_insert_with(|| {
debug!(
uuid = material.uuid().to_string(),
"Sending material to gpu"
);
Arc::new(GpuMaterial::from_resource(
device,
queue,
&self.material_bgl,
&material_ref,
))
});
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_buffers,
material_buffers,
entity_meshes,
mesh,
entity
))]
fn process_mesh(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
mesh_buffers: &mut RenderAssets<MeshBufferStorage>,
material_buffers: &mut RenderAssets<Arc<GpuMaterial>>,
entity_meshes: &mut FxHashMap<Entity, uuid::Uuid>,
entity: Entity,
mesh: &Mesh,
mesh_uuid: Uuid,
) -> bool {
#[allow(clippy::map_entry)]
if !mesh_buffers.contains_key(&mesh_uuid) {
// create the mesh's buffers
let buffers = self.create_mesh_buffers(device, queue, material_buffers, mesh);
mesh_buffers.insert(mesh_uuid, buffers);
entity_meshes.insert(entity, mesh_uuid);
true
} else {
false
}
}
/// If the resource does not exist in the world, add the default
fn try_init_resource<T: ResourceObject + Default>(world: &mut World) {
if !world.has_resource::<T>() {
world.add_resource_default::<T>();
}
}
}
impl Node for MeshPrepNode {
fn desc(
&mut self,
_: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::NodeDesc {
NodeDesc::new(NodeType::Node, None, vec![])
}
fn prepare(
&mut self,
_: &mut crate::render::graph::RenderGraph,
world: &mut lyra_ecs::World,
context: &mut crate::render::graph::RenderGraphContext,
) {
let device = &context.device;
let queue = &context.queue;
let render_limits = device.limits();
let last_epoch = world.current_tick();
let mut alive_entities = HashSet::new();
{
// prepare the world with resources
if !world.has_resource::<TransformBuffers>() {
let buffers = TransformBuffers::new(device);
world.add_resource(buffers);
}
Self::try_init_resource::<RenderMeshes>(world);
Self::try_init_resource::<RenderAssets<MeshBufferStorage>>(world);
Self::try_init_resource::<RenderAssets<Arc<GpuMaterial>>>(world);
Self::try_init_resource::<FxHashMap<Entity, uuid::Uuid>>(world);
let mut render_meshes = world.get_resource_mut::<RenderMeshes>();
render_meshes.clear();
}
let view = world.view_iter::<(
Entities,
&Transform,
TickOf<Transform>,
Or<(&MeshHandle, TickOf<MeshHandle>), (&SceneHandle, TickOf<SceneHandle>)>,
Option<&mut InterpTransform>,
Res<DeltaTime>,
ResMut<TransformBuffers>,
ResMut<RenderMeshes>,
ResMut<RenderAssets<MeshBufferStorage>>,
ResMut<RenderAssets<Arc<GpuMaterial>>>,
ResMut<FxHashMap<Entity, uuid::Uuid>>,
)>();
// 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,
mut transforms,
mut render_meshes,
mut mesh_buffers,
mut material_buffers,
mut entity_meshes,
) 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
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,
&mut mesh_buffers,
&mut material_buffers,
&mut entity_meshes,
entity,
&mesh,
mesh_han.uuid(),
) && mesh_epoch == last_epoch
{
self.check_mesh_buffers(device, queue, &mut mesh_buffers, &mesh_han);
}
let group = TransformGroup::EntityRes(entity, mesh_han.uuid());
let transform_id = transforms.update_or_push(
device,
queue,
&render_limits,
group,
interp_transform.calculate_mat4(),
glam::Mat3::from_quat(interp_transform.rotation),
);
let material = mesh.material.as_ref().unwrap().data_ref().unwrap();
let shader = material.shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id);
render_meshes.push_back(job);
}
}
if let Some((scene_han, scene_epoch)) = scene_pair {
if let Some(scene) = scene_han.data_ref() {
if scene_epoch == last_epoch {
let view = scene.world().view::<(
Entities,
&mut WorldTransform,
&Transform,
Not<Has<RelationOriginComponent<ChildOf>>>,
)>();
lyra_scene::system_update_world_transforms(scene.world(), view).unwrap();
}
for (mesh_han, pos) in
scene.world().view_iter::<(&MeshHandle, &WorldTransform)>()
{
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,
&mut mesh_buffers,
&mut material_buffers,
&mut entity_meshes,
entity,
&mesh,
mesh_han.uuid(),
) && scene_epoch == last_epoch
{
self.check_mesh_buffers(
device,
queue,
&mut mesh_buffers,
&mesh_han,
);
}
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);
render_meshes.push_back(job);
}
}
}
}
}
for (en, interp) in component_queue {
world.insert(en, interp);
}
let mut transforms = world.get_resource_mut::<TransformBuffers>();
transforms.send_to_gpu(queue);
}
fn execute(
&mut self,
_: &mut crate::render::graph::RenderGraph,
_: &crate::render::graph::NodeDesc,
_: &mut crate::render::graph::RenderGraphContext,
) {
}
}
#[repr(transparent)]
pub struct RenderAssets<T>(FxHashMap<Uuid, T>);
impl<T> Deref for RenderAssets<T> {
type Target = FxHashMap<Uuid, T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for RenderAssets<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> Default for RenderAssets<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> RenderAssets<T> {
pub fn new() -> Self {
Self::default()
}
}
#[allow(dead_code)]
pub struct GpuMaterial {
pub bind_group: Arc<wgpu::BindGroup>,
bind_group_layout: Arc<wgpu::BindGroupLayout>,
material_properties_buffer: wgpu::Buffer,
diffuse_texture: wgpu::Texture,
diffuse_texture_sampler: wgpu::Sampler,
/* specular_texture: wgpu::Texture,
specular_texture_sampler: wgpu::Sampler, */
}
impl GpuMaterial {
fn create_bind_group_layout(device: &wgpu::Device) -> Arc<wgpu::BindGroupLayout> {
Arc::new(
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("bgl_material"),
entries: &[
// material properties
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None, /* Some(
NonZeroU64::new(mem::size_of::<MaterialPropertiesUniform>() as _)
.unwrap(),
) */
},
count: None,
},
// diffuse texture
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
// diffuse texture sampler
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
// specular texture
/* wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
// specular texture sampler
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
}, */
],
}),
)
}
fn texture_desc(label: &str, size: UVec2) -> wgpu::TextureDescriptor {
//debug!("Texture desc size: {:?}", size);
wgpu::TextureDescriptor {
label: Some(label),
size: wgpu::Extent3d {
width: size.x,
height: size.y,
depth_or_array_layers: 1,
},
mip_level_count: 1, // TODO
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
}
}
fn write_texture(queue: &wgpu::Queue, texture: &wgpu::Texture, img: &lyra_resource::Image) {
let dim = img.dimensions();
//debug!("Write texture size: {:?}", dim);
queue.write_texture(
wgpu::ImageCopyTexture {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&img.to_rgba8(),
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: std::num::NonZeroU32::new(4 * dim.0),
rows_per_image: std::num::NonZeroU32::new(dim.1),
},
wgpu::Extent3d {
width: dim.0,
height: dim.1,
depth_or_array_layers: 1,
},
);
}
fn from_resource(
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &Arc<wgpu::BindGroupLayout>,
mat: &lyra_resource::gltf::Material,
) -> Self {
//let specular = mat.specular.as_ref().unwrap_or_default();
//let specular_
let prop = MaterialPropertiesUniform {
ambient: Vec3::ONE,
_padding1: 0,
diffuse: Vec3::ONE,
shininess: 32.0,
specular_factor: 0.0,
_padding2: [0; 3],
specular_color_factor: Vec3::ZERO,
_padding3: 0,
};
let properties_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("buffer_material"),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
contents: bytemuck::bytes_of(&prop),
});
let diffuse_tex = mat.base_color_texture.as_ref().unwrap();
let diffuse_tex = diffuse_tex.data_ref().unwrap();
let diffuse_tex_img = diffuse_tex.image.data_ref().unwrap();
let diffuse_tex_dim = diffuse_tex_img.dimensions();
let diffuse_texture = device.create_texture(&Self::texture_desc(
"material_diffuse_texture",
UVec2::new(diffuse_tex_dim.0, diffuse_tex_dim.1),
));
let diffuse_tex_view = diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler_desc = match &diffuse_tex.sampler {
Some(sampler) => {
let magf = res_filter_to_wgpu(
sampler
.mag_filter
.unwrap_or(lyra_resource::FilterMode::Linear),
);
let minf = res_filter_to_wgpu(
sampler
.min_filter
.unwrap_or(lyra_resource::FilterMode::Nearest),
);
let mipf = res_filter_to_wgpu(
sampler
.mipmap_filter
.unwrap_or(lyra_resource::FilterMode::Nearest),
);
let wrap_u = res_wrap_to_wgpu(sampler.wrap_u);
let wrap_v = res_wrap_to_wgpu(sampler.wrap_v);
let wrap_w = res_wrap_to_wgpu(sampler.wrap_w);
wgpu::SamplerDescriptor {
address_mode_u: wrap_u,
address_mode_v: wrap_v,
address_mode_w: wrap_w,
mag_filter: magf,
min_filter: minf,
mipmap_filter: mipf,
..Default::default()
}
}
None => wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
},
};
let diffuse_sampler = device.create_sampler(&sampler_desc);
Self::write_texture(queue, &diffuse_texture, &diffuse_tex_img);
debug!("TODO: specular texture");
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("bg_material"),
layout: &layout,
entries: &[
// material properties
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &properties_buffer,
offset: 0,
size: None,
}),
},
// diffuse texture
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&diffuse_tex_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
},
// TODO: specular textures
],
});
Self {
bind_group: Arc::new(bg),
bind_group_layout: layout.clone(),
material_properties_buffer: properties_buffer,
diffuse_texture,
diffuse_texture_sampler: diffuse_sampler,
}
}
}
/// Uniform for MaterialProperties in a shader
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct MaterialPropertiesUniform {
ambient: glam::Vec3,
_padding1: u32,
diffuse: glam::Vec3,
shininess: f32,
specular_factor: f32,
_padding2: [u32; 3],
specular_color_factor: glam::Vec3,
_padding3: u32,
}
#[derive(Default)]
pub struct RenderMeshes(VecDeque<RenderJob>);
impl Deref for RenderMeshes {
type Target = VecDeque<RenderJob>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for RenderMeshes {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

File diff suppressed because it is too large Load Diff

View File

@ -21,3 +21,9 @@ pub use tint::*;
mod fxaa; mod fxaa;
pub use fxaa::*; pub use fxaa::*;
mod shadows;
pub use shadows::*;
mod mesh_prepare;
pub use mesh_prepare::*;

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, rc::Rc}; use std::{collections::HashMap, rc::Rc, sync::Arc};
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
@ -13,7 +13,7 @@ pub struct TintPassLabel;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TintPass { pub struct TintPass {
target_sampler: Option<wgpu::Sampler>, target_sampler: Option<wgpu::Sampler>,
bgl: Option<Rc<wgpu::BindGroupLayout>>, bgl: Option<Arc<wgpu::BindGroupLayout>>,
/// Store bind groups for the input textures. /// Store bind groups for the input textures.
/// The texture may change due to resizes, or changes to the view target chain /// The texture may change due to resizes, or changes to the view target chain
/// from other nodes. /// from other nodes.
@ -54,7 +54,7 @@ impl Node for TintPass {
}, },
], ],
}); });
let bgl = Rc::new(bgl); let bgl = Arc::new(bgl);
self.bgl = Some(bgl.clone()); self.bgl = Some(bgl.clone());
self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default())); self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default()));

View File

@ -1,17 +1,24 @@
pub mod point;
pub mod directional; pub mod directional;
pub mod point;
pub mod spotlight; pub mod spotlight;
use lyra_ecs::{Entity, Tick, World}; use lyra_ecs::{Entity, Tick, World};
pub use point::*; pub use point::*;
pub use spotlight::*; pub use spotlight::*;
use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem, rc::Rc}; use std::{
collections::{HashMap, VecDeque},
marker::PhantomData,
mem,
sync::Arc,
};
use crate::math::Transform; use crate::math::Transform;
use self::directional::DirectionalLight; use self::directional::DirectionalLight;
use super::graph::LightShadowMapId;
const MAX_LIGHT_COUNT: usize = 16; const MAX_LIGHT_COUNT: usize = 16;
/// A struct that stores a list of lights in a wgpu::Buffer. /// A struct that stores a list of lights in a wgpu::Buffer.
@ -47,15 +54,27 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
} }
/// Update an existing light in the light buffer. /// Update an existing light in the light buffer.
pub fn update_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) { pub fn update_light(
let buffer_idx = *self.used_indexes.get(&entity) &mut self,
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
entity: Entity,
light: U,
) {
let buffer_idx = *self
.used_indexes
.get(&entity)
.expect("Entity for Light is not in buffer!"); .expect("Entity for Light is not in buffer!");
lights_buffer[buffer_idx] = light; lights_buffer[buffer_idx] = light;
} }
/// Add a new light to the light buffer. /// Add a new light to the light buffer.
pub fn add_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) { pub fn add_light(
&mut self,
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
entity: Entity,
light: U,
) {
let buffer_idx = match self.dead_indexes.pop_front() { let buffer_idx = match self.dead_indexes.pop_front() {
Some(i) => i, Some(i) => i,
None => { None => {
@ -67,7 +86,7 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
assert!(self.buffer_count <= self.max_count); assert!(self.buffer_count <= self.max_count);
i i
}, }
}; };
self.used_indexes.insert(entity, buffer_idx); self.used_indexes.insert(entity, buffer_idx);
@ -75,7 +94,12 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
} }
/// Update, or add a new caster, to the light buffer. /// Update, or add a new caster, to the light buffer.
pub fn update_or_add(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) { pub fn update_or_add(
&mut self,
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
entity: Entity,
light: U,
) {
if self.used_indexes.contains_key(&entity) { if self.used_indexes.contains_key(&entity) {
self.update_light(lights_buffer, entity, light); self.update_light(lights_buffer, entity, light);
} else { } else {
@ -84,7 +108,11 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
} }
/// Remove a caster from the buffer, returns true if it was removed. /// Remove a caster from the buffer, returns true if it was removed.
pub fn remove_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity) -> bool { pub fn remove_light(
&mut self,
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
entity: Entity,
) -> bool {
if let Some(removed_idx) = self.used_indexes.remove(&entity) { if let Some(removed_idx) = self.used_indexes.remove(&entity) {
self.dead_indexes.push_back(removed_idx); self.dead_indexes.push_back(removed_idx);
//self.current_count -= 1; //self.current_count -= 1;
@ -98,9 +126,9 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
} }
pub(crate) struct LightUniformBuffers { pub(crate) struct LightUniformBuffers {
pub buffer: Rc<wgpu::Buffer>, pub buffer: Arc<wgpu::Buffer>,
pub bind_group: Rc<wgpu::BindGroup>, pub bind_group: Arc<wgpu::BindGroup>,
pub bind_group_layout: Rc<wgpu::BindGroupLayout>, pub bind_group_layout: Arc<wgpu::BindGroupLayout>,
max_light_count: u64, max_light_count: u64,
} }
@ -110,54 +138,44 @@ impl LightUniformBuffers {
// TODO: ensure we dont write over this limit // TODO: ensure we dont write over this limit
let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2; let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2;
let buffer = device.create_buffer( let buffer = device.create_buffer(&wgpu::BufferDescriptor {
&wgpu::BufferDescriptor { label: Some("UBO_Lights"),
label: Some("UBO_Lights"), usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, size: max_buffer_sizes,
size: max_buffer_sizes, mapped_at_creation: false,
mapped_at_creation: false, });
}
);
let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry { binding: 0,
binding: 0, visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE,
visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer {
ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true },
ty: wgpu::BufferBindingType::Storage { has_dynamic_offset: false,
read_only: true min_binding_size: None,
},
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}, },
], count: None,
}],
label: Some("BGL_Lights"), label: Some("BGL_Lights"),
}); });
let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bindgroup_layout, layout: &bindgroup_layout,
entries: &[ entries: &[wgpu::BindGroupEntry {
wgpu::BindGroupEntry { binding: 0,
binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
resource: wgpu::BindingResource::Buffer( buffer: &buffer,
wgpu::BufferBinding { offset: 0,
buffer: &buffer, size: None, // use the full buffer
offset: 0, }),
size: None, // use the full buffer }],
}
)
},
],
label: Some("BG_Lights"), label: Some("BG_Lights"),
}); });
Self { Self {
buffer: Rc::new(buffer), buffer: Arc::new(buffer),
bind_group: Rc::new(bindgroup), bind_group: Arc::new(bindgroup),
bind_group_layout: Rc::new(bindgroup_layout), bind_group_layout: Arc::new(bindgroup_layout),
max_light_count: max_buffer_sizes / mem::size_of::<LightUniform>() as u64, max_light_count: max_buffer_sizes / mem::size_of::<LightUniform>() as u64,
} }
} }
@ -166,27 +184,43 @@ impl LightUniformBuffers {
let _ = world_tick; let _ = world_tick;
let mut lights = vec![]; let mut lights = vec![];
for (point_light, transform) in world.view_iter::<(&PointLight, &Transform)>() { for (point_light, transform, shadow_map_id) in
let uniform = LightUniform::from_point_light_bundle(&point_light, &transform); world.view_iter::<(&PointLight, &Transform, Option<&LightShadowMapId>)>()
{
let shadow_map_id = shadow_map_id.map(|m| m.clone());
let uniform =
LightUniform::from_point_light_bundle(&point_light, &transform, shadow_map_id);
lights.push(uniform); lights.push(uniform);
} }
for (spot_light, transform) in world.view_iter::<(&SpotLight, &Transform)>() { for (spot_light, transform, shadow_map_id) in
let uniform = LightUniform::from_spot_light_bundle(&spot_light, &transform); world.view_iter::<(&SpotLight, &Transform, Option<&LightShadowMapId>)>()
{
let shadow_map_id = shadow_map_id.map(|m| m.clone());
let uniform =
LightUniform::from_spot_light_bundle(&spot_light, &transform, shadow_map_id);
lights.push(uniform); lights.push(uniform);
} }
for (dir_light, transform) in world.view_iter::<(&DirectionalLight, &Transform)>() { for (dir_light, transform, shadow_map_id) in
let uniform = LightUniform::from_directional_bundle(&dir_light, &transform); world.view_iter::<(&DirectionalLight, &Transform, Option<&LightShadowMapId>)>()
{
let shadow_map_id = shadow_map_id.map(|m| m.clone());
let uniform =
LightUniform::from_directional_bundle(&dir_light, &transform, shadow_map_id);
lights.push(uniform); lights.push(uniform);
} }
assert!(lights.len() < self.max_light_count as _); // ensure we dont overwrite the buffer assert!(lights.len() < self.max_light_count as usize); // ensure we dont overwrite the buffer
// write the amount of lights to the buffer, and right after that the list of lights. // write the amount of lights to the buffer, and right after that the list of lights.
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[lights.len()])); queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[lights.len()]));
// the size of u32 is multiplied by 4 because of gpu alignment requirements // the size of u32 is multiplied by 4 because of gpu alignment requirements
queue.write_buffer(&self.buffer, mem::size_of::<u32>() as u64 * 4, bytemuck::cast_slice(lights.as_slice())); queue.write_buffer(
&self.buffer,
mem::size_of::<u32>() as u64 * 4,
bytemuck::cast_slice(lights.as_slice()),
);
} }
} }
@ -209,17 +243,22 @@ pub(crate) struct LightUniform {
pub color: glam::Vec3, pub color: glam::Vec3,
// no padding is needed here since range acts as the padding // no padding is needed here since range acts as the padding
// that would usually be needed for the vec3 // that would usually be needed for the vec3
pub range: f32, pub range: f32,
pub intensity: f32, pub intensity: f32,
pub smoothness: f32, pub smoothness: f32,
pub spot_cutoff_rad: f32, pub spot_cutoff_rad: f32,
pub spot_outer_cutoff_rad: f32, pub spot_outer_cutoff_rad: f32,
pub light_shadow_uniform_index: [i32; 6],
_padding: [u32; 2],
} }
impl LightUniform { impl LightUniform {
pub fn from_point_light_bundle(light: &PointLight, transform: &Transform) -> Self { pub fn from_point_light_bundle(
light: &PointLight,
transform: &Transform,
map_id: Option<LightShadowMapId>,
) -> Self {
Self { Self {
light_type: LightType::Point as u32, light_type: LightType::Point as u32,
enabled: light.enabled as u32, enabled: light.enabled as u32,
@ -233,11 +272,27 @@ impl LightUniform {
spot_cutoff_rad: 0.0, spot_cutoff_rad: 0.0,
spot_outer_cutoff_rad: 0.0, spot_outer_cutoff_rad: 0.0,
light_shadow_uniform_index: map_id
.map(|m| {
[
m.uniform_index(0) as i32,
m.uniform_index(1) as i32,
m.uniform_index(2) as i32,
m.uniform_index(3) as i32,
m.uniform_index(4) as i32,
m.uniform_index(5) as i32,
]
})
.unwrap_or([-1; 6]),
_padding: [0; 2],
} }
} }
pub fn from_directional_bundle(light: &DirectionalLight, transform: &Transform) -> Self { pub fn from_directional_bundle(
light: &DirectionalLight,
transform: &Transform,
map_id: Option<LightShadowMapId>,
) -> Self {
Self { Self {
light_type: LightType::Directional as u32, light_type: LightType::Directional as u32,
enabled: light.enabled as u32, enabled: light.enabled as u32,
@ -251,11 +306,28 @@ impl LightUniform {
spot_cutoff_rad: 0.0, spot_cutoff_rad: 0.0,
spot_outer_cutoff_rad: 0.0, spot_outer_cutoff_rad: 0.0,
light_shadow_uniform_index: map_id
.map(|m| {
[
m.uniform_index(0) as i32,
m.uniform_index(1) as i32,
m.uniform_index(2) as i32,
m.uniform_index(3) as i32,
m.uniform_index(4) as i32,
m.uniform_index(5) as i32,
]
})
.unwrap_or([-1; 6]),
_padding: [0; 2],
} }
} }
// Create the SpotLightUniform from an ECS bundle // Create the SpotLightUniform from an ECS bundle
pub fn from_spot_light_bundle(light: &SpotLight, transform: &Transform) -> Self { pub fn from_spot_light_bundle(
light: &SpotLight,
transform: &Transform,
map_id: Option<LightShadowMapId>,
) -> Self {
Self { Self {
light_type: LightType::Spotlight as u32, light_type: LightType::Spotlight as u32,
enabled: light.enabled as u32, enabled: light.enabled as u32,
@ -269,7 +341,19 @@ impl LightUniform {
spot_cutoff_rad: light.cutoff.to_radians(), spot_cutoff_rad: light.cutoff.to_radians(),
spot_outer_cutoff_rad: light.outer_cutoff.to_radians(), spot_outer_cutoff_rad: light.outer_cutoff.to_radians(),
light_shadow_uniform_index: map_id
.map(|m| {
[
m.uniform_index(0) as i32,
m.uniform_index(1) as i32,
m.uniform_index(2) as i32,
m.uniform_index(3) as i32,
m.uniform_index(4) as i32,
m.uniform_index(5) as i32,
]
})
.unwrap_or([-1; 6]),
_padding: [0; 2],
} }
} }
} }

View File

@ -1,9 +1,10 @@
use std::rc::Rc; use std::sync::Arc;
use lyra_resource::{ResHandle, Texture}; use lyra_resource::{ResHandle, Texture};
use super::texture::RenderTexture; use super::texture::RenderTexture;
#[derive(Default)]
pub struct MaterialSpecular { pub struct MaterialSpecular {
pub factor: f32, pub factor: f32,
pub color_factor: glam::Vec3, pub color_factor: glam::Vec3,
@ -11,7 +12,7 @@ pub struct MaterialSpecular {
pub color_texture: Option<RenderTexture>, pub color_texture: Option<RenderTexture>,
} }
fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Rc<wgpu::BindGroupLayout>, i: &Option<ResHandle<Texture>>) -> Option<RenderTexture> { fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Arc<wgpu::BindGroupLayout>, i: &Option<ResHandle<Texture>>) -> Option<RenderTexture> {
if let Some(tex) = i { if let Some(tex) = i {
RenderTexture::from_resource(device, queue, bg_layout.clone(), tex, None).ok() RenderTexture::from_resource(device, queue, bg_layout.clone(), tex, None).ok()
} else { } else {
@ -20,7 +21,7 @@ fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Rc<
} }
impl MaterialSpecular { impl MaterialSpecular {
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Specular) -> Self { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Specular) -> Self {
let tex = texture_to_render(device, queue, &bg_layout, &value.texture); let tex = texture_to_render(device, queue, &bg_layout, &value.texture);
let color_tex = texture_to_render(device, queue, &bg_layout, &value.color_texture); let color_tex = texture_to_render(device, queue, &bg_layout, &value.color_texture);
@ -45,7 +46,7 @@ pub struct Material {
} }
impl Material { impl Material {
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Material) -> Self { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Material) -> Self {
let diffuse_texture = texture_to_render(device, queue, &bg_layout, &value.base_color_texture); let diffuse_texture = texture_to_render(device, queue, &bg_layout, &value.base_color_texture);
let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s)); let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s));
@ -57,7 +58,7 @@ impl Material {
//diffuse: glam::Vec3::new(value.base_color.x, value.base_color.y, value.base_color.z), //diffuse: glam::Vec3::new(value.base_color.x, value.base_color.y, value.base_color.z),
//diffuse: glam::Vec3::new(1.0, 0.5, 0.31), //diffuse: glam::Vec3::new(1.0, 0.5, 0.31),
//specular: glam::Vec3::new(0.5, 0.5, 0.5), //specular: glam::Vec3::new(0.5, 0.5, 0.5),
ambient: glam::Vec3::new(1.0, 1.0, 1.0), ambient: glam::Vec3::new(1.0, 1.0, 1.0) * 0.5,
diffuse: glam::Vec3::new(1.0, 1.0, 1.0), diffuse: glam::Vec3::new(1.0, 1.0, 1.0),
shininess: 32.0, shininess: 32.0,

View File

@ -15,3 +15,9 @@ pub mod light;
//pub mod light_cull_compute; //pub mod light_cull_compute;
pub mod avec; pub mod avec;
pub mod graph; pub mod graph;
mod texture_atlas;
pub use texture_atlas::*;
mod slot_buffer;
pub use slot_buffer::*;

View File

@ -1,4 +1,4 @@
use std::{num::NonZeroU32, rc::Rc}; use std::{num::NonZeroU32, sync::Arc};
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
@ -23,11 +23,11 @@ impl RenderBuffer {
pub struct BindGroupPair { pub struct BindGroupPair {
pub bindgroup: wgpu::BindGroup, pub bindgroup: wgpu::BindGroup,
pub layout: Rc<wgpu::BindGroupLayout>, pub layout: Arc<wgpu::BindGroupLayout>,
} }
impl BindGroupPair { impl BindGroupPair {
pub fn create_bind_group(device: &wgpu::Device, layout: Rc<wgpu::BindGroupLayout>, entries: &[wgpu::BindGroupEntry<'_>]) -> Self { pub fn create_bind_group(device: &wgpu::Device, layout: Arc<wgpu::BindGroupLayout>, entries: &[wgpu::BindGroupEntry<'_>]) -> Self {
let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &layout, layout: &layout,
entries, entries,
@ -43,7 +43,7 @@ impl BindGroupPair {
pub fn new(bindgroup: wgpu::BindGroup, layout: wgpu::BindGroupLayout) -> Self { pub fn new(bindgroup: wgpu::BindGroup, layout: wgpu::BindGroupLayout) -> Self {
Self { Self {
bindgroup, bindgroup,
layout: Rc::new(layout), layout: Arc::new(layout),
} }
} }
} }
@ -136,7 +136,7 @@ impl BufferWrapper {
} }
/// Take the bind group layout, the bind group, and the buffer out of the wrapper. /// 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) { pub fn parts(self) -> (Option<Arc<wgpu::BindGroupLayout>>, Option<wgpu::BindGroup>, wgpu::Buffer) {
if let Some(pair) = self.bindgroup_pair { if let Some(pair) = self.bindgroup_pair {
(Some(pair.layout), Some(pair.bindgroup), self.inner_buf) (Some(pair.layout), Some(pair.bindgroup), self.inner_buf)
} else { } else {
@ -297,7 +297,7 @@ impl BufferWrapperBuilder {
BindGroupPair { BindGroupPair {
bindgroup: bg, bindgroup: bg,
layout: Rc::new(bg_layout), layout: Arc::new(bg_layout),
} }
} }
}; };
@ -308,7 +308,7 @@ impl BufferWrapperBuilder {
len: Some(self.count.unwrap_or_default() as usize), 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) (Arc::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 { pub fn finish(self, device: &wgpu::Device) -> BufferWrapper {
@ -316,7 +316,7 @@ impl BufferWrapperBuilder {
BufferWrapper { BufferWrapper {
bindgroup_pair: Some(BindGroupPair { bindgroup_pair: Some(BindGroupPair {
layout: Rc::new(bgl), layout: Arc::new(bgl),
bindgroup: bg bindgroup: bg
}), }),
inner_buf: buff, inner_buf: buff,

View File

@ -9,7 +9,7 @@ use lyra_game_derive::RenderGraphLabel;
use tracing::{debug, instrument, warn}; use tracing::{debug, instrument, warn};
use winit::window::Window; use winit::window::Window;
use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, SubGraphNode, ViewTarget}; use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshPrepNode, MeshPrepNodeLabel, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, ShadowMapsPass, ShadowMapsPassLabel, SubGraphNode, ViewTarget};
use super::graph::RenderGraph; use super::graph::RenderGraph;
use super::{resource::RenderPipeline, render_job::RenderJob}; use super::{resource::RenderPipeline, render_job::RenderJob};
@ -90,7 +90,7 @@ impl BasicRenderer {
let (device, queue) = adapter.request_device( let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
// WebGL does not support all wgpu features. // WebGL does not support all wgpu features.
// Not sure if the engine will ever completely support WASM, // Not sure if the engine will ever completely support WASM,
// but its here just in case // but its here just in case
@ -147,9 +147,21 @@ impl BasicRenderer {
forward_plus_graph.add_node(LightCullComputePassLabel, LightCullComputePass::new(size)); forward_plus_graph.add_node(LightCullComputePassLabel, LightCullComputePass::new(size));
debug!("Adding mesh pass"); debug!("Adding mesh pass");
forward_plus_graph.add_node(MeshesPassLabel, MeshPass::new()); forward_plus_graph.add_node(ShadowMapsPassLabel, ShadowMapsPass::new(&device));
let mesh_prep = MeshPrepNode::new(&device);
let material_bgl = mesh_prep.material_bgl.clone();
forward_plus_graph.add_node(MeshPrepNodeLabel, mesh_prep);
forward_plus_graph.add_node(MeshesPassLabel, MeshPass::new(material_bgl));
forward_plus_graph.add_edge(LightBasePassLabel, LightCullComputePassLabel); forward_plus_graph.add_edge(LightBasePassLabel, LightCullComputePassLabel);
forward_plus_graph.add_edge(LightCullComputePassLabel, MeshesPassLabel);
forward_plus_graph.add_edge(MeshPrepNodeLabel, MeshesPassLabel);
// run ShadowMapsPass after MeshPrep and before MeshesPass
forward_plus_graph.add_edge(MeshPrepNodeLabel, ShadowMapsPassLabel);
forward_plus_graph.add_edge(ShadowMapsPassLabel, MeshesPassLabel);
main_graph.add_sub_graph(TestSubGraphLabel, forward_plus_graph); main_graph.add_sub_graph(TestSubGraphLabel, forward_plus_graph);
main_graph.add_node(TestSubGraphLabel, SubGraphNode::new(TestSubGraphLabel, main_graph.add_node(TestSubGraphLabel, SubGraphNode::new(TestSubGraphLabel,

View File

@ -1,4 +1,4 @@
use std::{ops::Deref, rc::Rc}; use std::{ops::Deref, rc::Rc, sync::Arc};
use wgpu::PipelineLayout; use wgpu::PipelineLayout;
@ -7,7 +7,7 @@ use super::Shader;
//#[derive(Debug, Clone)] //#[derive(Debug, Clone)]
pub struct ComputePipelineDescriptor { pub struct ComputePipelineDescriptor {
pub label: Option<String>, pub label: Option<String>,
pub layouts: Vec<Rc<wgpu::BindGroupLayout>>, pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
pub push_constant_ranges: Vec<wgpu::PushConstantRange>, pub push_constant_ranges: Vec<wgpu::PushConstantRange>,
// TODO: make this a ResHandle<Shader> // TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage. /// The compiled shader module for the stage.

View File

@ -1,4 +1,4 @@
use std::{num::NonZeroU32, ops::Deref, rc::Rc}; use std::{num::NonZeroU32, ops::Deref, sync::Arc};
use wgpu::PipelineLayout; use wgpu::PipelineLayout;
@ -7,7 +7,7 @@ use super::{FragmentState, VertexState};
//#[derive(Debug, Clone)] //#[derive(Debug, Clone)]
pub struct RenderPipelineDescriptor { pub struct RenderPipelineDescriptor {
pub label: Option<String>, pub label: Option<String>,
pub layouts: Vec<Rc<wgpu::BindGroupLayout>>, pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
pub push_constant_ranges: Vec<wgpu::PushConstantRange>, pub push_constant_ranges: Vec<wgpu::PushConstantRange>,
pub vertex: VertexState, pub vertex: VertexState,
pub fragment: Option<FragmentState>, pub fragment: Option<FragmentState>,
@ -87,7 +87,7 @@ impl RenderPipeline {
// an Rc was used here so that this shader could be reused by the fragment stage if // 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 // they share the same shader. I tried to do it without an Rc but couldn't get past
// the borrow checker // the borrow checker
let vrtx_shad = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { let vrtx_shad = Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: desc.vertex.module.label.as_deref(), label: desc.vertex.module.label.as_deref(),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
&desc.vertex.module.source, &desc.vertex.module.source,
@ -103,7 +103,7 @@ impl RenderPipeline {
if f.module == desc.vertex.module { if f.module == desc.vertex.module {
vrtx_shad.clone() vrtx_shad.clone()
} else { } else {
Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: f.module.label.as_deref(), label: f.module.label.as_deref(),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)),
})) }))

View File

@ -19,6 +19,16 @@ struct VertexOutput {
@location(0) tex_coords: vec2<f32>, @location(0) tex_coords: vec2<f32>,
@location(1) world_position: vec3<f32>, @location(1) world_position: vec3<f32>,
@location(2) world_normal: vec3<f32>, @location(2) world_normal: vec3<f32>,
@location(3) frag_pos_light_space: vec4<f32>,
}
struct TextureAtlasFrame {
/*offset: vec2<u32>,
size: vec2<u32>,*/
x: u32,
y: u32,
width: u32,
height: u32,
} }
struct TransformData { struct TransformData {
@ -48,6 +58,7 @@ struct Light {
spot_cutoff: f32, spot_cutoff: f32,
spot_outer_cutoff: f32, spot_outer_cutoff: f32,
light_shadow_uniform_index: array<i32, 6>,
}; };
struct Lights { struct Lights {
@ -70,16 +81,16 @@ fn vs_main(
) -> VertexOutput { ) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
var world_position: vec4<f32> = u_model_transform_data.transform * vec4<f32>(model.position, 1.0);
out.world_position = world_position.xyz;
out.tex_coords = model.tex_coords; out.tex_coords = model.tex_coords;
out.clip_position = u_camera.view_projection * u_model_transform_data.transform * vec4<f32>(model.position, 1.0); out.clip_position = u_camera.view_projection * world_position;
// the normal mat is actually only a mat3x3, but there's a bug in wgpu: https://github.com/gfx-rs/wgpu-rs/issues/36 // the normal mat is actually only a mat3x3, but there's a bug in wgpu: https://github.com/gfx-rs/wgpu-rs/issues/36
let normal_mat4 = u_model_transform_data.normal_matrix; let normal_mat4 = u_model_transform_data.normal_matrix;
let normal_mat = mat3x3(normal_mat4[0].xyz, normal_mat4[1].xyz, normal_mat4[2].xyz); let normal_mat = mat3x3(normal_mat4[0].xyz, normal_mat4[1].xyz, normal_mat4[2].xyz);
out.world_normal = normalize(normal_mat * model.normal, ); out.world_normal = normalize(normal_mat * model.normal);
var world_position: vec4<f32> = u_model_transform_data.transform * vec4<f32>(model.position, 1.0);
out.world_position = world_position.xyz;
return out; return out;
} }
@ -87,29 +98,60 @@ fn vs_main(
// Fragment shader // Fragment shader
struct Material { struct Material {
ambient: vec4<f32>, ambient: vec3<f32>,
diffuse: vec4<f32>, diffuse: vec3<f32>,
specular: vec4<f32>,
shininess: f32, shininess: f32,
specular_factor: f32,
specular_color: vec3<f32>,
} }
@group(0) @binding(0) @group(0) @binding(0)
var t_diffuse: texture_2d<f32>; var<uniform> u_material: Material;
@group(0) @binding(1) @group(0) @binding(1)
var t_diffuse: texture_2d<f32>;
@group(0) @binding(2)
var s_diffuse: sampler; var s_diffuse: sampler;
struct LightShadowMapUniform {
light_space_matrix: mat4x4<f32>,
atlas_frame: TextureAtlasFrame,
near_plane: f32,
far_plane: f32,
light_size_uv: f32,
light_pos: vec3<f32>,
/// boolean casted as u32
has_shadow_settings: u32,
pcf_samples_num: u32,
pcss_blocker_search_samples: u32,
constant_depth_bias: f32,
}
struct ShadowSettingsUniform {
pcf_samples_num: u32,
pcss_blocker_search_samples: u32,
}
@group(4) @binding(0) @group(4) @binding(0)
var<uniform> u_material: Material; var<storage, read_write> u_light_indices: array<u32>;
@group(4) @binding(1)
var t_light_grid: texture_storage_2d<rg32uint, read_write>; // rg32uint = vec2<u32>
@group(5) @binding(0) @group(5) @binding(0)
var t_specular: texture_2d<f32>; var t_shadow_maps_atlas: texture_depth_2d;
@group(5) @binding(1) @group(5) @binding(1)
var s_specular: sampler; var s_shadow_maps_atlas: sampler;
@group(5) @binding(2)
@group(6) @binding(0) var s_shadow_maps_atlas_compare: sampler_comparison;
var<storage, read_write> u_light_indices: array<u32>; @group(5) @binding(3)
@group(6) @binding(1) var<uniform> u_shadow_settings: ShadowSettingsUniform;
var t_light_grid: texture_storage_2d<rg32uint, read_write>; // vec2<u32> @group(5) @binding(4)
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
@group(5) @binding(5)
var<storage, read> u_pcf_poisson_disc: array<vec2<f32>>;
@group(5) @binding(6)
var<storage, read> u_pcf_poisson_disc_3d: array<vec3<f32>>;
@group(5) @binding(7)
var<storage, read> u_pcss_poisson_disc: array<vec2<f32>>;
@fragment @fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
@ -118,7 +160,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
} }
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords); let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
let specular_color: vec3<f32> = textureSample(t_specular, s_specular, in.tex_coords).xyz; let specular_color: vec3<f32> = vec3<f32>(0.0); //textureSample(t_specular, s_specular, in.tex_coords).xyz;
var light_res = vec3<f32>(0.0); var light_res = vec3<f32>(0.0);
if (object_color.a < ALPHA_CUTOFF) { if (object_color.a < ALPHA_CUTOFF) {
@ -131,16 +173,25 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let light_offset = tile.x; let light_offset = tile.x;
let light_count = tile.y; let light_count = tile.y;
let atlas_dimensions = textureDimensions(t_shadow_maps_atlas);
for (var i = 0u; i < light_count; i++) { for (var i = 0u; i < light_count; i++) {
let light_index = u_light_indices[light_offset + i]; let light_index = u_light_indices[light_offset + i];
let light: Light = u_lights.data[light_index]; let light: Light = u_lights.data[light_index];
let light_dir = normalize(-light.direction);
if (light.light_ty == LIGHT_TY_DIRECTIONAL) { if (light.light_ty == LIGHT_TY_DIRECTIONAL) {
light_res += blinn_phong_dir_light(in.world_position, in.world_normal, light, u_material, specular_color); let shadow_u: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let frag_pos_light_space = shadow_u.light_space_matrix * vec4<f32>(in.world_position, 1.0);
let shadow = calc_shadow_dir_light(in.world_position, in.world_normal, light_dir, light);
light_res += blinn_phong_dir_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
} else if (light.light_ty == LIGHT_TY_POINT) { } else if (light.light_ty == LIGHT_TY_POINT) {
light_res += blinn_phong_point_light(in.world_position, in.world_normal, light, u_material, specular_color); let shadow = calc_shadow_point_light(in.world_position, in.world_normal, light_dir, light, atlas_dimensions);
light_res += blinn_phong_point_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
} else if (light.light_ty == LIGHT_TY_SPOT) { } else if (light.light_ty == LIGHT_TY_SPOT) {
light_res += blinn_phong_spot_light(in.world_position, in.world_normal, light, u_material, specular_color); let shadow = calc_shadow_spot_light(in.world_position, in.world_normal, light_dir, light, atlas_dimensions);
light_res += blinn_phong_spot_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
} }
} }
@ -148,6 +199,355 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(light_object_res, object_color.a); return vec4<f32>(light_object_res, object_color.a);
} }
/// Convert 3d coords for an unwrapped cubemap to 2d coords and a side index of the cube map.
///
/// The `xy` components are the 2d coordinates in the side of the cube, and `z` is the cube
/// map side index.
///
/// Cube map index results:
/// 0 -> UNKNOWN
/// 1 -> right
/// 2 -> left
/// 3 -> top
/// 4 -> bottom
/// 5 -> near
/// 6 -> far
fn coords_to_cube_atlas(tex_coord: vec3<f32>) -> vec3<f32> {
let abs_x = abs(tex_coord.x);
let abs_y = abs(tex_coord.y);
let abs_z = abs(tex_coord.z);
var major_axis: f32 = 0.0;
var cube_idx: i32 = 0;
var res = vec2<f32>(0.0);
// Determine the dominant axis
if (abs_x >= abs_y && abs_x >= abs_z) {
major_axis = tex_coord.x;
if (tex_coord.x > 0.0) {
cube_idx = 1;
res = vec2<f32>(-tex_coord.z, -tex_coord.y);
} else {
cube_idx = 2;
res = vec2<f32>(tex_coord.z, -tex_coord.y);
}
} else if (abs_y >= abs_x && abs_y >= abs_z) {
major_axis = tex_coord.y;
if (tex_coord.y > 0.0) {
cube_idx = 3;
res = vec2<f32>(tex_coord.x, tex_coord.z);
} else {
cube_idx = 4;
res = vec2<f32>(tex_coord.x, -tex_coord.z);
}
} else {
major_axis = tex_coord.z;
if (tex_coord.z > 0.0) {
cube_idx = 5;
res = vec2<f32>(tex_coord.x, -tex_coord.y);
} else {
cube_idx = 6;
res = vec2<f32>(-tex_coord.x, -tex_coord.y);
}
}
res = (res / abs(major_axis) + 1.0) * 0.5;
res.y = 1.0 - res.y;
return vec3<f32>(res, f32(cube_idx));
}
/// Get shadow settings for a light.
/// Returns x as `pcf_samples_num` and y as `pcss_blocker_search_samples`.
fn get_shadow_settings(shadow_u: LightShadowMapUniform) -> vec2<u32> {
if shadow_u.has_shadow_settings == 1u {
return vec2<u32>(shadow_u.pcf_samples_num, shadow_u.pcss_blocker_search_samples);
} else {
return vec2<u32>(u_shadow_settings.pcf_samples_num, u_shadow_settings.pcss_blocker_search_samples);
}
}
fn calc_shadow_dir_light(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light) -> f32 {
let map_data: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let frag_pos_light_space = map_data.light_space_matrix * vec4<f32>(world_pos, 1.0);
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
// for some reason the y component is flipped after transforming
proj_coords.y = -proj_coords.y;
// Remap xy to [0.0, 1.0]
let xy_remapped = proj_coords.xy * 0.5 + 0.5;
// use a bias to avoid shadow acne
let current_depth = proj_coords.z - map_data.constant_depth_bias;
// get settings
let settings = get_shadow_settings(map_data);
let pcf_samples_num = settings.x;
let pcss_blocker_search_samples = settings.y;
var shadow = 0.0;
// hardware 2x2 PCF via camparison sampler
if pcf_samples_num == 2u {
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
}
// PCSS
else if pcf_samples_num > 0u && pcss_blocker_search_samples > 0u {
shadow = pcss_dir_light(xy_remapped, current_depth, map_data);
}
// only PCF
else if pcf_samples_num > 0u {
let texel_size = 1.0 / f32(map_data.atlas_frame.width);
shadow = pcf_dir_light(xy_remapped, current_depth, map_data, texel_size);
}
// no filtering
else {
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0);
shadow = select(1.0, 0.0, current_depth > closest_depth);
}
// dont cast shadows outside the light's far plane
if (proj_coords.z > 1.0) {
shadow = 1.0;
}
// dont cast shadows if the texture coords would go past the shadow maps
if (xy_remapped.x > 1.0 || xy_remapped.x < 0.0 || xy_remapped.y > 1.0 || xy_remapped.y < 0.0) {
shadow = 1.0;
}
return shadow;
}
// Comes from https://developer.download.nvidia.com/whitepapers/2008/PCSS_Integration.pdf
fn search_width(light_near: f32, uv_light_size: f32, receiver_depth: f32) -> f32 {
return uv_light_size * (receiver_depth - light_near) / receiver_depth;
}
/// Convert texture coords to be texture coords of an atlas frame.
///
/// If `safety_offset` is true, the frame will be shrank by a tiny amount to avoid bleeding
/// into adjacent frames from fiiltering.
fn to_atlas_frame_coords(shadow_u: LightShadowMapUniform, coords: vec2<f32>, safety_offset: bool) -> vec2<f32> {
let atlas_dimensions = textureDimensions(t_shadow_maps_atlas);
// get the rect of the frame as a vec4
var region_rect = vec4<f32>(f32(shadow_u.atlas_frame.x), f32(shadow_u.atlas_frame.y),
f32(shadow_u.atlas_frame.width), f32(shadow_u.atlas_frame.height));
// put the frame rect in atlas UV space
region_rect /= f32(atlas_dimensions.x);
// if safety_offset is true, calculate a relatively tiny offset to avoid getting the end of
// the frame and causing linear or nearest filtering to bleed to the adjacent frame.
let texel_size = select(0.0, (1.0 / f32(shadow_u.atlas_frame.x)) * 4.0, safety_offset);
// lerp input coords
let region_coords = vec2<f32>(
mix(region_rect.x + texel_size, region_rect.x + region_rect.z - texel_size, coords.x),
mix(region_rect.y + texel_size, region_rect.y + region_rect.w - texel_size, coords.y)
);
return region_coords;
}
/// Find the average blocker distance for a directiona llight
fn find_blocker_distance_dir_light(tex_coords: vec2<f32>, receiver_depth: f32, bias: f32, shadow_u: LightShadowMapUniform) -> vec2<f32> {
let search_width = search_width(shadow_u.near_plane, shadow_u.light_size_uv, receiver_depth);
var blockers = 0;
var avg_dist = 0.0;
let samples = i32(u_shadow_settings.pcss_blocker_search_samples);
for (var i = 0; i < samples; i++) {
let offset_coords = tex_coords + u_pcss_poisson_disc[i] * search_width;
let new_coords = to_atlas_frame_coords(shadow_u, offset_coords, false);
let z = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, new_coords, 0.0);
if z < (receiver_depth - bias) {
blockers += 1;
avg_dist += z;
}
}
let b = f32(blockers);
return vec2<f32>(avg_dist / b, b);
}
fn pcss_dir_light(tex_coords: vec2<f32>, receiver_depth: f32, shadow_u: LightShadowMapUniform) -> f32 {
let blocker_search = find_blocker_distance_dir_light(tex_coords, receiver_depth, 0.0, shadow_u);
// If no blockers were found, exit now to save in filtering
if blocker_search.y == 0.0 {
return 1.0;
}
let blocker_depth = blocker_search.x;
// penumbra estimation
let penumbra_width = (receiver_depth - blocker_depth) / blocker_depth;
// PCF
let uv_radius = penumbra_width * shadow_u.light_size_uv * shadow_u.near_plane / receiver_depth;
return pcf_dir_light(tex_coords, receiver_depth, shadow_u, uv_radius);
}
/// Calculate the shadow coefficient using PCF of a directional light
fn pcf_dir_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, uv_radius: f32) -> f32 {
var shadow = 0.0;
let samples_num = i32(u_shadow_settings.pcf_samples_num);
for (var i = 0; i < samples_num; i++) {
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
let new_coords = to_atlas_frame_coords(shadow_u, offset, false);
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
}
shadow /= f32(samples_num);
// clamp shadow to [0; 1]
return saturate(shadow);
}
fn calc_shadow_point_light(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light, atlas_dimensions: vec2<i32>) -> f32 {
var frag_to_light = world_pos - light.position;
let temp = coords_to_cube_atlas(normalize(frag_to_light));
var coords_2d = temp.xy;
let cube_idx = i32(temp.z);
var indices = light.light_shadow_uniform_index;
let i = indices[cube_idx - 1];
let u: LightShadowMapUniform = u_light_shadow[i];
let uniforms = array<LightShadowMapUniform, 6>(
u_light_shadow[indices[0]],
u_light_shadow[indices[1]],
u_light_shadow[indices[2]],
u_light_shadow[indices[3]],
u_light_shadow[indices[4]],
u_light_shadow[indices[5]]
);
var current_depth = length(frag_to_light);
current_depth /= u.far_plane;
current_depth -= u.constant_depth_bias;
// get settings
let settings = get_shadow_settings(u);
let pcf_samples_num = settings.x;
let pcss_blocker_search_samples = settings.y;
var shadow = 0.0;
// hardware 2x2 PCF via camparison sampler
if pcf_samples_num == 2u {
let region_coords = to_atlas_frame_coords(u, coords_2d, true);
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
}
// PCSS
else if pcf_samples_num > 0u && pcss_blocker_search_samples > 0u {
shadow = pcss_dir_light(coords_2d, current_depth, u);
}
// only PCF
else if pcf_samples_num > 0u {
let texel_size = 1.0 / f32(u.atlas_frame.width);
shadow = pcf_point_light(frag_to_light, current_depth, uniforms, pcf_samples_num, 0.007);
//shadow = pcf_point_light(coords_2d, current_depth, u, pcf_samples_num, texel_size);
}
// no filtering
else {
let region_coords = to_atlas_frame_coords(u, coords_2d, true);
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0);
shadow = select(1.0, 0.0, current_depth > closest_depth);
}
return shadow;
}
/// Calculate the shadow coefficient using PCF of a directional light
fn pcf_point_light(tex_coords: vec3<f32>, test_depth: f32, shadow_us: array<LightShadowMapUniform, 6>, samples_num: u32, uv_radius: f32) -> f32 {
var shadow_unis = shadow_us;
var shadow = 0.0;
for (var i = 0; i < i32(samples_num); i++) {
var temp = coords_to_cube_atlas(tex_coords);
var coords_2d = temp.xy;
var cube_idx = i32(temp.z);
var shadow_u = shadow_unis[cube_idx - 1];
coords_2d += u_pcf_poisson_disc[i] * uv_radius;
let new_coords = to_atlas_frame_coords(shadow_u, coords_2d, true);
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
}
shadow /= f32(samples_num);
// clamp shadow to [0; 1]
return saturate(shadow);
}
fn calc_shadow_spot_light(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light, atlas_dimensions: vec2<i32>) -> f32 {
let map_data: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let frag_pos_light_space = map_data.light_space_matrix * vec4<f32>(world_pos, 1.0);
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
// for some reason the y component is flipped after transforming
proj_coords.y = -proj_coords.y;
// Remap xy to [0.0, 1.0]
let xy_remapped = proj_coords.xy * 0.5 + 0.5;
// use a bias to avoid shadow acne
let current_depth = proj_coords.z - map_data.constant_depth_bias;
// get settings
let settings = get_shadow_settings(map_data);
let pcf_samples_num = settings.x;
let pcss_blocker_search_samples = settings.y;
var shadow = 0.0;
// hardware 2x2 PCF via camparison sampler
if pcf_samples_num == 2u {
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
}
// only PCF is supported for spot lights
else if pcf_samples_num > 0u {
let texel_size = 1.0 / f32(map_data.atlas_frame.width);
shadow = pcf_spot_light(xy_remapped, current_depth, map_data, i32(pcf_samples_num), texel_size);
}
// no filtering
else {
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0);
shadow = select(1.0, 0.0, current_depth > closest_depth);
}
// dont cast shadows outside the light's far plane
if (proj_coords.z > 1.0) {
shadow = 1.0;
}
// dont cast shadows if the texture coords would go past the shadow maps
if (xy_remapped.x > 1.0 || xy_remapped.x < 0.0 || xy_remapped.y > 1.0 || xy_remapped.y < 0.0) {
shadow = 1.0;
}
return shadow;
}
/// Calculate the shadow coefficient using PCF of a directional light
fn pcf_spot_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, samples_num: i32, uv_radius: f32) -> f32 {
var shadow = 0.0;
for (var i = 0; i < samples_num; i++) {
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
let new_coords = to_atlas_frame_coords(shadow_u, offset, false);
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
}
shadow /= f32(samples_num);
// clamp shadow to [0; 1]
return saturate(shadow);
}
fn debug_grid(in: VertexOutput) -> vec4<f32> { fn debug_grid(in: VertexOutput) -> vec4<f32> {
let tile_index_float: vec2<f32> = in.clip_position.xy / 16.0; let tile_index_float: vec2<f32> = in.clip_position.xy / 16.0;
let tile_index = vec2<u32>(floor(tile_index_float)); let tile_index = vec2<u32>(floor(tile_index_float));
@ -163,7 +563,7 @@ fn debug_grid(in: VertexOutput) -> vec4<f32> {
return vec4<f32>(ratio, ratio, ratio, 1.0); return vec4<f32>(ratio, ratio, ratio, 1.0);
} }
fn blinn_phong_dir_light(world_pos: vec3<f32>, world_norm: vec3<f32>, dir_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> { fn blinn_phong_dir_light(world_pos: vec3<f32>, world_norm: vec3<f32>, dir_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {
let light_color = dir_light.color.xyz; let light_color = dir_light.color.xyz;
let camera_view_pos = u_camera.position; let camera_view_pos = u_camera.position;
@ -189,10 +589,10 @@ fn blinn_phong_dir_light(world_pos: vec3<f32>, world_norm: vec3<f32>, dir_light:
diffuse_color *= dir_light.diffuse; diffuse_color *= dir_light.diffuse;
specular_color *= dir_light.specular;*/ specular_color *= dir_light.specular;*/
return (ambient_color + diffuse_color + specular_color) * dir_light.intensity; return (ambient_color + (shadow) * (diffuse_color + specular_color)) * dir_light.intensity;
} }
fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> { fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {
let light_color = point_light.color.xyz; let light_color = point_light.color.xyz;
let light_pos = point_light.position.xyz; let light_pos = point_light.position.xyz;
let camera_view_pos = u_camera.position; let camera_view_pos = u_camera.position;
@ -222,10 +622,11 @@ fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_li
diffuse_color *= attenuation; diffuse_color *= attenuation;
specular_color *= attenuation; specular_color *= attenuation;
return (ambient_color + diffuse_color + specular_color) * point_light.intensity; //return (ambient_color + shadow * (diffuse_color + specular_color)) * point_light.intensity;
return (shadow * (ambient_color + diffuse_color + specular_color)) * point_light.intensity;
} }
fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> { fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {
let light_color = spot_light.color; let light_color = spot_light.color;
let light_pos = spot_light.position; let light_pos = spot_light.position;
let camera_view_pos = u_camera.position; let camera_view_pos = u_camera.position;
@ -260,13 +661,13 @@ fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_ligh
let distance = length(light_pos - world_pos); let distance = length(light_pos - world_pos);
let attenuation = calc_attenuation(spot_light, distance); let attenuation = calc_attenuation(spot_light, distance);
ambient_color *= attenuation * spot_light.intensity * cone; ambient_color *= attenuation * cone;
diffuse_color *= attenuation * spot_light.intensity * cone; diffuse_color *= attenuation * cone;
specular_color *= attenuation * spot_light.intensity * cone; specular_color *= attenuation * cone;
//// end of spot light attenuation //// //// end of spot light attenuation ////
//return /*ambient_color +*/ diffuse_color + specular_color;
return /*ambient_color +*/ diffuse_color + specular_color; return (shadow * (diffuse_color + specular_color)) * spot_light.intensity;
} }
fn calc_attenuation(light: Light, distance: f32) -> f32 { fn calc_attenuation(light: Light, distance: f32) -> f32 {

View File

@ -31,6 +31,7 @@ struct Light {
spot_cutoff: f32, spot_cutoff: f32,
spot_outer_cutoff: f32, spot_outer_cutoff: f32,
light_shadow_uniform_index: array<i32, 6>,
}; };
struct Lights { struct Lights {

View File

@ -0,0 +1,70 @@
struct TransformData {
transform: mat4x4<f32>,
normal_matrix: mat4x4<f32>,
}
struct TextureAtlasFrame {
offset: vec2<u32>,
size: vec2<u32>,
}
struct LightShadowMapUniform {
light_space_matrix: mat4x4<f32>,
atlas_frame: TextureAtlasFrame,
near_plane: f32,
far_plane: f32,
light_size_uv: f32,
light_pos: vec3<f32>,
/// boolean casted as u32
has_shadow_settings: u32,
pcf_samples_num: u32,
pcss_blocker_search_samples: u32,
constant_depth_bias: f32,
}
@group(0) @binding(0)
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
/*@group(0) @binding(1)
var<uniform> u_light_pos: vec3<f32>;
@group(0) @binding(2)
var<uniform> u_light_far_plane: f32;*/
@group(1) @binding(0)
var<uniform> u_model_transform_data: TransformData;
struct VertexOutput {
@builtin(position)
clip_position: vec4<f32>,
@location(0) world_pos: vec3<f32>,
@location(1) instance_index: u32,
}
@vertex
fn vs_main(
@location(0) position: vec3<f32>,
@builtin(instance_index) instance_index: u32,
) -> VertexOutput {
let world_pos = u_model_transform_data.transform * vec4<f32>(position, 1.0);
let pos = u_light_shadow[instance_index].light_space_matrix * world_pos;
return VertexOutput(pos, world_pos.xyz, instance_index);
}
struct FragmentOutput {
@builtin(frag_depth) depth: f32,
}
/// Fragment shader used for point lights (or other perspective lights) to create linear depth
@fragment
fn fs_point_light_main(
in: VertexOutput
) -> FragmentOutput {
let u = u_light_shadow[in.instance_index];
var light_dis = length(in.world_pos - u.light_pos);
// map to [0; 1] range by dividing by far plane
light_dis = light_dis / u.far_plane;
return FragmentOutput(light_dis);
}

View File

@ -0,0 +1,149 @@
use std::{collections::VecDeque, marker::PhantomData, mem, sync::Arc};
/// A buffer on the GPU that has persistent indices.
///
/// `GpuSlotBuffer` allocates a buffer on the GPU and keeps stable indices of elements and
/// reuses ones that were removed. It supports aligned buffers with [`GpuSlotBuffer::new_aligned`],
/// as well as unaligned buffers with [`GpuSlotBuffer::new`].
pub struct GpuSlotBuffer<T: bytemuck::Pod + bytemuck::Zeroable> {
/// The amount of elements that can fit in the buffer.
capacity: u64,
/// The ending point of the buffer elements.
len: u64,
/// The list of dead and reusable indices in the buffer.
dead_indices: VecDeque<u64>,
/// The optional alignment of elements in the buffer.
alignment: Option<u64>,
/// The actual gpu buffer
buffer: Arc<wgpu::Buffer>,
_marker: PhantomData<T>,
}
impl<T: bytemuck::Pod + bytemuck::Zeroable> GpuSlotBuffer<T> {
/// Create a new GpuSlotBuffer with unaligned elements.
///
/// See [`GpuSlotBuffer::new_aligned`].
pub fn new(device: &wgpu::Device, label: Option<&str>, usage: wgpu::BufferUsages, capacity: u64) -> Self {
Self::new_impl(device, label, usage, capacity, None)
}
/// Create a new buffer with **aligned** elements.
///
/// See [`GpuSlotBuffer::new`].
pub fn new_aligned(device: &wgpu::Device, label: Option<&str>, usage: wgpu::BufferUsages, capacity: u64, alignment: u64) -> Self {
Self::new_impl(device, label, usage, capacity, Some(alignment))
}
fn new_impl(device: &wgpu::Device, label: Option<&str>, usage: wgpu::BufferUsages, capacity: u64, alignment: Option<u64>) -> Self {
let buffer = Arc::new(device.create_buffer(&wgpu::BufferDescriptor {
label,
size: capacity * mem::size_of::<T>() as u64,
usage,
mapped_at_creation: false,
}));
Self {
capacity,
len: 0,
dead_indices: VecDeque::default(),
buffer,
alignment,
_marker: PhantomData
}
}
/// Calculates the byte offset in the buffer of the element at `i`.
pub fn offset_of(&self, i: u64) -> u64 {
if let Some(align) = self.alignment {
let transform_index = i % self.capacity;
transform_index * align
} else {
i * mem::size_of::<T>() as u64
}
}
/// Set an element at `i` in the buffer to `val`.
pub fn set_at(&self, queue: &wgpu::Queue, i: u64, val: &T) {
let offset = self.offset_of(i);
queue.write_buffer(&self.buffer, offset, bytemuck::bytes_of(val));
}
/// Attempt to insert an element to the GPU buffer, returning the index it was inserted at.
///
/// Returns `None` when the buffer has no space to fit the element.
pub fn try_insert(&mut self, queue: &wgpu::Queue, val: &T) -> Option<u64> {
// reuse a dead index or get the next one
let i = match self.dead_indices.pop_front() {
Some(i) => i,
None => {
if self.len == self.capacity {
return None;
}
let i = self.len;
self.len += 1;
i
}
};
self.set_at(queue, i, val);
Some(i)
}
/// Insert an element to the GPU buffer, returning the index it was inserted at.
///
/// The index is not guaranteed to be the end of the buffer since this structure reuses
/// indices after they're removed.
///
/// # Panics
/// Panics if the buffer does not have space to fit `val`, see [`GpuSlotBuffer::try_insert`].
pub fn insert(&mut self, queue: &wgpu::Queue, val: &T) -> u64 {
self.try_insert(queue, val)
.expect("GPU slot buffer ran out of slots to push elements into")
}
/// Remove the element at `i`, clearing the elements slot in the buffer.
///
/// If you do not care that the slot in the buffer is emptied, use
/// [`GpuSlotBuffer::remove_quick`].
pub fn remove(&mut self, queue: &wgpu::Queue, i: u64) {
let mut zeros = Vec::new();
zeros.resize(mem::size_of::<T>(), 0);
let offset = self.offset_of(i);
queue.write_buffer(&self.buffer, offset, bytemuck::cast_slice(zeros.as_slice()));
self.dead_indices.push_back(i);
}
/// Remove the element at `i` without clearing its space in the buffer.
///
/// If you want to ensure that the slot in the buffer is emptied, use
/// [`GpuSlotBuffer::remove`].
pub fn remove_quick(&mut self, i: u64) {
self.dead_indices.push_back(i);
}
/// Returns the backing [`wgpu::Buffer`].
pub fn buffer(&self) -> &Arc<wgpu::Buffer> {
&self.buffer
}
/// Return the length of the buffer.
///
/// This value may not reflect the amount of elements that are actually alive in the buffer if
/// elements were removed and not re-added.
pub fn len(&self) -> u64 {
self.len
}
/// Return the amount of inuse indices in the buffer.
pub fn inuse_len(&self) -> u64 {
self.len - self.dead_indices.len() as u64
}
/// Returns the amount of elements the buffer can fit.
pub fn capacity(&self) -> u64 {
self.capacity
}
}

View File

@ -1,4 +1,4 @@
use std::rc::Rc; use std::sync::Arc;
use image::GenericImageView; use image::GenericImageView;
use lyra_resource::{FilterMode, ResHandle, Texture, WrappingMode}; use lyra_resource::{FilterMode, ResHandle, Texture, WrappingMode};
@ -44,7 +44,7 @@ impl RenderTexture {
}) })
} }
fn create_bind_group_pair(device: &wgpu::Device, layout: Rc<wgpu::BindGroupLayout>, view: &wgpu::TextureView, sampler: &wgpu::Sampler) -> BindGroupPair { fn create_bind_group_pair(device: &wgpu::Device, layout: Arc<wgpu::BindGroupLayout>, view: &wgpu::TextureView, sampler: &wgpu::Sampler) -> BindGroupPair {
let bg = device.create_bind_group( let bg = device.create_bind_group(
&wgpu::BindGroupDescriptor { &wgpu::BindGroupDescriptor {
layout: &layout, layout: &layout,
@ -68,12 +68,12 @@ impl RenderTexture {
} }
} }
pub fn from_bytes(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, bytes: &[u8], label: &str) -> anyhow::Result<Self> { pub fn from_bytes(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, bytes: &[u8], label: &str) -> anyhow::Result<Self> {
let img = image::load_from_memory(bytes)?; let img = image::load_from_memory(bytes)?;
Self::from_image(device, queue, bg_layout, &img, Some(label)) Self::from_image(device, queue, bg_layout, &img, Some(label))
} }
pub fn from_image(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, img: &image::DynamicImage, label: Option<&str>) -> anyhow::Result<Self> { pub fn from_image(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, img: &image::DynamicImage, label: Option<&str>) -> anyhow::Result<Self> {
let rgba = img.to_rgba8(); let rgba = img.to_rgba8();
let dimensions = img.dimensions(); let dimensions = img.dimensions();
@ -134,7 +134,7 @@ impl RenderTexture {
}) })
} }
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, texture_res: &ResHandle<Texture>, label: Option<&str>) -> anyhow::Result<Self> { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, texture_res: &ResHandle<Texture>, label: Option<&str>) -> anyhow::Result<Self> {
let texture_ref = texture_res.data_ref().unwrap(); let texture_ref = texture_res.data_ref().unwrap();
let img = texture_ref.image.data_ref().unwrap(); let img = texture_ref.image.data_ref().unwrap();
@ -371,7 +371,7 @@ impl RenderTexture {
/// Convert [`lyra_resource::WrappingMode`] to [`wgpu::AddressMode`] /// Convert [`lyra_resource::WrappingMode`] to [`wgpu::AddressMode`]
#[inline(always)] #[inline(always)]
fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode { pub(crate) fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode {
match wmode { match wmode {
WrappingMode::ClampToEdge => wgpu::AddressMode::ClampToEdge, WrappingMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
WrappingMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat, WrappingMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat,
@ -381,7 +381,7 @@ fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode {
/// Convert [`lyra_resource::FilterMode`] to [`wgpu::FilterMode`] /// Convert [`lyra_resource::FilterMode`] to [`wgpu::FilterMode`]
#[inline(always)] #[inline(always)]
fn res_filter_to_wgpu(fmode: FilterMode) -> wgpu::FilterMode { pub(crate) fn res_filter_to_wgpu(fmode: FilterMode) -> wgpu::FilterMode {
match fmode { match fmode {
FilterMode::Nearest => wgpu::FilterMode::Nearest, FilterMode::Nearest => wgpu::FilterMode::Nearest,
FilterMode::Linear => wgpu::FilterMode::Linear, FilterMode::Linear => wgpu::FilterMode::Linear,

View File

@ -0,0 +1,297 @@
use std::{
cmp::max, collections::HashMap, sync::Arc
};
use glam::UVec2;
#[derive(Debug, thiserror::Error)]
pub enum AtlasPackError {
/// The rectangles can't be placed into the atlas. The atlas must increase in size
#[error("There is not enough space in the atlas for the textures")]
NotEnoughSpace,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct AtlasFrame {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl AtlasFrame {
pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
Self {
x, y, width, height
}
}
}
pub struct TextureAtlas<P: AtlasPacker = SkylinePacker> {
atlas_size: UVec2,
texture_format: wgpu::TextureFormat,
texture: Arc<wgpu::Texture>,
view: Arc<wgpu::TextureView>,
packer: P,
}
impl<P: AtlasPacker> TextureAtlas<P> {
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
usages: wgpu::TextureUsages,
atlas_size: UVec2,
) -> Self {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("texture_atlas"),
size: wgpu::Extent3d {
width: atlas_size.x,
height: atlas_size.y,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: usages,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Self {
atlas_size,
texture_format: format,
texture: Arc::new(texture),
view: Arc::new(view),
packer: P::new(atlas_size),
}
}
/// Add a texture of `size` and pack it into the atlas, returning the id of the texture in
/// the atlas.
///
/// If you are adding multiple textures at a time and want to wait to pack the atlas, use
/// [`TextureAtlas::add_texture_unpacked`] and then after you're done adding them, pack them
/// with [`TextureAtlas::pack_atlas`].
pub fn pack(&mut self, width: u32, height: u32) -> Result<u64, AtlasPackError> {
let id = self.packer.pack(width, height)?;
Ok(id as u64)
}
/// Get the viewport of a texture index in the atlas.
pub fn texture_frame(&self, atlas_index: u64) -> Option<AtlasFrame> {
self.packer.frame(atlas_index as _)
}
pub fn view(&self) -> &Arc<wgpu::TextureView> {
&self.view
}
pub fn texture(&self) -> &Arc<wgpu::Texture> {
&self.texture
}
pub fn texture_format(&self) -> &wgpu::TextureFormat {
&self.texture_format
}
/// Returns the size of the entire texture atlas.
pub fn atlas_size(&self) -> UVec2 {
self.atlas_size
}
}
pub trait AtlasPacker {
fn new(size: UVec2) -> Self;
/// Get an [`AtlasFrame`] of a texture with `id`.
fn frame(&self, id: usize) -> Option<AtlasFrame>;
/// Get all [`AtlasFrame`]s in the atlas.
fn frames(&self) -> &HashMap<usize, AtlasFrame>;
/// Pack a new rect into the atlas.
fn pack(&mut self, width: u32, height: u32) -> Result<usize, AtlasPackError>;
}
struct Skyline {
/// Starting x of the skyline
x: usize,
/// Starting y of the skyline
y: usize,
/// Width of the skyline
width: usize,
}
impl Skyline {
fn right(&self) -> usize {
self.x + self.width
}
}
pub struct SkylinePacker {
size: UVec2,
skylines: Vec<Skyline>,
frame_idx: usize,
frames: HashMap<usize, AtlasFrame>,
}
impl SkylinePacker {
pub fn new(size: UVec2) -> Self {
let skylines = vec![Skyline {
x: 0,
y: 0,
width: size.x as _,
}];
Self {
size,
skylines,
frame_idx: 0,
frames: Default::default(),
}
}
fn can_add(&self, mut i: usize, w: u32, h: u32) -> Option<usize> {
let x = self.skylines[i].x as u32;
if x + w > self.size.x {
return None;
}
let mut width_left = w;
let mut y = self.skylines[i].y as u32;
loop {
y = max(y, self.skylines[i].y as u32);
if y + h > self.size.y {
return None;
}
if self.skylines[i].width as u32 >= width_left {
return Some(y as usize);
}
width_left -= self.skylines[i].width as u32;
i += 1;
if i >= self.skylines.len() {
return None;
}
}
}
fn find_skyline(&self, width: u32, height: u32) -> Option<(usize, AtlasFrame)> {
let mut min_height = std::u32::MAX;
let mut min_width = std::u32::MAX;
let mut index = None;
let mut frame = AtlasFrame::default();
// keep the min height as small as possible
for i in 0..self.skylines.len() {
if let Some(y) = self.can_add(i, width, height) {
let y = y as u32;
/* if r.bottom() < min_height
|| (r.bottom() == min_height && self.skylines[i].width < min_width as usize) */
if y + height < min_height ||
(y + height == min_height && self.skylines[i].width < min_width as usize)
{
min_height = y + height;
min_width = self.skylines[i].width as _;
index = Some(i);
frame = AtlasFrame::new(self.skylines[i].x as _, y, width, height);
}
}
// TODO: rotation
}
if let Some(index) = index {
Some((index, frame))
} else {
None
}
}
fn split(&mut self, i: usize, frame: &AtlasFrame) {
let skyline = Skyline {
x: frame.x as _,
y: (frame.y + frame.height) as _,
width: frame.width as _
};
assert!(skyline.right() <= self.size.x as usize);
assert!(skyline.y <= self.size.y as usize);
self.skylines.insert(i, skyline);
let i = i + 1;
while i < self.skylines.len() {
assert!(self.skylines[i - 1].x <= self.skylines[i].x);
if self.skylines[i].x < self.skylines[i - 1].x + self.skylines[i - 1].width {
let shrink = self.skylines[i-1].x + self.skylines[i-1].width - self.skylines[i].x;
if self.skylines[i].width <= shrink {
self.skylines.remove(i);
} else {
self.skylines[i].x += shrink;
self.skylines[i].width -= shrink;
break;
}
} else {
break;
}
}
}
/// Merge skylines with the same y value
fn merge(&mut self) {
let mut i = 1;
while i < self.skylines.len() {
if self.skylines[i - 1].y == self.skylines[i].y {
self.skylines[i - 1].width += self.skylines[i].width;
self.skylines.remove(i);
} else {
i += 1;
}
}
}
//pub fn pack(&mut self, )
}
impl AtlasPacker for SkylinePacker {
fn new(size: UVec2) -> Self {
SkylinePacker::new(size)
}
fn frame(&self, id: usize) -> Option<AtlasFrame> {
self.frames.get(&id).cloned()
}
fn frames(&self) -> &HashMap<usize, AtlasFrame> {
&self.frames
}
fn pack(&mut self, width: u32, height: u32) -> Result<usize, AtlasPackError> {
if let Some((i, frame)) = self.find_skyline(width, height) {
self.split(i, &frame);
self.merge();
let frame_idx = self.frame_idx;
self.frame_idx += 1;
self.frames.insert(frame_idx, frame);
Ok(frame_idx)
} else {
Err(AtlasPackError::NotEnoughSpace)
}
}
}

View File

@ -1,4 +1,4 @@
use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64, rc::Rc}; use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64, sync::Arc};
use lyra_ecs::Entity; use lyra_ecs::Entity;
use tracing::instrument; use tracing::instrument;
@ -165,7 +165,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, /// [`TransformGroup`]s are used to represent entries in the buffer. They are used to insert,
/// update, and retrieve the transforms. /// update, and retrieve the transforms.
pub struct TransformBuffers { pub struct TransformBuffers {
pub bindgroup_layout: Rc<wgpu::BindGroupLayout>, pub bindgroup_layout: Arc<wgpu::BindGroupLayout>,
//groups: CachedValMap<TransformGroupId, TransformIndex>, //groups: CachedValMap<TransformGroupId, TransformIndex>,
//groups: SlotMap<TransformGroupId, TransformIndex>, //groups: SlotMap<TransformGroupId, TransformIndex>,
entries: Vec<BufferEntry>, entries: Vec<BufferEntry>,
@ -195,7 +195,7 @@ impl TransformBuffers {
}); });
let mut s = Self { let mut s = Self {
bindgroup_layout: Rc::new(bindgroup_layout), bindgroup_layout: Arc::new(bindgroup_layout),
entries: Default::default(), 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>()), 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, limits,
@ -345,9 +345,6 @@ impl TransformBuffers {
/// Returns a boolean indicating if the buffers need to be expanded /// Returns a boolean indicating if the buffers need to be expanded
pub fn needs_expand(&self) -> bool { pub fn needs_expand(&self) -> bool {
false false
/* self.entries.last()
.map(|entry| entry.len >= self.max_transform_count)
.unwrap_or(false) */
} }
} }

View File

@ -15,6 +15,23 @@ impl Vertex {
position, tex_coords, normals position, tex_coords, normals
} }
} }
/// Returns a [`wgpu::VertexBufferLayout`] with only the position as a vertex attribute.
///
/// The stride is still `std::mem::size_of::<Vertex>()`, but only position is included.
pub fn position_desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3, // Vec3
},
]
}
}
} }
impl DescVertexBufferLayout for Vertex { impl DescVertexBufferLayout for Vertex {

View File

@ -10,7 +10,7 @@ pub fn radians_to_degrees(radians: f32) -> f32 {
radians * 180.0 / PI radians * 180.0 / PI
} }
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Angle { pub enum Angle {
Degrees(f32), Degrees(f32),
Radians(f32), Radians(f32),
@ -69,3 +69,17 @@ impl std::ops::SubAssign for Angle {
} }
} }
} }
impl std::ops::Mul<f32> for Angle {
type Output = Angle;
fn mul(self, rhs: f32) -> Self::Output {
Angle::Radians(self.to_radians() * rhs)
}
}
impl std::ops::MulAssign<f32> for Angle {
fn mul_assign(&mut self, rhs: f32) {
*self = *self * rhs;
}
}

View File

@ -95,7 +95,7 @@ impl From<gltf::material::AlphaMode> for AlphaMode {
} }
} }
#[derive(Clone, Reflect)] #[derive(Clone, Reflect, Default)]
pub struct Specular { pub struct Specular {
/// The strength of the specular reflection, default of 1.0 /// The strength of the specular reflection, default of 1.0
pub factor: f32, pub factor: f32,