diff --git a/.cargo/config b/.cargo/config index 263cedb682..7ce962b17f 100644 --- a/.cargo/config +++ b/.cargo/config @@ -9,9 +9,11 @@ csv-import = "run --manifest-path common/Cargo.toml --features=bin_csv --bin csv test-server = "run --bin veloren-server-cli --no-default-features -- -b" tracy-server = "-Zunstable-options run --bin veloren-server-cli --no-default-features --features tracy,simd --profile no_overflow" tracy-world-server = "-Zunstable-options run --bin veloren-server-cli --features tracy,simd --profile no_overflow -- -b" -test-voxygen = "run --bin veloren-voxygen --no-default-features --features gl,simd" -tracy-voxygen = "-Zunstable-options run --bin veloren-voxygen --no-default-features --features tracy,gl,simd --profile no_overflow" +test-voxygen = "run --bin veloren-voxygen --no-default-features --features simd" +tracy-voxygen = "-Zunstable-options run --bin veloren-voxygen --no-default-features --features tracy,simd --profile no_overflow" server = "run --bin veloren-server-cli" +dbg-voxygen = "run --bin veloren-voxygen -Zunstable-options --profile debuginfo" + [env] RUSTC_FORCE_INCREMENTAL = "1" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e11aa2427b..9590b819bd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,6 +37,7 @@ before_script: - export VELOREN_ASSETS="$(pwd)/assets" - echo "VELOREN_ASSETS=$VELOREN_ASSETS" - export RUSTFLAGS="-D warnings" + - export SHADERC_LIB_DIR=/shaderc/combined/ - rm -rf target || echo "it seems that sometimes OLD data is left over" # 8866215 is the user that is used to sync data to the collaboration repos diff --git a/.gitlab/CI/build.gitlab-ci.yml b/.gitlab/CI/build.gitlab-ci.yml index 6f374ee28b..516a74ad9f 100644 --- a/.gitlab/CI/build.gitlab-ci.yml +++ b/.gitlab/CI/build.gitlab-ci.yml @@ -75,17 +75,25 @@ coverage: .twindows: image: registry.gitlab.com/veloren/veloren-docker-ci/cache/release-windows:${CACHE_IMAGE_TAG} script: + - update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix + - update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix - ln -s /dockercache/target target - rm -r target/release/incremental/veloren_* || echo "all good" # TMP FIX FOR 2021-03-22-nightly - VELOREN_USERDATA_STRATEGY=executable cargo build --target=x86_64-pc-windows-gnu --release - cp -r target/x86_64-pc-windows-gnu/release/veloren-server-cli.exe $CI_PROJECT_DIR - cp -r target/x86_64-pc-windows-gnu/release/veloren-voxygen.exe $CI_PROJECT_DIR + - cp /usr/lib/gcc/x86_64-w64-mingw32/7.3-posix/libgcc_s_seh-1.dll $CI_PROJECT_DIR + - cp /usr/lib/gcc/x86_64-w64-mingw32/7.3-posix/libstdc++-6.dll $CI_PROJECT_DIR + - cp /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll $CI_PROJECT_DIR artifacts: paths: - veloren-server-cli.exe - veloren-voxygen.exe - assets/ - LICENSE + - libgcc_s_seh-1.dll + - libstdc++-6.dll + - libwinpthread-1.dll expire_in: 1 week .tmacos: diff --git a/CHANGELOG.md b/CHANGELOG.md index cf8840897a..b9459c0951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Renamed Animal Trainers to Beastmasters and gave them their own set of armor to wear - ChargedRanged attacks (such as some bow attacks) use an FOV zoom effect to indicate charge. - Add chest to each dungeon with unique loot +- Added a new option in the graphics menu to enable GPU timing (not always supported). The timing values can be viewed in the HUD debug info (F3) and will be saved as chrome trace files in the working directory when taking a screenshot. +- Added new Present Mode option in the graphics menu. Selecting Fifo (i.e. vsync) or Mailbox can be used to eliminate screen tearing. ### Changed @@ -113,6 +115,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Water extinguishes entities on fire - Item pickups are shown in separate window and inventory-full shows above item - Reworked bow +- Switched to the `wgpu` graphics library giving us support for vulkan, dx12, metal, and dx11 (support for opengl is lost for the moment). This improves the graphics performance for many users. +- Reworked sprite rendering to vastly reduce the CPU work. Large sprite view distances are now much more performant. +- Optimized rendering of quads (most of the graphics in the game) using an index buffer, decreasing the number of vertices that need to be processed by 33%. +- Moved the rest of screenshot work into the background. Screenshoting no longer induces large pauses. ### Removed diff --git a/Cargo.lock b/Cargo.lock index ae25780cc5..d5e53c4fcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,12 +100,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "android_glue" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" - [[package]] name = "ansi-parser" version = "0.7.0" @@ -176,7 +170,7 @@ checksum = "0609c78bd572f4edc74310dfb63a01f5609d53fa8b4dd7c4d98aef3b3e8d72d1" dependencies = [ "proc-macro-hack", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -190,6 +184,9 @@ name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +dependencies = [ + "serde", +] [[package]] name = "as-slice" @@ -203,6 +200,15 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ash" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06063a002a77d2734631db74e8f4ce7148b77fe522e6bca46f2ae7774fd48112" +dependencies = [ + "libloading 0.7.0", +] + [[package]] name = "assets_manager" version = "0.4.4" @@ -239,7 +245,7 @@ checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -358,6 +364,21 @@ dependencies = [ "shlex", ] +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.2.1" @@ -428,7 +449,7 @@ checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -511,13 +532,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "cgl" -version = "0.3.2" +name = "cfg_aliases" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" -dependencies = [ - "libc", -] +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" @@ -647,22 +665,6 @@ dependencies = [ "cc", ] -[[package]] -name = "cocoa" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54201c07dcf3a5ca33fececb8042aed767ee4bfd5a0235a8ceabcda956044b2" -dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "core-foundation 0.9.1", - "core-graphics 0.22.2", - "foreign-types", - "libc", - "objc", -] - [[package]] name = "cocoa" version = "0.24.0" @@ -694,6 +696,16 @@ dependencies = [ "objc", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -764,6 +776,12 @@ dependencies = [ "walkdir 0.1.8", ] +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + [[package]] name = "copypasta" version = "0.7.1" @@ -1205,13 +1223,24 @@ dependencies = [ "sct", ] +[[package]] +name = "d3d12" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091ed1b25fe47c7ff129fc440c23650b6114f36aa00bc7212cc8041879294428" +dependencies = [ + "bitflags", + "libloading 0.7.0", + "winapi 0.3.9", +] + [[package]] name = "daggy" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9293a0da7d1bc1f30090ece4d9f9de79a07be7302ddb00e5eb1fefb6ee6409e2" dependencies = [ - "petgraph", + "petgraph 0.4.13", ] [[package]] @@ -1245,7 +1274,7 @@ dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", "strsim 0.9.3", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -1259,7 +1288,7 @@ dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", "strsim 0.10.0", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -1270,7 +1299,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core 0.10.2", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -1281,7 +1310,7 @@ checksum = "0a7a1445d54b2f9792e3b31a3e715feabbace393f38dc4ffd49d94ee9bc487d5" dependencies = [ "darling_core 0.12.3", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -1311,7 +1340,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -1405,15 +1434,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "draw_state" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cf9537e2d06891448799b96d5a8c8083e0e90522a7fdabe6ebf4f41d79d651" -dependencies = [ - "bitflags", -] - [[package]] name = "either" version = "1.6.1" @@ -1452,7 +1472,7 @@ checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -1473,7 +1493,7 @@ dependencies = [ "darling 0.12.3", "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -1539,7 +1559,7 @@ checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -1566,6 +1586,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + [[package]] name = "flate2" version = "1.0.20" @@ -1746,7 +1772,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -1851,45 +1877,147 @@ dependencies = [ ] [[package]] -name = "gfx" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01de46f9508a5c259aef105f0bff760ceddca832ea9c87ce03d1923e22ee155b" +name = "gfx-auxil" +version = "0.9.0" +source = "git+https://github.com/gfx-rs/gfx?rev=27a1dae3796d33d23812f2bb8c7e3b5aea18b521#27a1dae3796d33d23812f2bb8c7e3b5aea18b521" dependencies = [ - "draw_state", - "gfx_core", - "log", + "fxhash", + "gfx-hal", + "spirv_cross", ] [[package]] -name = "gfx_core" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75fbddaef2e12b4995900539d7209d947b988a3d87ee8737484d049b526e5441" +name = "gfx-backend-dx11" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/gfx?rev=27a1dae3796d33d23812f2bb8c7e3b5aea18b521#27a1dae3796d33d23812f2bb8c7e3b5aea18b521" +dependencies = [ + "arrayvec", + "bitflags", + "gfx-auxil", + "gfx-hal", + "libloading 0.7.0", + "log", + "parking_lot 0.11.1", + "range-alloc", + "raw-window-handle", + "smallvec", + "spirv_cross", + "thunderdome", + "winapi 0.3.9", + "wio", +] + +[[package]] +name = "gfx-backend-dx12" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/gfx?rev=27a1dae3796d33d23812f2bb8c7e3b5aea18b521#27a1dae3796d33d23812f2bb8c7e3b5aea18b521" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags", + "d3d12", + "gfx-auxil", + "gfx-hal", + "log", + "parking_lot 0.11.1", + "range-alloc", + "raw-window-handle", + "smallvec", + "spirv_cross", + "thunderdome", + "winapi 0.3.9", +] + +[[package]] +name = "gfx-backend-empty" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/gfx?rev=27a1dae3796d33d23812f2bb8c7e3b5aea18b521#27a1dae3796d33d23812f2bb8c7e3b5aea18b521" +dependencies = [ + "gfx-hal", + "log", + "raw-window-handle", +] + +[[package]] +name = "gfx-backend-gl" +version = "0.8.1" +source = "git+https://github.com/gfx-rs/gfx?rev=27a1dae3796d33d23812f2bb8c7e3b5aea18b521#27a1dae3796d33d23812f2bb8c7e3b5aea18b521" +dependencies = [ + "arrayvec", + "bitflags", + "fxhash", + "gfx-auxil", + "gfx-hal", + "glow", + "js-sys", + "khronos-egl", + "libloading 0.7.0", + "log", + "naga", + "parking_lot 0.11.1", + "raw-window-handle", + "spirv_cross", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gfx-backend-metal" +version = "0.8.1" +source = "git+https://github.com/gfx-rs/gfx?rev=27a1dae3796d33d23812f2bb8c7e3b5aea18b521#27a1dae3796d33d23812f2bb8c7e3b5aea18b521" +dependencies = [ + "arrayvec", + "bitflags", + "block", + "cocoa-foundation", + "copyless", + "foreign-types", + "fxhash", + "gfx-auxil", + "gfx-hal", + "log", + "metal", + "naga", + "objc", + "parking_lot 0.11.1", + "profiling", + "range-alloc", + "raw-window-handle", + "spirv_cross", + "storage-map", +] + +[[package]] +name = "gfx-backend-vulkan" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/gfx?rev=27a1dae3796d33d23812f2bb8c7e3b5aea18b521#27a1dae3796d33d23812f2bb8c7e3b5aea18b521" +dependencies = [ + "arrayvec", + "ash", + "byteorder", + "core-graphics-types", + "gfx-hal", + "inplace_it", + "libloading 0.7.0", + "log", + "naga", + "objc", + "parking_lot 0.11.1", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "gfx-hal" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/gfx?rev=27a1dae3796d33d23812f2bb8c7e3b5aea18b521#27a1dae3796d33d23812f2bb8c7e3b5aea18b521" dependencies = [ "bitflags", - "draw_state", - "log", -] - -[[package]] -name = "gfx_device_gl" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c385fa380c18888633aa27d1e16cbae518469702a2f69dcb5f52d5378bebc" -dependencies = [ - "gfx_core", - "gfx_gl", - "log", -] - -[[package]] -name = "gfx_gl" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d38164670920cfb7491bc0cf6f49f0554bd1c44cdbedc6c78d2bf91691ff5e" -dependencies = [ - "gl_generator", + "naga", + "raw-window-handle", + "thiserror", ] [[package]] @@ -1956,17 +2084,6 @@ dependencies = [ "url", ] -[[package]] -name = "gl_generator" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] - [[package]] name = "glam" version = "0.10.2" @@ -1983,85 +2100,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] -name = "glsl-include" -version = "0.3.1" +name = "glow" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa2afb1631e7ab4543e0dde0e3fc68bb49c58fee89c07f30a26553b1f684ab6" +checksum = "4b80b98efaa8a34fce11d60dd2ce2760d5d83c373cbcc73bb87c2a3a84a54108" dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "glutin" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae1cbb9176b9151c4ce03f012e3cd1c6c18c4be79edeaeb3d99f5d8085c5fa3" -dependencies = [ - "android_glue", - "cgl", - "cocoa 0.23.0", - "core-foundation 0.9.1", - "glutin_egl_sys", - "glutin_emscripten_sys", - "glutin_gles2_sys", - "glutin_glx_sys", - "glutin_wgl_sys", - "lazy_static", - "libloading 0.6.7", - "log", - "objc", - "osmesa-sys", - "parking_lot 0.11.1", - "wayland-client 0.28.5", - "wayland-egl", - "winapi 0.3.9", - "winit", -] - -[[package]] -name = "glutin_egl_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211" -dependencies = [ - "gl_generator", - "winapi 0.3.9", -] - -[[package]] -name = "glutin_emscripten_sys" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1" - -[[package]] -name = "glutin_gles2_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103" -dependencies = [ - "gl_generator", - "objc", -] - -[[package]] -name = "glutin_glx_sys" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351" -dependencies = [ - "gl_generator", - "x11-dl", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696" -dependencies = [ - "gl_generator", + "js-sys", + "slotmap 0.4.0", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -2103,6 +2150,45 @@ dependencies = [ "xi-unicode", ] +[[package]] +name = "gpu-alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76088804bb65a6f3b880bea9306fdaeffb25ebb453105fafa691282ee9fdba" +dependencies = [ + "bitflags", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-descriptor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a70f1e87a3840ed6a3e99e02c2b861e4dbdf26f0d07e38f42ea5aff46cfce2" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + [[package]] name = "guillotiere" version = "0.6.0" @@ -2432,6 +2518,12 @@ dependencies = [ "libc", ] +[[package]] +name = "inplace_it" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" + [[package]] name = "instant" version = "0.1.9" @@ -2553,10 +2645,14 @@ dependencies = [ ] [[package]] -name = "khronos_api" -version = "3.1.0" +name = "khronos-egl" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading 0.7.0", +] [[package]] name = "lazy-bytes-cast" @@ -2832,6 +2928,20 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c12e48c737ee9a55e8bb2352bcde588f79ae308d3529ee888f7cc0f469b5777" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "foreign-types", + "log", + "objc", +] + [[package]] name = "minifb" version = "0.19.1" @@ -2947,13 +3057,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" +[[package]] +name = "naga" +version = "0.4.0" +source = "git+https://github.com/gfx-rs/naga?tag=gfx-25#057d03ad86f18e3bb3866b20901d8d4e892dd3d6" +dependencies = [ + "bit-set", + "bitflags", + "codespan-reporting", + "fxhash", + "log", + "num-traits", + "petgraph 0.5.1", + "rose_tree", + "spirv_headers", + "thiserror", +] + [[package]] name = "native-dialog" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d689b01017db3e350e0e9798d233cca9ad3bf810e7c02b9b55ec06b9ee7dbd8a" dependencies = [ - "cocoa 0.24.0", + "cocoa", "dirs-next", "objc", "objc-foundation", @@ -3028,7 +3155,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -3257,7 +3384,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -3365,7 +3492,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -3377,7 +3504,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -3387,6 +3514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", + "objc_exception", ] [[package]] @@ -3400,6 +3528,15 @@ dependencies = [ "objc_id", ] +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "objc_id" version = "0.1.1" @@ -3457,17 +3594,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "old_school_gfx_glutin_ext" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "450a2a0e6805771787b965af9a552581c9dfc588dc33761c1be690117cd792e1" -dependencies = [ - "gfx_core", - "gfx_device_gl", - "glutin", -] - [[package]] name = "once_cell" version = "1.7.2" @@ -3519,15 +3645,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "osmesa-sys" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" -dependencies = [ - "shared_library", -] - [[package]] name = "owned_ttf_parser" version = "0.6.0" @@ -3634,7 +3751,17 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" dependencies = [ - "fixedbitset", + "fixedbitset 0.1.9", +] + +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset 0.2.0", + "indexmap", ] [[package]] @@ -3654,7 +3781,7 @@ checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -3774,7 +3901,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", "version_check 0.9.3", ] @@ -3819,6 +3946,26 @@ dependencies = [ "unicode-xid 0.2.1", ] +[[package]] +name = "profiling" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a66d5e88679f2720126c11ee29da07a08f094eac52306b066edd7d393752d6" +dependencies = [ + "profiling-procmacros", + "tracy-client", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b83296b4ea7c60800e0832617bb9d274e4f5928abd08acc5a6ae8be0fb15969" +dependencies = [ + "quote 1.0.9", + "syn 1.0.72", +] + [[package]] name = "prometheus" version = "0.12.0" @@ -4018,6 +4165,11 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "range-alloc" +version = "0.1.2" +source = "git+https://github.com/gfx-rs/gfx?rev=27a1dae3796d33d23812f2bb8c7e3b5aea18b521#27a1dae3796d33d23812f2bb8c7e3b5aea18b521" + [[package]] name = "raw-window-handle" version = "0.3.3" @@ -4127,7 +4279,7 @@ dependencies = [ "quote 1.0.9", "refinery-core", "regex", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -4189,6 +4341,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + [[package]] name = "ring" version = "0.16.20" @@ -4231,6 +4389,15 @@ version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84348444bd7ad45729d0c49a4240d7cdc11c9d512c06c5ad1835c1ad4acda6db" +[[package]] +name = "rose_tree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284de9dae38774e2813aaabd7e947b4a6fe9b8c58c2309f754a487cdd50de1c2" +dependencies = [ + "petgraph 0.5.1", +] + [[package]] name = "rusqlite" version = "0.24.2" @@ -4531,7 +4698,7 @@ checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -4553,7 +4720,7 @@ checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -4562,6 +4729,26 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +[[package]] +name = "shaderc" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b8aeaae10b9bda5cba66736a7e265f67698e912e1cc6a4678acba286e22be9" +dependencies = [ + "libc", + "shaderc-sys", +] + +[[package]] +name = "shaderc-sys" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b12d7c62d6732884c9dfab587503fa3a795b108df152415a89da23812d4737e" +dependencies = [ + "cmake", + "libc", +] + [[package]] name = "sharded-slab" version = "0.1.1" @@ -4571,16 +4758,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shared_library" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" -dependencies = [ - "lazy_static", - "libc", -] - [[package]] name = "shell-words" version = "1.0.0" @@ -4625,7 +4802,7 @@ checksum = "d5404c36bd155e41a54276ab6aafedad2fb627e5e5849d36ec439c9ddc044a2f" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -4676,6 +4853,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "slotmap" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c46a3482db8f247956e464d783693ece164ca056e6e67563ee5505bdb86452cd" + [[package]] name = "slotmap" version = "1.0.3" @@ -4766,7 +4949,7 @@ source = "git+https://github.com/amethyst/specs.git?rev=5a9b71035007be0e3574f351 dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -4793,6 +4976,27 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "spirv_cross" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60647fadbf83c4a72f0d7ea67a7ca3a81835cf442b8deae5c134c3e0055b2e14" +dependencies = [ + "cc", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "spirv_headers" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f5b132530b1ac069df335577e3581765995cba5a13995cdbbdbc8fb057c532c" +dependencies = [ + "bitflags", + "num-traits", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -4846,7 +5050,7 @@ dependencies = [ "quote 1.0.9", "serde", "serde_derive", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -4862,7 +5066,7 @@ dependencies = [ "serde_derive", "serde_json", "sha1", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -4871,6 +5075,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "storage-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418bb14643aa55a7841d5303f72cf512cfb323b8cc221d51580500a1ca75206c" +dependencies = [ + "lock_api 0.4.3", +] + [[package]] name = "str-buf" version = "1.0.5" @@ -4916,7 +5129,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -4934,7 +5147,7 @@ dependencies = [ "heck", "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -4962,9 +5175,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.69" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", @@ -5043,7 +5256,7 @@ checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -5055,6 +5268,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "thunderdome" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b4947742c93ece24a0032141d9caa3d853752e694a57e35029dd2bd08673e0" + [[package]] name = "time" version = "0.1.43" @@ -5117,7 +5336,7 @@ checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -5202,7 +5421,7 @@ checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -5514,7 +5733,7 @@ dependencies = [ "serde", "serde_repr", "slab", - "slotmap", + "slotmap 1.0.3", "specs", "specs-idvs", "spin_sleep", @@ -5709,7 +5928,7 @@ version = "0.1.0" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -5796,6 +6015,7 @@ version = "0.9.0" dependencies = [ "backtrace", "bincode", + "bytemuck", "chrono", "conrod_core", "conrod_winit", @@ -5810,12 +6030,8 @@ dependencies = [ "dot_vox", "enum-iterator", "euc", - "gfx", - "gfx_device_gl", - "gfx_gl", + "futures-executor", "gilrs", - "glsl-include", - "glutin", "glyph_brush", "guillotiere", "hashbrown", @@ -5828,13 +6044,14 @@ dependencies = [ "native-dialog", "num 0.4.0", "num_cpus", - "old_school_gfx_glutin_ext", "ordered-float 2.1.1", + "profiling", "rand 0.8.3", "rayon", "rodio", "ron", "serde", + "shaderc", "specs", "specs-idvs", "strum", @@ -5855,6 +6072,8 @@ dependencies = [ "veloren-server", "veloren-voxygen-anim", "veloren-world", + "wgpu", + "wgpu-profiler", "window_clipboard 0.2.0", "winit", "winres", @@ -5864,6 +6083,7 @@ dependencies = [ name = "veloren-voxygen-anim" version = "0.9.0" dependencies = [ + "bytemuck", "find_folder", "lazy_static", "libloading 0.7.0", @@ -6010,7 +6230,7 @@ dependencies = [ "log", "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", "wasm-bindgen-shared", ] @@ -6044,7 +6264,7 @@ checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6123,7 +6343,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.26", "quote 1.0.9", - "syn 1.0.69", + "syn 1.0.72", ] [[package]] @@ -6330,16 +6550,6 @@ dependencies = [ "xcursor", ] -[[package]] -name = "wayland-egl" -version = "0.28.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9461a67930ec16da7a4fd8b50e9ffa23f4417240b43ec84008bd1b2c94421c94" -dependencies = [ - "wayland-client 0.28.5", - "wayland-sys 0.28.5", -] - [[package]] name = "wayland-protocols" version = "0.27.0" @@ -6436,6 +6646,75 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "wgpu" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/wgpu-rs.git?rev=7486bdad64bb5d17b709ecccb41e063469efff88#7486bdad64bb5d17b709ecccb41e063469efff88" +dependencies = [ + "arrayvec", + "js-sys", + "log", + "naga", + "parking_lot 0.11.1", + "raw-window-handle", + "serde", + "smallvec", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=53eab747a32414232be45d47cae8a43a369395d0#53eab747a32414232be45d47cae8a43a369395d0" +dependencies = [ + "arrayvec", + "bitflags", + "cfg_aliases", + "copyless", + "fxhash", + "gfx-backend-dx11", + "gfx-backend-dx12", + "gfx-backend-empty", + "gfx-backend-gl", + "gfx-backend-metal", + "gfx-backend-vulkan", + "gfx-hal", + "gpu-alloc", + "gpu-descriptor", + "log", + "naga", + "parking_lot 0.11.1", + "profiling", + "raw-window-handle", + "ron", + "serde", + "smallvec", + "thiserror", + "wgpu-types", +] + +[[package]] +name = "wgpu-profiler" +version = "0.4.0" +source = "git+https://github.com/Imberflur/wgpu-profiler?tag=wgpu-0.8#b156eb145bc223386ef344860d9b33b3c181650c" +dependencies = [ + "futures", + "wgpu", +] + +[[package]] +name = "wgpu-types" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=53eab747a32414232be45d47cae8a43a369395d0#53eab747a32414232be45d47cae8a43a369395d0" +dependencies = [ + "bitflags", + "serde", +] + [[package]] name = "which" version = "4.1.0" @@ -6530,7 +6809,7 @@ version = "0.24.0" source = "git+https://gitlab.com/veloren/winit.git?branch=macos-test-spiffed#488c511802dfd95ca54f6f76a38547c93c7b02c9" dependencies = [ "bitflags", - "cocoa 0.24.0", + "cocoa", "core-foundation 0.9.1", "core-graphics 0.22.2", "core-video-sys", @@ -6564,6 +6843,15 @@ dependencies = [ "toml", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index dffbf6ba0d..bd0e04061c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,11 +98,6 @@ incremental = true inherits = 'release' debug = 1 -[patch.crates-io] -# macos CI fix isn't merged yet -winit = { git = "https://gitlab.com/veloren/winit.git", branch = "macos-test-spiffed" } -vek = { git = "https://gitlab.com/veloren/vek.git", branch = "fix_intrinsics2" } - [workspace.metadata.nix] systems = ["x86_64-linux"] @@ -113,3 +108,36 @@ key = "veloren-nix.cachix.org-1:zokfKJqVsNV6kI/oJdLF6TYBdNPYGSb+diMVQPn/5Rc=" [workspace.metadata.nix.crateOverride.veloren-network] buildInputs = ["openssl"] nativeBuildInputs = ["pkg-config"] + +[patch.crates-io] +# macos CI fix isn't released yet +winit = { git = "https://gitlab.com/veloren/winit.git", branch = "macos-test-spiffed" } +vek = { git = "https://gitlab.com/veloren/vek.git", branch = "fix_intrinsics2" } +# patch wgpu so we can use wgpu-profiler crate +wgpu = { git = "https://github.com/gfx-rs/wgpu-rs.git", rev = "7486bdad64bb5d17b709ecccb41e063469efff88" } + +# # use the latest fixes in naga (remove when updates trickle down to wgpu-rs) +# naga = { git = "https://github.com/gfx-rs/naga.git", rev = "3a0f0144112ff621dd7f731bf455adf6cab19164" } +# # use the latest fixes in gfx (remove when updates trickle down to wgpu-rs) +# gfx-hal = { git = "https://github.com/gfx-rs/gfx.git", rev = "e305dcca3557923a6a8810162d8dd09cb45a43a6" } +# gfx-backend-empty = { git = "https://github.com/gfx-rs/gfx.git", rev = "e305dcca3557923a6a8810162d8dd09cb45a43a6" } +# gfx-backend-vulkan = { git = "https://github.com/gfx-rs/gfx.git", rev = "e305dcca3557923a6a8810162d8dd09cb45a43a6" } +# gfx-backend-gl = { git = "https://github.com/gfx-rs/gfx.git", rev = "e305dcca3557923a6a8810162d8dd09cb45a43a6" } +# gfx-backend-dx12 = { git = "https://github.com/gfx-rs/gfx.git", rev = "e305dcca3557923a6a8810162d8dd09cb45a43a6" } +# gfx-backend-dx11 = { git = "https://github.com/gfx-rs/gfx.git", rev = "e305dcca3557923a6a8810162d8dd09cb45a43a6" } +# gfx-backend-metal = { git = "https://github.com/gfx-rs/gfx.git", rev = "e305dcca3557923a6a8810162d8dd09cb45a43a6" } + +# # Uncomment this to use a local fork of wgpu (for testing purposes) +# [patch.'https://github.com/gfx-rs/wgpu'] +# wgpu-core = { path = "../wgpu/wgpu-core" } +# wgpu-types = { path = "../wgpu/wgpu-types" } + +# # Uncomment this to use a local fork of gfx-hal (for testing purposes) +# [patch."https://github.com/gfx-rs/gfx"] +# gfx-hal = { path = "../gfx/src/hal" } +# gfx-backend-empty = { path = "../gfx/src/backend/empty" } +# gfx-backend-vulkan = { path = "../gfx/src/backend/vulkan" } +# gfx-backend-gl = { path = "../gfx/src/backend/gl" } +# gfx-backend-dx12 = { path = "../gfx/src/backend/dx12" } +# gfx-backend-dx11 = { path = "../gfx/src/backend/dx11" } +# gfx-backend-metal = { path = "../gfx/src/backend/metal" } diff --git a/assets/voxygen/i18n/en/hud/hud_settings.ron b/assets/voxygen/i18n/en/hud/hud_settings.ron index 037e4868d1..24023e2ba3 100644 --- a/assets/voxygen/i18n/en/hud/hud_settings.ron +++ b/assets/voxygen/i18n/en/hud/hud_settings.ron @@ -10,6 +10,7 @@ "hud.settings.press_behavior.hold": "Hold", "hud.settings.help_window": "Help Window", "hud.settings.debug_info": "Debug Info", + "hud.settings.show_hitboxes": "Show hitboxes", "hud.settings.tips_on_startup": "Tips-On-Startup", "hud.settings.ui_scale": "UI-Scale", "hud.settings.relative_scaling": "Relative Scaling", @@ -57,6 +58,10 @@ "hud.settings.sprites_view_distance": "Sprites View Distance", "hud.settings.figures_view_distance": "Entities View Distance", "hud.settings.maximum_fps": "Maximum FPS", + "hud.settings.present_mode": "Present Mode", + "hud.settings.present_mode.fifo": "Fifo", + "hud.settings.present_mode.mailbox": "Mailbox", + "hud.settings.present_mode.immediate": "Immediate", "hud.settings.fov": "Field of View (deg)", "hud.settings.gamma": "Gamma", "hud.settings.exposure": "Exposure", @@ -76,6 +81,7 @@ "hud.settings.fullscreen_mode": "Fullscreen Mode", "hud.settings.fullscreen_mode.exclusive": "Exclusive", "hud.settings.fullscreen_mode.borderless": "Borderless", + "hud.settings.gpu_profiler": "Enable GPU timing (not supported everywhere)", "hud.settings.particles": "Particles", "hud.settings.lossy_terrain_compression": "Lossy terrain compression", "hud.settings.resolution": "Resolution", diff --git a/assets/voxygen/shaders/antialias/fxaa.glsl b/assets/voxygen/shaders/antialias/fxaa.glsl index c04d82bf02..24ffe84cb9 100644 --- a/assets/voxygen/shaders/antialias/fxaa.glsl +++ b/assets/voxygen/shaders/antialias/fxaa.glsl @@ -1,5 +1,3 @@ -uniform sampler2D src_color; - const float FXAA_SCALE = 1.25; /** @@ -57,17 +55,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //optimized version for mobile, where dependent //texture reads can be a bottleneck -vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 resolution, +vec4 fxaa(texture2D tex, sampler smplr, vec2 fragCoord, vec2 resolution, vec2 v_rgbNW, vec2 v_rgbNE, vec2 v_rgbSW, vec2 v_rgbSE, vec2 v_rgbM) { vec4 color; mediump vec2 inverseVP = vec2(1.0 / resolution.x, 1.0 / resolution.y); - vec3 rgbNW = texture(tex, v_rgbNW).xyz; - vec3 rgbNE = texture(tex, v_rgbNE).xyz; - vec3 rgbSW = texture(tex, v_rgbSW).xyz; - vec3 rgbSE = texture(tex, v_rgbSE).xyz; - vec4 texColor = texture(tex, v_rgbM); + vec3 rgbNW = texture(sampler2D(tex, smplr), v_rgbNW).xyz; + vec3 rgbNE = texture(sampler2D(tex, smplr), v_rgbNE).xyz; + vec3 rgbSW = texture(sampler2D(tex, smplr), v_rgbSW).xyz; + vec3 rgbSE = texture(sampler2D(tex, smplr), v_rgbSE).xyz; + vec4 texColor = texture(sampler2D(tex, smplr), v_rgbM); vec3 rgbM = texColor.xyz; vec3 luma = vec3(0.299, 0.587, 0.114); float lumaNW = dot(rgbNW, luma); @@ -91,11 +89,11 @@ vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 resolution, dir * rcpDirMin)) * inverseVP; vec3 rgbA = 0.5 * ( - texture(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + - texture(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz); + texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + + texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz); vec3 rgbB = rgbA * 0.5 + 0.25 * ( - texture(tex, fragCoord * inverseVP + dir * -0.5).xyz + - texture(tex, fragCoord * inverseVP + dir * 0.5).xyz); + texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * -0.5).xyz + + texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * 0.5).xyz); float lumaB = dot(rgbB, luma); if ((lumaB < lumaMin) || (lumaB > lumaMax)) @@ -119,7 +117,7 @@ void texcoords(vec2 fragCoord, vec2 resolution, } -vec4 aa_apply(sampler2D tex, vec2 fragCoord, vec2 resolution) { +vec4 aa_apply(texture2D tex, sampler smplr, vec2 fragCoord, vec2 resolution) { mediump vec2 v_rgbNW; mediump vec2 v_rgbNE; mediump vec2 v_rgbSW; @@ -133,5 +131,5 @@ vec4 aa_apply(sampler2D tex, vec2 fragCoord, vec2 resolution) { texcoords(scaled_fc, scaled_res, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); //compute FXAA - return fxaa(tex, scaled_fc, scaled_res, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); + return fxaa(tex, smplr, scaled_fc, scaled_res, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); } diff --git a/assets/voxygen/shaders/antialias/msaa-x16.glsl b/assets/voxygen/shaders/antialias/msaa-x16.glsl index 7a23b212a8..df5bbaaa2c 100644 --- a/assets/voxygen/shaders/antialias/msaa-x16.glsl +++ b/assets/voxygen/shaders/antialias/msaa-x16.glsl @@ -1,24 +1,22 @@ -uniform sampler2DMS src_color; - -vec4 aa_apply(sampler2DMS tex, vec2 fragCoord, vec2 resolution) { +vec4 aa_apply(texture2D tex, sampler smplr, vec2 fragCoord, vec2 resolution) { ivec2 texel_coord = ivec2(fragCoord.x, fragCoord.y); - vec4 sample1 = texelFetch(tex, texel_coord, 0); - vec4 sample2 = texelFetch(tex, texel_coord, 1); - vec4 sample3 = texelFetch(tex, texel_coord, 2); - vec4 sample4 = texelFetch(tex, texel_coord, 3); - vec4 sample5 = texelFetch(tex, texel_coord, 4); - vec4 sample6 = texelFetch(tex, texel_coord, 5); - vec4 sample7 = texelFetch(tex, texel_coord, 6); - vec4 sample8 = texelFetch(tex, texel_coord, 7); - vec4 sample9 = texelFetch(tex, texel_coord, 8); - vec4 sample10 = texelFetch(tex, texel_coord, 9); - vec4 sample11 = texelFetch(tex, texel_coord, 10); - vec4 sample12 = texelFetch(tex, texel_coord, 11); - vec4 sample13 = texelFetch(tex, texel_coord, 12); - vec4 sample14 = texelFetch(tex, texel_coord, 13); - vec4 sample15 = texelFetch(tex, texel_coord, 14); - vec4 sample16 = texelFetch(tex, texel_coord, 15); + vec4 sample1 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 0); + vec4 sample2 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 1); + vec4 sample3 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 2); + vec4 sample4 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 3); + vec4 sample5 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 4); + vec4 sample6 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 5); + vec4 sample7 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 6); + vec4 sample8 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 7); + vec4 sample9 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 8); + vec4 sample10 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 9); + vec4 sample11 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 10); + vec4 sample12 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 11); + vec4 sample13 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 12); + vec4 sample14 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 13); + vec4 sample15 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 14); + vec4 sample16 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 15); // Average Samples vec4 msaa_color = ( @@ -27,4 +25,4 @@ vec4 aa_apply(sampler2DMS tex, vec2 fragCoord, vec2 resolution) { ) / 16.0; return msaa_color; -} \ No newline at end of file +} diff --git a/assets/voxygen/shaders/antialias/msaa-x4.glsl b/assets/voxygen/shaders/antialias/msaa-x4.glsl index 8537f5eb4d..f6d2e76841 100644 --- a/assets/voxygen/shaders/antialias/msaa-x4.glsl +++ b/assets/voxygen/shaders/antialias/msaa-x4.glsl @@ -1,15 +1,13 @@ -uniform sampler2DMS src_color; - -vec4 aa_apply(sampler2DMS tex, vec2 fragCoord, vec2 resolution) { +vec4 aa_apply(texture2D tex, sampler smplr, vec2 fragCoord, vec2 resolution) { ivec2 texel_coord = ivec2(fragCoord.x, fragCoord.y); - vec4 sample1 = texelFetch(tex, texel_coord, 0); - vec4 sample2 = texelFetch(tex, texel_coord, 1); - vec4 sample3 = texelFetch(tex, texel_coord, 2); - vec4 sample4 = texelFetch(tex, texel_coord, 3); + vec4 sample1 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 0); + vec4 sample2 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 1); + vec4 sample3 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 2); + vec4 sample4 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 3); // Average Samples vec4 msaa_color = (sample1 + sample2 + sample3 + sample4) / 4.0; return msaa_color; -} \ No newline at end of file +} diff --git a/assets/voxygen/shaders/antialias/msaa-x8.glsl b/assets/voxygen/shaders/antialias/msaa-x8.glsl index d39d824639..cacdc7c4bf 100644 --- a/assets/voxygen/shaders/antialias/msaa-x8.glsl +++ b/assets/voxygen/shaders/antialias/msaa-x8.glsl @@ -1,19 +1,17 @@ -uniform sampler2DMS src_color; - -vec4 aa_apply(sampler2DMS tex, vec2 fragCoord, vec2 resolution) { +vec4 aa_apply(texture2D tex, sampler smplr, vec2 fragCoord, vec2 resolution) { ivec2 texel_coord = ivec2(fragCoord.x, fragCoord.y); - vec4 sample1 = texelFetch(tex, texel_coord, 0); - vec4 sample2 = texelFetch(tex, texel_coord, 1); - vec4 sample3 = texelFetch(tex, texel_coord, 2); - vec4 sample4 = texelFetch(tex, texel_coord, 3); - vec4 sample5 = texelFetch(tex, texel_coord, 4); - vec4 sample6 = texelFetch(tex, texel_coord, 5); - vec4 sample7 = texelFetch(tex, texel_coord, 6); - vec4 sample8 = texelFetch(tex, texel_coord, 7); + vec4 sample1 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 0); + vec4 sample2 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 1); + vec4 sample3 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 2); + vec4 sample4 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 3); + vec4 sample5 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 4); + vec4 sample6 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 5); + vec4 sample7 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 6); + vec4 sample8 = texelFetch(sampler2DMS(tex, smplr), texel_coord, 7); // Average Samples vec4 msaa_color = (sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7 + sample8) / 8.0; return msaa_color; -} \ No newline at end of file +} diff --git a/assets/voxygen/shaders/antialias/none.glsl b/assets/voxygen/shaders/antialias/none.glsl index 50c953b175..2e537bc3f8 100644 --- a/assets/voxygen/shaders/antialias/none.glsl +++ b/assets/voxygen/shaders/antialias/none.glsl @@ -1,5 +1,3 @@ -uniform sampler2D src_color; - -vec4 aa_apply(sampler2D tex, vec2 fragCoord, vec2 resolution) { - return texture(src_color, fragCoord / resolution); -} \ No newline at end of file +vec4 aa_apply(texture2D tex, sampler smplr, vec2 fragCoord, vec2 resolution) { + return texture(sampler2D(tex, smplr), fragCoord / resolution); +} diff --git a/assets/voxygen/shaders/blit-frag.glsl b/assets/voxygen/shaders/blit-frag.glsl new file mode 100644 index 0000000000..d0818b374a --- /dev/null +++ b/assets/voxygen/shaders/blit-frag.glsl @@ -0,0 +1,16 @@ +#version 420 core + +layout(set = 0, binding = 0) +uniform texture2D t_src_color; +layout(set = 0, binding = 1) +uniform sampler s_src_color; + +layout(location = 0) in vec2 uv; + +layout(location = 0) out vec4 tgt_color; + +void main() { + vec4 color = texture(sampler2D(t_src_color, s_src_color), uv); + + tgt_color = vec4(color.rgb, 1); +} diff --git a/assets/voxygen/shaders/blit-vert.glsl b/assets/voxygen/shaders/blit-vert.glsl new file mode 100644 index 0000000000..f6538bd743 --- /dev/null +++ b/assets/voxygen/shaders/blit-vert.glsl @@ -0,0 +1,15 @@ +#version 420 core + +layout(location = 0) out vec2 uv; + +void main() { + // Generate fullscreen triangle + vec2 v_pos = vec2( + float(gl_VertexIndex / 2) * 4.0 - 1.0, + float(gl_VertexIndex % 2) * 4.0 - 1.0 + ); + + uv = (v_pos * vec2(1.0, -1.0) + 1.0) * 0.5; + + gl_Position = vec4(v_pos, 0.0, 1.0); +} diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl index 082765829f..7d02bf0f1d 100644 --- a/assets/voxygen/shaders/clouds-frag.glsl +++ b/assets/voxygen/shaders/clouds-frag.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -22,46 +22,45 @@ #include #include -uniform sampler2D src_depth; +layout(set = 1, binding = 0) +uniform texture2D t_src_color; +layout(set = 1, binding = 1) +uniform sampler s_src_color; -in vec2 f_pos; +layout(set = 1, binding = 2) +uniform texture2D t_src_depth; +layout(set = 1, binding = 3) +uniform sampler s_src_depth; -layout (std140) +layout(location = 0) in vec2 uv; + +layout (std140, set = 1, binding = 4) uniform u_locals { mat4 proj_mat_inv; mat4 view_mat_inv; }; -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; -float depth_at(vec2 uv) { - float buf_depth = texture(src_depth, uv).x; - vec4 clip_space = vec4(uv * 2.0 - 1.0, buf_depth, 1.0); - vec4 view_space = proj_mat_inv * clip_space; - view_space /= view_space.w; - return -view_space.z; -} vec3 wpos_at(vec2 uv) { - float buf_depth = texture(src_depth, uv).x * 2.0 - 1.0; + float buf_depth = texture(sampler2D(t_src_depth, s_src_depth), uv).x; mat4 inv = view_mat_inv * proj_mat_inv;//inverse(all_mat); - vec4 clip_space = vec4(uv * 2.0 - 1.0, buf_depth, 1.0); + vec4 clip_space = vec4((uv * 2.0 - 1.0) * vec2(1, -1), buf_depth, 1.0); vec4 view_space = inv * clip_space; view_space /= view_space.w; - if (buf_depth == 1.0) { + if (buf_depth == 0.0) { vec3 direction = normalize(view_space.xyz); - return direction.xyz * 100000.0 + cam_pos.xyz; + return direction.xyz * 524288.0625 + cam_pos.xyz; } else { return view_space.xyz; } } void main() { - vec2 uv = (f_pos + 1.0) * 0.5; + vec4 color = texture(sampler2D(t_src_color, s_src_color), uv); - vec4 color = texture(src_color, uv); - - // Apply clouds to `aa_color` + // Apply clouds #if (CLOUD_MODE != CLOUD_MODE_NONE) vec3 wpos = wpos_at(uv); float dist = distance(wpos, cam_pos.xyz); diff --git a/assets/voxygen/shaders/clouds-vert.glsl b/assets/voxygen/shaders/clouds-vert.glsl index 35b786997f..933d3a3dc3 100644 --- a/assets/voxygen/shaders/clouds-vert.glsl +++ b/assets/voxygen/shaders/clouds-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -18,12 +18,17 @@ #include -in vec2 v_pos; - -out vec2 f_pos; +layout(location = 0) out vec2 uv; void main() { - f_pos = v_pos; + // Generate fullscreen triangle + vec2 v_pos = vec2( + float(gl_VertexIndex / 2) * 4.0 - 1.0, + float(gl_VertexIndex % 2) * 4.0 - 1.0 + ); - gl_Position = vec4(v_pos, -1.0, 1.0); + // Flip y and transform into 0.0 to 1.0 range + uv = (v_pos * vec2(1.0, -1.0) + 1.0) * 0.5; + + gl_Position = vec4(v_pos, 0.0, 1.0); } diff --git a/assets/voxygen/shaders/debug-frag.glsl b/assets/voxygen/shaders/debug-frag.glsl new file mode 100644 index 0000000000..c681b64bfc --- /dev/null +++ b/assets/voxygen/shaders/debug-frag.glsl @@ -0,0 +1,19 @@ +#version 420 core + +#include + +layout (location = 0) +in vec4 f_color; + +layout (std140, set = 1, binding = 0) +uniform u_locals { + vec4 w_pos; + vec4 w_color; +}; + +layout (location = 0) +out vec4 tgt_color; + +void main() { + tgt_color = f_color; +} diff --git a/assets/voxygen/shaders/debug-vert.glsl b/assets/voxygen/shaders/debug-vert.glsl new file mode 100644 index 0000000000..97d774a642 --- /dev/null +++ b/assets/voxygen/shaders/debug-vert.glsl @@ -0,0 +1,20 @@ +#version 420 core + +#include + +layout (location = 0) +in vec3 v_pos; + +layout (std140, set = 1, binding = 0) +uniform u_locals { + vec4 w_pos; + vec4 w_color; +}; + +layout (location = 0) +out vec4 f_color; + +void main() { + f_color = w_color; + gl_Position = all_mat * vec4((v_pos + w_pos.xyz) - focus_off.xyz, 1); +} diff --git a/assets/voxygen/shaders/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index 0bfb9a5cad..2685fb61a6 100644 --- a/assets/voxygen/shaders/figure-frag.glsl +++ b/assets/voxygen/shaders/figure-frag.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #define FIGURE_SHADER @@ -17,14 +17,17 @@ #define HAS_SHADOW_MAPS #include +#include +#include +#include -in vec3 f_pos; +layout(location = 0) in vec3 f_pos; // in float dummy; // in vec3 f_col; // in float f_ao; // flat in uint f_pos_norm; -flat in vec3 f_norm; -/*centroid */in vec2 f_uv_pos; +layout(location = 1) flat in vec3 f_norm; +/*centroid */layout(location = 2) in vec2 f_uv_pos; // in float f_alt; // in vec4 f_shadow; // in vec3 light_pos[2]; @@ -35,7 +38,10 @@ flat in vec3 f_norm; // const vec4 sun_pos = vec4(0.0); // #endif -uniform sampler2D t_col_light; +layout(set = 3, binding = 0) +uniform texture2D t_col_light; +layout(set = 3, binding = 1) +uniform sampler s_col_light; //struct ShadowLocals { // mat4 shadowMatrices; @@ -47,7 +53,7 @@ uniform sampler2D t_col_light; // ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192]; //}; -layout (std140) +layout (std140, set = 2, binding = 0) uniform u_locals { mat4 model_mat; vec4 highlight_col; @@ -65,16 +71,12 @@ struct BoneData { mat4 normals_mat; }; -layout (std140) +layout (std140, set = 2, binding = 1) uniform u_bones { BoneData bones[16]; }; -#include -#include -#include - -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; void main() { // vec2 texSize = textureSize(t_col_light, 0); @@ -88,8 +90,7 @@ void main() { float f_ao, f_glow; uint material = 0xFFu; - vec3 f_col = greedy_extract_col_light_attr(t_col_light, f_uv_pos, f_ao, f_glow, material); - + vec3 f_col = greedy_extract_col_light_attr(t_col_light, s_col_light, f_uv_pos, f_ao, f_glow, material); // float /*f_light*/f_ao = textureProj(t_col_light, vec3(f_uv_pos, texSize)).a;//1.0;//f_col_light.a * 4.0;// f_light = float(v_col_light & 0x3Fu) / 64.0; // vec3 my_chunk_pos = (vec3((uvec3(f_pos_norm) >> uvec3(0, 9, 18)) & uvec3(0x1FFu)) - 256.0) / 2.0; @@ -131,7 +132,7 @@ void main() { #endif #if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP) - vec4 f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy)); + vec4 f_shadow = textureBicubic(t_horizon, s_horizon, pos_to_tex(f_pos.xy)); float sun_shade_frac = horizon_at2(f_shadow, f_alt, f_pos, sun_dir); #elif (SHADOW_MODE == SHADOW_MODE_NONE) float sun_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, sun_dir); diff --git a/assets/voxygen/shaders/figure-vert.glsl b/assets/voxygen/shaders/figure-vert.glsl index 021b319a11..931cb09984 100644 --- a/assets/voxygen/shaders/figure-vert.glsl +++ b/assets/voxygen/shaders/figure-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -17,15 +17,15 @@ #include #include -in uint v_pos_norm; -in uint v_atlas_pos; +layout(location = 0) in uint v_pos_norm; +layout(location = 1) in uint v_atlas_pos; // in vec3 v_norm; /* in uint v_col; // out vec3 light_pos[2]; in uint v_ao_bone; */ -layout (std140) +layout (std140, set = 2, binding = 0) uniform u_locals { mat4 model_mat; vec4 highlight_col; @@ -40,10 +40,16 @@ uniform u_locals { struct BoneData { mat4 bone_mat; + // This is actually a matrix, but we explicitly rely on being able to index into it + // in column major order, and some shader compilers seem to transpose the matrix to + // a different format when it's copied out of the array. So we shouldn't put it in + // a local variable (I think explicitly marking it as a vec4[4] works, but I'm not + // sure whether it optimizes the same, and in any case the fact that there's a + // format change suggests an actual wasteful copy is happening). mat4 normals_mat; }; -layout (std140) +layout (std140, set = 2, binding = 1) uniform u_bones { // Warning: might not actually be 16 elements long. Don't index out of bounds! BoneData bones[16]; @@ -59,11 +65,11 @@ uniform u_bones { // ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192]; //}; -out vec3 f_pos; +layout(location = 0) out vec3 f_pos; // flat out uint f_pos_norm; -flat out vec3 f_norm; +layout(location = 1) flat out vec3 f_norm; // float dummy; -/*centroid */out vec2 f_uv_pos; +/*centroid */layout(location = 2) out vec2 f_uv_pos; // out vec3 f_col; // out float f_ao; // out float f_alt; @@ -78,16 +84,14 @@ void main() { /* uint bone_idx = (v_ao_bone >> 2) & 0x3Fu; */ uint bone_idx = (v_pos_norm >> 27) & 0xFu; - mat4 bone_mat = bones[bone_idx].bone_mat; - mat4 normals_mat = bones[bone_idx].normals_mat; - mat4 combined_mat = /*model_mat * */bone_mat; + // mat4 combined_mat = model_mat * bone_mat; vec3 pos = (vec3((uvec3(v_pos_norm) >> uvec3(0, 9, 18)) & uvec3(0x1FFu)) - 256.0) / 2.0; // vec4 bone_pos = bones[bone_idx].bone_mat * vec4(pos, 1); f_pos = ( - combined_mat * + bones[bone_idx].bone_mat * vec4(pos, 1.0) ).xyz + (model_pos - focus_off.xyz); @@ -110,7 +114,7 @@ void main() { // vec3 norm = normals[normal_idx]; uint axis_idx = v_atlas_pos & 3u; - vec3 norm = normals_mat[axis_idx].xyz; + vec3 norm = bones[bone_idx].normals_mat[axis_idx].xyz; // norm = normalize(norm); // vec3 norm = norm_mat * vec4(uvec3(1 << axis_idx) & uvec3(0x1u, 0x3u, 0x7u), 1); diff --git a/assets/voxygen/shaders/fluid-frag/cheap.glsl b/assets/voxygen/shaders/fluid-frag/cheap.glsl index 91828bf179..f405fdd039 100644 --- a/assets/voxygen/shaders/fluid-frag/cheap.glsl +++ b/assets/voxygen/shaders/fluid-frag/cheap.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -21,8 +21,8 @@ #include #include -in vec3 f_pos; -flat in uint f_pos_norm; +layout(location = 0) in vec3 f_pos; +layout(location = 1) flat in uint f_pos_norm; // in vec3 f_col; // in float f_light; // in vec3 light_pos[2]; @@ -37,16 +37,14 @@ flat in uint f_pos_norm; // ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192]; // }; -layout (std140) +layout(std140, set = 2, binding = 0) uniform u_locals { vec3 model_offs; float load_time; ivec4 atlas_offs; }; -uniform sampler2D t_waves; - -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; #include #include @@ -92,7 +90,7 @@ void main() { #endif #if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP) - vec4 f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy)); + vec4 f_shadow = textureBicubic(t_horizon, s_horizon, pos_to_tex(f_pos.xy)); float sun_shade_frac = horizon_at2(f_shadow, f_alt, f_pos, sun_dir); #elif (SHADOW_MODE == SHADOW_MODE_NONE) float sun_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, sun_dir); diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl index 1b409c2a53..019e2b0618 100644 --- a/assets/voxygen/shaders/fluid-frag/shiny.glsl +++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -23,8 +23,8 @@ #include #include -in vec3 f_pos; -flat in uint f_pos_norm; +layout(location = 0) in vec3 f_pos; +layout(location = 1) flat in uint f_pos_norm; // in vec3 f_col; // in float f_light; // in vec3 light_pos[2]; @@ -39,16 +39,14 @@ flat in uint f_pos_norm; // ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192]; //}; -layout (std140) +layout(std140, set = 2, binding = 0) uniform u_locals { vec3 model_offs; float load_time; ivec4 atlas_offs; }; -uniform sampler2D t_waves; - -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; #include #include @@ -65,25 +63,25 @@ float wave_height(vec3 pos) { pos *= 0.5; vec3 big_warp = ( - texture(t_noise, fract(pos.xy * 0.03 + timer * 0.01)).xyz * 0.5 + - texture(t_noise, fract(pos.yx * 0.03 - timer * 0.01)).xyz * 0.5 + + texture(sampler2D(t_noise, s_noise), fract(pos.xy * 0.03 + timer * 0.01)).xyz * 0.5 + + texture(sampler2D(t_noise, s_noise), fract(pos.yx * 0.03 - timer * 0.01)).xyz * 0.5 + vec3(0) ); vec3 warp = ( - texture(t_noise, fract(pos.yx * 0.1 + timer * 0.02)).xyz * 0.3 + - texture(t_noise, fract(pos.yx * 0.1 - timer * 0.02)).xyz * 0.3 + + texture(sampler2D(t_noise, s_noise), fract(pos.yx * 0.1 + timer * 0.02)).xyz * 0.3 + + texture(sampler2D(t_noise, s_noise), fract(pos.yx * 0.1 - timer * 0.02)).xyz * 0.3 + vec3(0) ); float height = ( - (texture(t_noise, (pos.xy + pos.z) * 0.03 + big_warp.xy + timer * 0.05).y - 0.5) * 1.0 + - (texture(t_noise, (pos.yx + pos.z) * 0.03 + big_warp.yx - timer * 0.05).y - 0.5) * 1.0 + - (texture(t_noise, (pos.xy + pos.z) * 0.1 + warp.xy + timer * 0.1).x - 0.5) * 0.5 + - (texture(t_noise, (pos.yx + pos.z) * 0.1 + warp.yx - timer * 0.1).x - 0.5) * 0.5 + - (texture(t_noise, (pos.yx + pos.z) * 0.3 + warp.xy * 0.5 + timer * 0.1).x - 0.5) * 0.2 + - (texture(t_noise, (pos.xy + pos.z) * 0.3 + warp.yx * 0.5 - timer * 0.1).x - 0.5) * 0.2 + - (texture(t_noise, (pos.yx + pos.z) * 1.0 + warp.yx * 0.0 - timer * 0.1).x - 0.5) * 0.05 + + (texture(sampler2D(t_noise, s_noise), (pos.xy + pos.z) * 0.03 + big_warp.xy + timer * 0.05).y - 0.5) * 1.0 + + (texture(sampler2D(t_noise, s_noise), (pos.yx + pos.z) * 0.03 + big_warp.yx - timer * 0.05).y - 0.5) * 1.0 + + (texture(sampler2D(t_noise, s_noise), (pos.xy + pos.z) * 0.1 + warp.xy + timer * 0.1).x - 0.5) * 0.5 + + (texture(sampler2D(t_noise, s_noise), (pos.yx + pos.z) * 0.1 + warp.yx - timer * 0.1).x - 0.5) * 0.5 + + (texture(sampler2D(t_noise, s_noise), (pos.yx + pos.z) * 0.3 + warp.xy * 0.5 + timer * 0.1).x - 0.5) * 0.2 + + (texture(sampler2D(t_noise, s_noise), (pos.xy + pos.z) * 0.3 + warp.yx * 0.5 - timer * 0.1).x - 0.5) * 0.2 + + (texture(sampler2D(t_noise, s_noise), (pos.yx + pos.z) * 1.0 + warp.yx * 0.0 - timer * 0.1).x - 0.5) * 0.05 + 0.0 ); @@ -191,7 +189,7 @@ void main() { /* vec3 sun_dir = get_sun_dir(time_of_day.x); vec3 moon_dir = get_moon_dir(time_of_day.x); */ #if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP) - vec4 f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy)); + vec4 f_shadow = textureBicubic(t_horizon, s_horizon, pos_to_tex(f_pos.xy)); float sun_shade_frac = horizon_at2(f_shadow, f_alt, f_pos, sun_dir); #elif (SHADOW_MODE == SHADOW_MODE_NONE) float sun_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, sun_dir); diff --git a/assets/voxygen/shaders/fluid-vert.glsl b/assets/voxygen/shaders/fluid-vert.glsl index 6802ab11b9..0d68559912 100644 --- a/assets/voxygen/shaders/fluid-vert.glsl +++ b/assets/voxygen/shaders/fluid-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -20,10 +20,10 @@ #include #include -in uint v_pos_norm; +layout(location = 0) in uint v_pos_norm; // in uint v_col_light; -layout (std140) +layout(std140, set = 2, binding = 0) uniform u_locals { vec3 model_offs; float load_time; @@ -40,8 +40,8 @@ uniform u_locals { // ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192]; // }; -out vec3 f_pos; -flat out uint f_pos_norm; +layout(location = 0) out vec3 f_pos; +layout(location = 1) flat out uint f_pos_norm; // out vec3 f_col; // out float f_light; // out vec3 light_pos[2]; diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index 3e11d10810..c31e3beca3 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -34,7 +34,7 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission) { // Mist sits close to the ground in valleys (TODO: use base_alt to put it closer to water) float mist_min_alt = 0.5; #if (CLOUD_MODE >= CLOUD_MODE_MEDIUM) - mist_min_alt = (texture(t_noise, pos.xy / 50000.0).x - 0.5) * 1.5 + 0.5; + mist_min_alt = (textureLod(sampler2D(t_noise, s_noise), pos.xy / 50000.0, 0).x - 0.5) * 1.5 + 0.5; #endif mist_min_alt = view_distance.z * 1.5 * (1.0 + mist_min_alt * 0.5) + alt * 0.5 + 250; const float MIST_FADE_HEIGHT = 1000; @@ -146,9 +146,9 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission) { #if (CLOUD_MODE >= CLOUD_MODE_MEDIUM) emission_alt += (noise_3d(vec3(wind_pos.xy * 0.0005 + cloud_tendency * 0.2, emission_alt * 0.0001 + time_of_day.x * 0.001)) - 0.5) * 1000; #endif - float tail = (texture(t_noise, wind_pos.xy * 0.00005).x - 0.5) * 4 + (pos.z - emission_alt) * 0.0001; + float tail = (textureLod(sampler2D(t_noise, s_noise), wind_pos.xy * 0.00005, 0).x - 0.5) * 4 + (pos.z - emission_alt) * 0.0001; vec3 emission_col = vec3(0.8 + tail * 1.5, 0.5 - tail * 0.2, 0.3 + tail * 0.2); - float emission_nz = max(pow(texture(t_noise, wind_pos.xy * 0.000015).x, 8), 0.01) * 0.25 / (10.0 + abs(pos.z - emission_alt) / 80); + float emission_nz = max(pow(textureLod(sampler2D(t_noise, s_noise), wind_pos.xy * 0.000015, 0).x, 8), 0.01) * 0.25 / (10.0 + abs(pos.z - emission_alt) / 80); emission = emission_col * emission_nz * emission_strength * max(sun_dir.z, 0) * 500000 / (1000.0 + abs(pos.z - emission_alt) * 0.1); } @@ -196,7 +196,7 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of // improves visual quality for low cloud settings float splay = 1.0; #if (CLOUD_MODE == CLOUD_MODE_MINIMAL) - splay += (texture(t_noise, vec2(atan2(dir.x, dir.y) * 2 / PI, dir.z) * 5.0 - time_of_day * 0.00005).x - 0.5) * 0.025 / (1.0 + pow(dir.z, 2) * 10); + splay += (textureLod(sampler2D(t_noise, s_noise), vec2(atan2(dir.x, dir.y) * 2 / PI, dir.z) * 5.0 - time_of_day * 0.00005, 0).x - 0.5) * 0.025 / (1.0 + pow(dir.z, 2) * 10); #endif /* const float RAYLEIGH = 0.25; */ @@ -218,12 +218,13 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of cdist = step_to_dist(trunc(dist_to_step(cdist - 0.25, quality)), quality); vec3 emission; - vec4 sample = cloud_at(origin + dir * ldist * splay, ldist, emission); + // `sample` is a reserved keyword + vec4 sample_ = cloud_at(origin + dir * ldist * splay, ldist, emission); - vec2 density_integrals = max(sample.zw, vec2(0)); + vec2 density_integrals = max(sample_.zw, vec2(0)); - float sun_access = max(sample.x, 0); - float moon_access = max(sample.y, 0); + float sun_access = max(sample_.x, 0); + float moon_access = max(sample_.y, 0); float cloud_scatter_factor = density_integrals.x; float global_scatter_factor = density_integrals.y; diff --git a/assets/voxygen/shaders/include/globals.glsl b/assets/voxygen/shaders/include/globals.glsl index 6b3c8373be..cc75645b39 100644 --- a/assets/voxygen/shaders/include/globals.glsl +++ b/assets/voxygen/shaders/include/globals.glsl @@ -1,5 +1,7 @@ -layout (std140) -uniform u_globals { +#ifndef GLOBALS_GLSL +#define GLOBALS_GLSL + +layout(std140, set = 0, binding = 0) uniform u_globals { mat4 view_mat; mat4 proj_mat; mat4 all_mat; @@ -22,6 +24,7 @@ uniform u_globals { // 1 - ThirdPerson uint cam_mode; float sprite_render_distance; + float globals_dummy; // Fix alignment. }; // Specifies the pattern used in the player dithering @@ -33,3 +36,5 @@ mat4 threshold_matrix = mat4( ); float distance_divider = 2; float shadow_dithering = 0.5; + +#endif diff --git a/assets/voxygen/shaders/include/light.glsl b/assets/voxygen/shaders/include/light.glsl index 8c64bd037d..2567109c10 100644 --- a/assets/voxygen/shaders/include/light.glsl +++ b/assets/voxygen/shaders/include/light.glsl @@ -7,16 +7,17 @@ struct Light { // mat4 light_proj; }; -layout (std140) +layout (std140, set = 0, binding = 3) uniform u_lights { - Light lights[31]; + // TODO: insert light max count constant here when loading the shaders + Light lights[20]; }; struct Shadow { vec4 shadow_pos_radius; }; -layout (std140) +layout (std140, set = 0, binding = 4) uniform u_shadows { Shadow shadows[24]; }; diff --git a/assets/voxygen/shaders/include/lod.glsl b/assets/voxygen/shaders/include/lod.glsl index 2644c2a4fe..74f73849cf 100644 --- a/assets/voxygen/shaders/include/lod.glsl +++ b/assets/voxygen/shaders/include/lod.glsl @@ -1,15 +1,20 @@ +#ifndef LOD_GLSL +#define LOD_GLSL + #include #include #include -uniform sampler2D t_alt; -uniform sampler2D t_horizon; +layout(set = 0, binding = 5) uniform texture2D t_alt; +layout(set = 0, binding = 6) uniform sampler s_alt; +layout(set = 0, binding = 7) uniform texture2D t_horizon; +layout(set = 0, binding = 8) uniform sampler s_horizon; const float MIN_SHADOW = 0.33; -vec2 pos_to_uv(sampler2D sampler, vec2 pos) { +vec2 pos_to_uv(texture2D tex, sampler s, vec2 pos) { // Want: (pixel + 0.5) / W - vec2 texSize = textureSize(sampler, 0); + vec2 texSize = textureSize(sampler2D(tex, s), 0); vec2 uv_pos = (focus_off.xy + pos + 16) / (32.0 * texSize); return vec2(uv_pos.x, /*1.0 - */uv_pos.y); } @@ -32,8 +37,9 @@ vec4 cubic(float v) { } // NOTE: We assume the sampled coordinates are already in "texture pixels". -vec4 textureBicubic(sampler2D sampler, vec2 texCoords) { - vec2 texSize = textureSize(sampler, 0); +vec4 textureBicubic(texture2D tex, sampler sampl, vec2 texCoords) { + // TODO: remove all textureSize calls and replace with constants + vec2 texSize = textureSize(sampler2D(tex, sampl), 0); vec2 invTexSize = 1.0 / texSize; /* texCoords.y = texSize.y - texCoords.y; */ @@ -56,10 +62,57 @@ vec4 textureBicubic(sampler2D sampler, vec2 texCoords) { /* // Correct for map rotaton. offset.zw = 1.0 - offset.zw; */ - vec4 sample0 = texture(sampler, offset.xz); - vec4 sample1 = texture(sampler, offset.yz); - vec4 sample2 = texture(sampler, offset.xw); - vec4 sample3 = texture(sampler, offset.yw); + vec4 sample0 = texture(sampler2D(tex, sampl), offset.xz); + vec4 sample1 = texture(sampler2D(tex, sampl), offset.yz); + vec4 sample2 = texture(sampler2D(tex, sampl), offset.xw); + vec4 sample3 = texture(sampler2D(tex, sampl), offset.yw); + // vec4 sample0 = texelFetch(sampler, offset.xz, 0); + // vec4 sample1 = texelFetch(sampler, offset.yz, 0); + // vec4 sample2 = texelFetch(sampler, offset.xw, 0); + // vec4 sample3 = texelFetch(sampler, offset.yw, 0); + + float sx = s.x / (s.x + s.y); + float sy = s.z / (s.z + s.w); + + return mix( + mix(sample3, sample2, sx), mix(sample1, sample0, sx) + , sy); +} + +// 16 bit version (each of the 2 8-bit components are combined after bilinear sampling) +// NOTE: We assume the sampled coordinates are already in "texture pixels". +vec2 textureBicubic16(texture2D tex, sampler sampl, vec2 texCoords) { + vec2 texSize = textureSize(sampler2D(tex, sampl), 0); + vec2 invTexSize = 1.0 / texSize; + /* texCoords.y = texSize.y - texCoords.y; */ + + texCoords = texCoords/* * texSize */ - 0.5; + + + vec2 fxy = fract(texCoords); + texCoords -= fxy; + + vec4 xcubic = cubic(fxy.x); + vec4 ycubic = cubic(fxy.y); + + vec4 c = texCoords.xxyy + vec2 (-0.5, +1.5).xyxy; + // vec4 c = texCoords.xxyy + vec2 (-1, +1).xyxy; + + vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw); + vec4 offset = c + vec4 (xcubic.yw, ycubic.yw) / s; + + offset *= invTexSize.xxyy; + /* // Correct for map rotaton. + offset.zw = 1.0 - offset.zw; */ + + vec4 sample0_v4 = texture(sampler2D(tex, sampl), offset.xz); + vec4 sample1_v4 = texture(sampler2D(tex, sampl), offset.yz); + vec4 sample2_v4 = texture(sampler2D(tex, sampl), offset.xw); + vec4 sample3_v4 = texture(sampler2D(tex, sampl), offset.yw); + vec2 sample0 = sample0_v4.rb / 256.0 + sample0_v4.ga; + vec2 sample1 = sample1_v4.rb / 256.0 + sample1_v4.ga; + vec2 sample2 = sample2_v4.rb / 256.0 + sample2_v4.ga; + vec2 sample3 = sample3_v4.rb / 256.0 + sample3_v4.ga; // vec4 sample0 = texelFetch(sampler, offset.xz, 0); // vec4 sample1 = texelFetch(sampler, offset.yz, 0); // vec4 sample2 = texelFetch(sampler, offset.xw, 0); @@ -74,8 +127,9 @@ vec4 textureBicubic(sampler2D sampler, vec2 texCoords) { } float alt_at(vec2 pos) { - return (/*round*/(texture/*textureBicubic*/(t_alt, pos_to_uv(t_alt, pos)).r * (/*1300.0*//*1278.7266845703125*/view_distance.w)) + /*140.0*/view_distance.z - focus_off.z); - //+ (texture(t_noise, pos * 0.002).x - 0.5) * 64.0; + vec4 alt_sample = textureLod/*textureBicubic16*/(sampler2D(t_alt, s_alt), pos_to_uv(t_alt, s_alt, pos), 0); + return (/*round*/((alt_sample.r / 256.0 + alt_sample.g) * (/*1300.0*//*1278.7266845703125*/view_distance.w)) + /*140.0*/view_distance.z - focus_off.z); + //+ (texture(t_noise, pos * 0.002).x - 0.5) * 64.0; // return 0.0 // + pow(texture(t_noise, pos * 0.00005).x * 1.4, 3.0) * 1000.0 @@ -88,7 +142,7 @@ float alt_at_real(vec2 pos) { // #if (FLUID_MODE == FLUID_MODE_CHEAP) // return alt_at(pos); // #elif (FLUID_MODE == FLUID_MODE_SHINY) - return (/*round*/(textureBicubic(t_alt, pos_to_tex(pos)).r * (/*1300.0*//*1278.7266845703125*/view_distance.w)) + /*140.0*/view_distance.z - focus_off.z); + return (/*round*/(textureBicubic16(t_alt, s_alt, pos_to_tex(pos)).r * (/*1300.0*//*1278.7266845703125*/view_distance.w)) + /*140.0*/view_distance.z - focus_off.z); // #endif //+ (texture(t_noise, pos * 0.002).x - 0.5) * 64.0; @@ -204,7 +258,7 @@ vec2 splay(vec2 pos) { const float SQRT_2 = sqrt(2.0) / 2.0; // /const float CBRT_2 = cbrt(2.0) / 2.0; // vec2 splayed = pos * (view_distance.x * SQRT_2 + pow(len * 0.5, 3.0) * (SPLAY_MULT - view_distance.x)); - vec2 splayed = pos * (view_distance.x * SQRT_2 + len_pow * (textureSize(t_alt, 0) * 32.0/* - view_distance.x*/)); + vec2 splayed = pos * (view_distance.x * SQRT_2 + len_pow * (textureSize(sampler2D(t_alt, s_alt), 0) * 32.0/* - view_distance.x*/)); if (abs(pos.x) > 0.99 || abs(pos.y) > 0.99) { splayed *= 10.0; } @@ -280,13 +334,18 @@ vec3 lod_pos(vec2 pos, vec2 focus_pos) { } #ifdef HAS_LOD_FULL_INFO -uniform sampler2D t_map; +layout(set = 0, binding = 10) +uniform texture2D t_map; +layout(set = 0, binding = 11) +uniform sampler s_map; vec3 lod_col(vec2 pos) { //return vec3(0, 0.5, 0); // return /*linear_to_srgb*/vec3(alt_at(pos), textureBicubic(t_map, pos_to_tex(pos)).gb); - return /*linear_to_srgb*/(textureBicubic(t_map, pos_to_tex(pos)).rgb) + return /*linear_to_srgb*/(textureBicubic(t_map, s_map, pos_to_tex(pos)).rgb) ;//+ (texture(t_noise, pos * 0.04 + texture(t_noise, pos * 0.005).xy * 2.0 + texture(t_noise, pos * 0.06).xy * 0.6).x - 0.5) * 0.1; //+ (texture(t_noise, pos * 0.04 + texture(t_noise, pos * 0.005).xy * 2.0 + texture(t_noise, pos * 0.06).xy * 0.6).x - 0.5) * 0.1; } #endif + +#endif diff --git a/assets/voxygen/shaders/include/random.glsl b/assets/voxygen/shaders/include/random.glsl index 6f337fee35..2b6347b71c 100644 --- a/assets/voxygen/shaders/include/random.glsl +++ b/assets/voxygen/shaders/include/random.glsl @@ -1,4 +1,8 @@ -uniform sampler2D t_noise; +#ifndef RANDOM_GLSL +#define RANDOM_GLSL + +layout(set = 0, binding = 1) uniform texture2D t_noise; +layout(set = 0, binding = 2) uniform sampler s_noise; float hash(vec4 p) { p = fract(p * 0.3183099 + 0.1) - fract(p + 23.22121); @@ -28,7 +32,7 @@ float hash_fast(uvec3 q) // 2D, but using shifted 2D textures float noise_2d(vec2 pos) { - return texture(t_noise, pos).x; + return textureLod(sampler2D(t_noise, s_noise), pos, 0).x; } // 3D, but using shifted 2D textures @@ -37,7 +41,7 @@ float noise_3d(vec3 pos) { uint z = uint(trunc(pos.z)); vec2 offs0 = vec2(hash_one(z), hash_one(z + 73u)); vec2 offs1 = vec2(hash_one(z + 1u), hash_one(z + 1u + 73u)); - return mix(texture(t_noise, pos.xy + offs0).x, texture(t_noise, pos.xy + offs1).x, fract(pos.z)); + return mix(textureLod(sampler2D(t_noise, s_noise), pos.xy + offs0, 0).x, textureLod(sampler2D(t_noise, s_noise), pos.xy + offs1, 0).x, fract(pos.z)); } // 3D version of `snoise` @@ -100,3 +104,4 @@ vec3 smooth_rand(vec3 pos, float lerp_axis) { vec3 r1 = rand_perm_3(vec3(pos.x, pos.y, pos.z) + floor(lerp_axis + 1.0)); return r0 + (r1 - r0) * fract(lerp_axis); } +#endif diff --git a/assets/voxygen/shaders/include/shadows.glsl b/assets/voxygen/shaders/include/shadows.glsl index 3f4b4a0ceb..bd65dd3b5c 100644 --- a/assets/voxygen/shaders/include/shadows.glsl +++ b/assets/voxygen/shaders/include/shadows.glsl @@ -1,22 +1,29 @@ +#ifndef SHADOWS_GLSL +#define SHADOWS_GLSL + #ifdef HAS_SHADOW_MAPS - #if (SHADOW_MODE == SHADOW_MODE_MAP) -struct ShadowLocals { +#if (SHADOW_MODE == SHADOW_MODE_MAP) +layout (std140, set = 0, binding = 9) +uniform u_light_shadows { mat4 shadowMatrices; mat4 texture_mat; }; -layout (std140) -uniform u_light_shadows { - ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192]; -}; - -uniform sampler2DShadow t_directed_shadow_maps; +// Use with sampler2DShadow +layout(set = 1, binding = 2) +uniform texture2D t_directed_shadow_maps; +layout(set = 1, binding = 3) +uniform samplerShadow s_directed_shadow_maps; // uniform sampler2DArrayShadow t_directed_shadow_maps; // uniform samplerCubeArrayShadow t_shadow_maps; // uniform samplerCubeArray t_shadow_maps; -uniform samplerCubeShadow t_point_shadow_maps; +// Use with samplerCubeShadow +layout(set = 1, binding = 0) +uniform textureCube t_point_shadow_maps; +layout(set = 1, binding = 1) +uniform samplerShadow s_point_shadow_maps; // uniform samplerCube t_shadow_maps; // uniform sampler2DArray t_directed_shadow_maps; @@ -35,9 +42,13 @@ float VectorToDepth (vec3 Vec) // float NormZComp = (screen_res.w+screen_res.z) / (screen_res.w-screen_res.z) - (2*screen_res.w*screen_res.z)/(screen_res.w-screen_res.z)/LocalZcomp; // float NormZComp = 1.0 - shadow_proj_factors.y / shadow_proj_factors.x / LocalZcomp; + // -(1 + 2n/(f-n)) - 2(1 + n/(f-n)) * n/z + // -(1 + n/(f-n)) - (1 + n/(f-n)) * n/z + // f/(f-n) - fn/(f-n)/z float NormZComp = shadow_proj_factors.x - shadow_proj_factors.y / LocalZcomp; // NormZComp = -1000.0 / (NormZComp + 10000.0); - return (NormZComp + 1.0) * 0.5; + // return (NormZComp + 1.0) * 0.5; + return NormZComp; // float NormZComp = length(LocalZcomp); // NormZComp = -NormZComp / screen_res.w; @@ -64,7 +75,9 @@ float ShadowCalculationPoint(uint lightIndex, vec3 fragToLight, vec3 fragNorm, / { float currentDepth = VectorToDepth(fragToLight);// + bias; - float visibility = texture(t_point_shadow_maps, vec4(fragToLight, currentDepth));// / (screen_res.w/* - screen_res.z*/)/*1.0 -bias*//*-(currentDepth - bias) / screen_res.w*//*-screen_res.w*/); + // currentDepth = -currentDepth * 0.5 + 0.5; + + float visibility = texture(samplerCubeShadow(t_point_shadow_maps, s_point_shadow_maps), vec4(fragToLight, currentDepth));// / (screen_res.w/* - screen_res.z*/)/*1.0 -bias*//*-(currentDepth - bias) / screen_res.w*//*-screen_res.w*/); /* if (visibility == 1.0 || visibility == 0.0) { return visibility; } */ @@ -154,10 +167,45 @@ float ShadowCalculationDirected(in vec3 fragPos)//in vec4 /*light_pos[2]*/sun_po } */ // vec3 fragPos = sun_pos.xyz;// / sun_pos.w;//light_pos[lightIndex].xyz; // sun_pos.z += sun_pos.w * bias; - mat4 texture_mat = shadowMats[0].texture_mat; - vec4 sun_pos = texture_mat * vec4(fragPos, 1.0); - // sun_pos.z -= sun_pos.w * bias; - float visibility = textureProj(t_directed_shadow_maps, sun_pos); + vec4 sun_pos = texture_mat/*shadowMatrices*/ * vec4(fragPos, 1.0); + // sun_pos.xy = 0.5 * sun_pos.w + sun_pos.xy * 0.5; + // sun_pos.xy = sun_pos.ww - sun_pos.xy; + // sun_pos.xyz /= abs(sun_pos.w); + // sun_pos.w = sign(sun_pos.w); + // sun_pos.xy = (sun_pos.xy + 1.0) * 0.5; + // vec4 orig_pos = warpViewMat * lightViewMat * vec4(fragPos, 1.0); + // + // vec4 shadow_pos; + // shadow_pos.xyz = (warpProjMat * orig_pos).xyz: + // shadow_pos.w = orig_pos.y; + // + // sun_pos.xy = 0.5 * (shadow_pos.xy + shadow_pos.w) = 0.5 * (shadow_pos.xy + orig_pos.yy); + // sun_pos.z = shadow_pos.z; + // + // sun_pos.w = sign(shadow_pos.w) = sign(orig_pos.y); + // sun_pos.xyz = sun_pos.xyz / shadow_pos.w = vec3(0.5 * shadow_pos.xy / orig_pos.yy + 0.5, shadow_pos.z / orig_pos.y) + // = vec3(0.5 * (2.0 * warp_pos.xy / orig_pos.yy - (max_warp_pos + min_warp_pos).xy) / (max_warp_pos - min_warp_pos).xy + 0.5, + // -(warp_pos.z / orig_pos.y - min_warp_pos.z) / (max_warp_pos - min_warp_pos).z ) + // = vec3((warp_pos.x / orig_pos.y - min_warp_pos.x) / (max_warp_pos - min_warp_pos).x, + // (warp_pos.y / orig_pos.y - min_warp_pos.y) / (max_warp_pos - min_warp_pos).y, + // -(warp_pos.z / orig_pos.y - min_warp_pos.z) / (max_warp_pos - min_warp_pos).z ) + // = vec3((near * orig_pos.x / orig_pos.y - min_warp_pos.x) / (max_warp_pos - min_warp_pos).x, + // (((far+near) - 2.0 * near * far / orig_pos.y)/(far-near) - min_warp_pos.y) / (max_warp_pos - min_warp_pos).y, + // -(near * orig_pos.z / orig_pos.y - min_warp_pos.z) / (max_warp_pos - min_warp_pos).z ) + // = vec3((near * orig_pos.x / orig_pos.y - min_warp_pos.x) / (max_warp_pos - min_warp_pos).x, + // (2.0 * (1.0 - far / orig_pos.y)*near/(far-near) + 1.0 - min_warp_pos.y) / (max_warp_pos - min_warp_pos).y, + // -(near * orig_pos.z / orig_pos.y - min_warp_pos.z) / (max_warp_pos - min_warp_pos).z ) + // = vec3((near * orig_pos.x / orig_pos.y - min_warp_pos.x) / (max_warp_pos - min_warp_pos).x, + // (2.0 * (1.0 - far / orig_pos.y)*near/(far-near) + 1.0 - 0.0) / (1.0 - 0.0), + // -(near * orig_pos.z / orig_pos.y - min_warp_pos.z) / (max_warp_pos - min_warp_pos).z ) + // = vec3((near * orig_pos.x / orig_pos.y - min_warp_pos.x) / (max_warp_pos - min_warp_pos).x, + // 2.0 * (1.0 - far / orig_pos.y)*near/(far-near) + 1.0, + // -(near * orig_pos.z / orig_pos.y - min_warp_pos.z) / (max_warp_pos - min_warp_pos).z ) + // + // orig_pos.y = n: warp_pos.y = 2*(1-f/n)*n/(f-n) + 1 = 2*(n-f)/(f-n) + 1 = 2 * -1 + 1 = -1, sun_pos.y = (-1 - -1) / 2 = 0 + // orig_pos.y = f: warp_pos.y = 2*(1-f/f)*n/(f-n) + 1 = 2*(1-1)*n/(f-n) + 1 = 2 * 0 * n/(f-n) + 1 = 1, sun_pos.y = (1 - -1) / 2 = 1 + // + float visibility = textureProj(sampler2DShadow(t_directed_shadow_maps, s_directed_shadow_maps), sun_pos); /* float visibilityLeft = textureProj(t_directed_shadow_maps, sun_shadow.texture_mat * vec4(fragPos + vec3(0.0, -diskRadius, 0.0), 1.0)); float visibilityRight = textureProj(t_directed_shadow_maps, sun_shadow.texture_mat * vec4(fragPos + vec3(0.0, diskRadius, 0.0), 1.0)); */ // float nearVisibility = textureProj(t_directed_shadow_maps + vec3(0.001, sun_pos)); @@ -216,3 +264,5 @@ float ShadowCalculationPoint(uint lightIndex, vec3 fragToLight, vec3 fragNorm, / return 1.0; } #endif + +#endif diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl index 41ac27e511..b44c95d522 100644 --- a/assets/voxygen/shaders/include/sky.glsl +++ b/assets/voxygen/shaders/include/sky.glsl @@ -1,3 +1,6 @@ +#ifndef SKY_GLSL +#define SKY_GLSL + #include #include #include @@ -84,7 +87,7 @@ vec2 wind_offset = vec2(time_of_day.x * wind_speed); float cloud_scale = view_distance.z / 150.0; float cloud_tendency_at(vec2 pos) { - float nz = texture(t_noise, (pos + wind_offset) / 60000.0 / cloud_scale).x - 0.3; + float nz = textureLod(sampler2D(t_noise, s_noise), (pos + wind_offset) / 60000.0 / cloud_scale, 0).x - 0.3; nz = pow(clamp(nz, 0, 1), 3); return nz; } @@ -425,7 +428,7 @@ vec3 get_sky_light(vec3 dir, float time_of_day, bool with_stars) { mix( SKY_DUSK_TOP, SKY_NIGHT_TOP, - max(pow(sun_dir.z, 0.2), 0) + pow(max(sun_dir.z, 0.0), 0.2) ) + star, SKY_DAY_TOP, max(-sun_dir.z, 0) @@ -434,7 +437,7 @@ vec3 get_sky_light(vec3 dir, float time_of_day, bool with_stars) { vec3 sky_mid = mix( mix( SKY_DUSK_MID, SKY_NIGHT_MID, - max(pow(sun_dir.z, 0.1), 0) + pow(max(sun_dir.z, 0.0), 0.1) ), SKY_DAY_MID, max(-sun_dir.z, 0) @@ -444,7 +447,7 @@ vec3 get_sky_light(vec3 dir, float time_of_day, bool with_stars) { mix( SKY_DUSK_BOT, SKY_NIGHT_BOT, - max(pow(sun_dir.z, 0.2), 0) + pow(max(sun_dir.z, 0.0), 0.2) ), SKY_DAY_BOT, max(-sun_dir.z, 0) @@ -640,3 +643,5 @@ vec3 illuminate(float max_light, vec3 view_dir, /*vec3 max_light, */vec3 emitted // float sum_col = color.r + color.g + color.b; // return /*srgb_to_linear*/(/*0.5*//*0.125 * */vec3(pow(color.x, gamma), pow(color.y, gamma), pow(color.z, gamma))); } + +#endif diff --git a/assets/voxygen/shaders/include/srgb.glsl b/assets/voxygen/shaders/include/srgb.glsl index c978b4febb..118221a137 100644 --- a/assets/voxygen/shaders/include/srgb.glsl +++ b/assets/voxygen/shaders/include/srgb.glsl @@ -1,3 +1,5 @@ +#ifndef SRGB_GLSL +#define SRGB_GLSL // Linear RGB, attenuation coefficients for water at roughly R, G, B wavelengths. // See https://en.wikipedia.org/wiki/Electromagnetic_absorption_by_water const vec3 MU_WATER = vec3(0.6, 0.04, 0.01); @@ -618,8 +620,8 @@ vec3 compute_attenuation_point(vec3 wpos, vec3 ray_dir, vec3 mu, float surface_a //} //#endif -vec3 greedy_extract_col_light_attr(sampler2D t_col_light, vec2 f_uv_pos, out float f_light, out float f_glow, out uint f_attr) { - uvec4 f_col_light = uvec4(texelFetch(t_col_light, ivec2(f_uv_pos), 0) * 255); +vec3 greedy_extract_col_light_attr(texture2D t_col_light, sampler s_col_light, vec2 f_uv_pos, out float f_light, out float f_glow, out uint f_attr) { + uvec4 f_col_light = uvec4(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos), 0) * 255); vec3 f_col = vec3( float(((f_col_light.r & 0x7u) << 1u) | (f_col_light.b & 0xF0u)), float(f_col_light.a), @@ -628,9 +630,9 @@ vec3 greedy_extract_col_light_attr(sampler2D t_col_light, vec2 f_uv_pos, out flo // TODO: Figure out how to use `texture` and modulation to avoid needing to do manual filtering vec2 light_00 = vec2(uvec2(f_col_light.rg) >> 3u); - vec2 light_10 = vec2(uvec2(texelFetch(t_col_light, ivec2(f_uv_pos) + ivec2(1, 0), 0).rg * 255.0) >> 3u); - vec2 light_01 = vec2(uvec2(texelFetch(t_col_light, ivec2(f_uv_pos) + ivec2(0, 1), 0).rg * 255.0) >> 3u); - vec2 light_11 = vec2(uvec2(texelFetch(t_col_light, ivec2(f_uv_pos) + ivec2(1, 1), 0).rg * 255.0) >> 3u); + vec2 light_10 = vec2(uvec2(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos) + ivec2(1, 0), 0).rg * 255.0) >> 3u); + vec2 light_01 = vec2(uvec2(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos) + ivec2(0, 1), 0).rg * 255.0) >> 3u); + vec2 light_11 = vec2(uvec2(texelFetch(sampler2D(t_col_light, s_col_light), ivec2(f_uv_pos) + ivec2(1, 1), 0).rg * 255.0) >> 3u); vec2 light_0 = mix(light_00, light_01, fract(f_uv_pos.y)); vec2 light_1 = mix(light_10, light_11, fract(f_uv_pos.y)); vec2 light = mix(light_0, light_1, fract(f_uv_pos.x)); @@ -644,7 +646,8 @@ vec3 greedy_extract_col_light_attr(sampler2D t_col_light, vec2 f_uv_pos, out flo return srgb_to_linear(f_col); } -vec3 greedy_extract_col_light_glow(sampler2D t_col_light, vec2 f_uv_pos, out float f_light, out float f_glow) { +vec3 greedy_extract_col_light_glow(texture2D t_col_light, sampler s_col_light, vec2 f_uv_pos, out float f_light, out float f_glow) { uint f_attr; - return greedy_extract_col_light_attr(t_col_light, f_uv_pos, f_light, f_glow, f_attr); + return greedy_extract_col_light_attr(t_col_light, s_col_light, f_uv_pos, f_light, f_glow, f_attr); } +#endif diff --git a/assets/voxygen/shaders/light-shadows-directed-frag.glsl b/assets/voxygen/shaders/light-shadows-directed-frag.glsl deleted file mode 100644 index e201b6ed5e..0000000000 --- a/assets/voxygen/shaders/light-shadows-directed-frag.glsl +++ /dev/null @@ -1,48 +0,0 @@ -// NOTE: We currently do nothing, and just rely on the default shader behavior. -// -// However, in the future we might apply some depth transforms here. - -#version 330 core -// #extension ARB_texture_storage : enable - -#include - -#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION - -#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY - -#if (FLUID_MODE == FLUID_MODE_CHEAP) -#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE -#elif (FLUID_MODE == FLUID_MODE_SHINY) -#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_RADIANCE -#endif - -#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET - -#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN - -// // Currently, we only need globals for the far plane. -// #include -// // Currently, we only need lights for the light position -// #include - -// in vec3 FragPos; // FragPos from GS (output per emitvertex) -// flat in int FragLayer; - -void main() -{ - // Only need to do anything with point lights, since sun and moon should already have nonlinear - // distance. - /*if (FragLayer > 0) */{ - // get distance between fragment and light source - // float lightDistance = length(FragPos - lights[FragLayer & 31].light_pos.xyz); - - // // // map to [0;1] range by dividing by far_plane - // lightDistance = lightDistance / screen_res.w;//FragPos.w;//screen_res.w; - - // // // write this as modified depth - // // // lightDistance = -1000.0 / (lightDistance + 10000.0); - // // // lightDistance /= screen_res.w; - // gl_FragDepth = lightDistance;// / /*FragPos.w;*/screen_res.w;//-1000.0 / (lightDistance + 1000.0);//lightDistance - } -} diff --git a/assets/voxygen/shaders/light-shadows-directed-vert.glsl b/assets/voxygen/shaders/light-shadows-directed-vert.glsl index 38b5ff611d..99aeaa61b1 100644 --- a/assets/voxygen/shaders/light-shadows-directed-vert.glsl +++ b/assets/voxygen/shaders/light-shadows-directed-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core // #extension ARB_texture_storage : enable #include @@ -22,7 +22,13 @@ // Currently, we only need globals for focus_off. #include // For shadow locals. -#include +// #include + +layout (std140, set = 0, binding = 9) +uniform u_light_shadows { + mat4 shadowMatrices; + mat4 texture_mat; +}; /* Accurate packed shadow maps for many lights at once! * @@ -30,12 +36,13 @@ * * */ -in uint v_pos_norm; +layout(location = 0) in uint v_pos_norm; // in uint v_col_light; // in vec4 v_pos; +// layout(location = 1) in uint v_atlas_pos; // Light projection matrices. -layout (std140) +layout (std140, set = 1, binding = 0) uniform u_locals { vec3 model_offs; float load_time; @@ -47,17 +54,15 @@ uniform u_locals { const int EXTRA_NEG_Z = 32768; void main() { -#if (SHADOW_MODE == SHADOW_MODE_MAP) vec3 f_chunk_pos = vec3(ivec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0xFFFFu)) - ivec3(0, 0, EXTRA_NEG_Z)); - vec3 f_pos = f_chunk_pos + model_offs - focus_off.xyz; + vec3 f_pos = f_chunk_pos + (model_offs - focus_off.xyz); // f_pos = v_pos; // vec3 f_pos = f_chunk_pos + model_offs; // gl_Position = v_pos + vec4(model_offs, 0.0); - gl_Position = /*all_mat * */shadowMats[/*layer_face*/0].shadowMatrices * vec4(f_pos/*, 1.0*/, /*float(((f_pos_norm >> 29) & 0x7u) ^ 0x1)*//*uintBitsToFloat(v_pos_norm)*/1.0); + gl_Position = /*all_mat * */shadowMatrices * vec4(f_pos/*, 1.0*/, /*float(((f_pos_norm >> 29) & 0x7u) ^ 0x1)*//*uintBitsToFloat(v_pos_norm)*/1.0); // gl_Position.z = -gl_Position.z; // gl_Position.z = clamp(gl_Position.z, -abs(gl_Position.w), abs(gl_Position.w)); // shadowMapCoord = lights[gl_InstanceID].light_pos * gl_Vertex; // vec4(v_pos, 0.0, 1.0); -#endif } diff --git a/assets/voxygen/shaders/light-shadows-figure-vert.glsl b/assets/voxygen/shaders/light-shadows-figure-vert.glsl index 2178cae73e..1df58ae055 100644 --- a/assets/voxygen/shaders/light-shadows-figure-vert.glsl +++ b/assets/voxygen/shaders/light-shadows-figure-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core // #extension ARB_texture_storage : enable #define FIGURE_SHADER @@ -24,7 +24,13 @@ // Currently, we only need globals for focus_off. #include // For shadow locals. -#include +// #include + +layout (std140, set = 0, binding = 9) +uniform u_light_shadows { + mat4 shadowMatrices; + mat4 texture_mat; +}; /* Accurate packed shadow maps for many lights at once! * @@ -32,12 +38,12 @@ * * */ -in uint v_pos_norm; -in uint v_atlas_pos; +layout(location = 0) in uint v_pos_norm; +layout(location = 1) in uint v_atlas_pos; // in uint v_col_light; // in vec4 v_pos; -layout (std140) +layout (std140, set = 1, binding = 0) uniform u_locals { mat4 model_mat; vec4 highlight_col; @@ -55,7 +61,7 @@ struct BoneData { mat4 normals_mat; }; -layout (std140) +layout (std140, set = 1, binding = 1) uniform u_bones { // Warning: might not actually be 16 elements long. Don't index out of bounds! BoneData bones[16]; @@ -64,7 +70,6 @@ uniform u_bones { // out vec4 shadowMapCoord; void main() { -#if (SHADOW_MODE == SHADOW_MODE_MAP) uint bone_idx = (v_pos_norm >> 27) & 0xFu; vec3 pos = (vec3((uvec3(v_pos_norm) >> uvec3(0, 9, 18)) & uvec3(0x1FFu)) - 256.0) / 2.0; @@ -73,6 +78,5 @@ void main() { vec4(pos, 1.0) ).xyz + (model_pos - focus_off.xyz/* + vec3(0.0, 0.0, 0.0001)*/); - gl_Position = shadowMats[/*layer_face*/0].shadowMatrices * vec4(f_pos, 1.0); -#endif + gl_Position = shadowMatrices * vec4(f_pos, 1.0); } diff --git a/assets/voxygen/shaders/light-shadows-frag.glsl b/assets/voxygen/shaders/light-shadows-frag.glsl index ee3f84070b..3671ace978 100644 --- a/assets/voxygen/shaders/light-shadows-frag.glsl +++ b/assets/voxygen/shaders/light-shadows-frag.glsl @@ -2,7 +2,7 @@ // // However, in the future we might apply some depth transforms here. -#version 330 core +#version 420 core // #extension ARB_texture_storage : enable #include diff --git a/assets/voxygen/shaders/light-shadows-vert.glsl b/assets/voxygen/shaders/light-shadows-vert.glsl index 66ca4bd041..4bdbae25f4 100644 --- a/assets/voxygen/shaders/light-shadows-vert.glsl +++ b/assets/voxygen/shaders/light-shadows-vert.glsl @@ -26,12 +26,12 @@ * * */ -in uint v_pos_norm; +layout(location = 1) in uint v_pos_norm; // in uint v_col_light; // in vec4 v_pos; // Light projection matrices. -layout (std140) +layout (std140, set = 1, binding = 0) uniform u_locals { vec3 model_offs; float load_time; diff --git a/assets/voxygen/shaders/lod-terrain-frag.glsl b/assets/voxygen/shaders/lod-terrain-frag.glsl index 6d8d6b2695..f55ae4e98c 100644 --- a/assets/voxygen/shaders/lod-terrain-frag.glsl +++ b/assets/voxygen/shaders/lod-terrain-frag.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -23,14 +23,14 @@ #include #include -in vec3 f_pos; -in vec3 f_norm; -in float pull_down; +layout(location = 0) in vec3 f_pos; +layout(location = 1) in vec3 f_norm; +layout(location = 2) in float pull_down; // in vec2 v_pos_orig; // in vec4 f_shadow; // in vec4 f_square; -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; /// const vec4 sun_pos = vec4(0); // const vec4 light_pos[2] = vec4[](vec4(0), vec4(0)/*, vec3(00), vec3(0), vec3(0), vec3(0)*/); @@ -444,7 +444,7 @@ void main() { #endif #if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP) - vec4 f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy)); + vec4 f_shadow = textureBicubic(t_horizon, s_horizon, pos_to_tex(f_pos.xy)); float sun_shade_frac = horizon_at2(f_shadow, shadow_alt, f_pos, sun_dir); // float sun_shade_frac = 1.0; #elif (SHADOW_MODE == SHADOW_MODE_NONE) diff --git a/assets/voxygen/shaders/lod-terrain-vert.glsl b/assets/voxygen/shaders/lod-terrain-vert.glsl index 5f03cf934c..48554ea74e 100644 --- a/assets/voxygen/shaders/lod-terrain-vert.glsl +++ b/assets/voxygen/shaders/lod-terrain-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -20,16 +20,11 @@ #include #include -in vec2 v_pos; +layout(location = 0) in vec2 v_pos; -layout (std140) -uniform u_locals { - vec4 nul; -}; - -out vec3 f_pos; -out vec3 f_norm; -out float pull_down; +layout(location = 0) out vec3 f_pos; +layout(location = 1) out vec3 f_norm; +layout(location = 2) out float pull_down; // out vec2 v_pos_orig; // out vec4 f_square; // out vec4 f_shadow; @@ -49,8 +44,11 @@ void main() { // f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy)); - float dist = distance(focus_pos.xy, f_pos.xy); - pull_down = 0.2 / pow(dist / (view_distance.x * 0.9), 20.0); + // TODO: disabled because it isn't designed to work with reverse depth + //float dist = distance(focus_pos.xy, f_pos.xy); + //pull_down = 0.2 / pow(dist / (view_distance.x * 0.9), 20.0); + + pull_down = 1.0 / pow(distance(focus_pos.xy, f_pos.xy) / (view_distance.x * 0.95), 20.0); f_pos.z -= pull_down; // f_pos.z -= 100.0 * pow(1.0 + 0.01 / view_distance.x, -pow(distance(focus_pos.xy, f_pos.xy), 2.0)); @@ -100,7 +98,9 @@ void main() { all_mat * vec4(f_pos/*newRay*/, 1); // Pull up the depth to avoid drawing over voxels (biased according to VD) - gl_Position.z += 0.1 * clamp((view_distance.x * 1.0 - dist) * 0.01, 0, 1); + // TODO: disabled because it isn't designed to work with reverse depth + //gl_Position.z += 0.1 * clamp((view_distance.x * 1.0 - dist) * 0.01, 0, 1); + // gl_Position.z = -gl_Position.z / gl_Position.w; // gl_Position.z = -gl_Position.z / gl_Position.w; // gl_Position.z = -gl_Position.z * gl_Position.w; diff --git a/assets/voxygen/shaders/particle-frag.glsl b/assets/voxygen/shaders/particle-frag.glsl index b5efe862a0..bd04b3388d 100644 --- a/assets/voxygen/shaders/particle-frag.glsl +++ b/assets/voxygen/shaders/particle-frag.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -16,12 +16,12 @@ #include -in vec3 f_pos; -flat in vec3 f_norm; -in vec4 f_col; -in float f_reflect; +layout(location = 0) in vec3 f_pos; +layout(location = 1) flat in vec3 f_norm; +layout(location = 2) in vec4 f_col; +layout(location = 3) in float f_reflect; -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; #include #include @@ -40,7 +40,7 @@ void main() { #endif #if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP) - vec4 f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy)); + vec4 f_shadow = textureBicubic(t_horizon, s_horizon, pos_to_tex(f_pos.xy)); float sun_shade_frac = horizon_at2(f_shadow, f_alt, f_pos, sun_dir); #elif (SHADOW_MODE == SHADOW_MODE_NONE) float sun_shade_frac = 1.0; diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index b3adde86cf..12be61b909 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -17,22 +17,22 @@ #include #include -in vec3 v_pos; +layout(location = 0) in vec3 v_pos; // in uint v_col; -in uint v_norm_ao; -in vec3 inst_pos; -in float inst_time; -in float inst_lifespan; -in float inst_entropy; -in vec3 inst_dir; -in int inst_mode; +layout(location = 1) in uint v_norm_ao; +layout(location = 2) in float inst_time; +layout(location = 3) in float inst_lifespan; +layout(location = 4) in float inst_entropy; +layout(location = 5) in int inst_mode; +layout(location = 6) in vec3 inst_dir; +layout(location = 7) in vec3 inst_pos; -out vec3 f_pos; -flat out vec3 f_norm; -out vec4 f_col; -out float f_ao; -out float f_light; -out float f_reflect; +layout(location = 0) out vec3 f_pos; +layout(location = 1) flat out vec3 f_norm; +layout(location = 2) out vec4 f_col; +//layout(location = x) out float f_ao; +//layout(location = x) out float f_light; +layout(location = 3) out float f_reflect; const float SCALE = 1.0 / 11.0; diff --git a/assets/voxygen/shaders/point-light-shadows-vert.glsl b/assets/voxygen/shaders/point-light-shadows-vert.glsl new file mode 100644 index 0000000000..7670a2f368 --- /dev/null +++ b/assets/voxygen/shaders/point-light-shadows-vert.glsl @@ -0,0 +1,61 @@ +#version 420 core +// #extension ARB_texture_storage : enable + +#include + +#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION + +#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY + +#if (FLUID_MODE == FLUID_MODE_CHEAP) +#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE +#elif (FLUID_MODE == FLUID_MODE_SHINY) +#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_RADIANCE +#endif + +#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET + +#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN + +// Currently, we only need globals for focus_off. +#include + +/* Accurate packed shadow maps for many lights at once! + * + * Ideally, we would just write to a bitmask... + * + * */ + +layout(location = 0) in uint v_pos_norm; +// layout(location = 1) in uint v_atlas_pos; +// in uint v_col_light; +// in vec4 v_pos; + +// Light projection matrices. +layout (std140, set = 1, binding = 0) +uniform u_locals { + vec3 model_offs; + float load_time; + ivec4 atlas_offs; +}; + +// out vec4 shadowMapCoord; + +const int EXTRA_NEG_Z = 32768; + +layout( push_constant ) uniform PointLightMatrix { + mat4 lightShadowMatrix; +}; + +void main() { + vec3 f_chunk_pos = vec3(ivec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0xFFFFu)) - ivec3(0, 0, EXTRA_NEG_Z)); + vec3 f_pos = f_chunk_pos + model_offs - focus_off.xyz; + // f_pos = v_pos; + // vec3 f_pos = f_chunk_pos + model_offs; + + // gl_Position = v_pos + vec4(model_offs, 0.0); + // gl_Position = /*all_mat * */vec4(f_pos/*, 1.0*/, /*float(((f_pos_norm >> 29) & 0x7u) ^ 0x1)*//*uintBitsToFloat(v_pos_norm)*/1.0); + // shadowMapCoord = lights[gl_InstanceID].light_pos * gl_Vertex; + // vec4(v_pos, 0.0, 1.0); + gl_Position = lightShadowMatrix * vec4(f_pos, 1.0); +} diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index 89ae9c1b59..aeba45f350 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -22,17 +22,21 @@ #include #include -//uniform sampler2D src_depth; +layout(set = 1, binding = 0) +uniform texture2D t_src_color; +layout(set = 1, binding = 1) +uniform sampler s_src_color; -in vec2 f_pos; -layout (std140) +layout(location = 0) in vec2 uv; + +layout (std140, set = 1, binding = 2) uniform u_locals { mat4 proj_mat_inv; mat4 view_mat_inv; }; -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; vec3 rgb2hsv(vec3 c) { vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); @@ -145,33 +149,7 @@ vec3 _illuminate(float max_light, vec3 view_dir, /*vec3 max_light, */vec3 emitte // return /*srgb_to_linear*/(/*0.5*//*0.125 * */vec3(pow(color.x, gamma), pow(color.y, gamma), pow(color.z, gamma))); } -/* -float depth_at(vec2 uv) { - float buf_depth = texture(src_depth, uv).x; - vec4 clip_space = vec4(uv * 2.0 - 1.0, buf_depth, 1.0); - vec4 view_space = proj_mat_inv * clip_space; - view_space /= view_space.w; - return -view_space.z; -} - -vec3 wpos_at(vec2 uv) { - float buf_depth = texture(src_depth, uv).x * 2.0 - 1.0; - mat4 inv = view_mat_inv * proj_mat_inv;//inverse(all_mat); - vec4 clip_space = vec4(uv * 2.0 - 1.0, buf_depth, 1.0); - vec4 view_space = inv * clip_space; - view_space /= view_space.w; - if (buf_depth == 1.0) { - vec3 direction = normalize(view_space.xyz); - return direction.xyz * 100000.0 + cam_pos.xyz; - } else { - return view_space.xyz; - } -} -*/ - void main() { - vec2 uv = (f_pos + 1.0) * 0.5; - /* if (medium.x == 1u) { uv = clamp(uv + vec2(sin(uv.y * 16.0 + tick.x), sin(uv.x * 24.0 + tick.x)) * 0.005, 0, 1); } */ @@ -202,7 +180,7 @@ void main() { // float bright_color = (bright_color0 + bright_color1 + bright_color2 + bright_color3 + bright_color4) / 5.0; - vec4 aa_color = aa_apply(src_color, uv * screen_res.xy, screen_res.xy); + vec4 aa_color = aa_apply(t_src_color, s_src_color, uv * screen_res.xy, screen_res.xy); // Tonemapping float exposure_offset = 1.0; diff --git a/assets/voxygen/shaders/postprocess-vert.glsl b/assets/voxygen/shaders/postprocess-vert.glsl index 35b786997f..21a6b3d8c2 100644 --- a/assets/voxygen/shaders/postprocess-vert.glsl +++ b/assets/voxygen/shaders/postprocess-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -18,12 +18,16 @@ #include -in vec2 v_pos; - -out vec2 f_pos; +layout(location = 0) out vec2 uv; void main() { - f_pos = v_pos; + // Generate fullscreen triangle + vec2 v_pos = vec2( + float(gl_VertexIndex / 2) * 4.0 - 1.0, + float(gl_VertexIndex % 2) * 4.0 - 1.0 + ); - gl_Position = vec4(v_pos, -1.0, 1.0); + uv = (v_pos * vec2(1.0, -1.0) + 1.0) * 0.5; + + gl_Position = vec4(v_pos, 0.0, 1.0); } diff --git a/assets/voxygen/shaders/skybox-frag.glsl b/assets/voxygen/shaders/skybox-frag.glsl index aa3824cde6..37e1676520 100644 --- a/assets/voxygen/shaders/skybox-frag.glsl +++ b/assets/voxygen/shaders/skybox-frag.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -20,14 +20,9 @@ #include #include -in vec3 f_pos; +layout(location = 0) in vec3 f_pos; -layout (std140) -uniform u_locals { - vec4 nul; -}; - -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; void main() { // tgt_color = vec4(MU_SCATTER, 1.0); diff --git a/assets/voxygen/shaders/skybox-vert.glsl b/assets/voxygen/shaders/skybox-vert.glsl index 3bc29c8c6a..9f362fedb7 100644 --- a/assets/voxygen/shaders/skybox-vert.glsl +++ b/assets/voxygen/shaders/skybox-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -18,31 +18,28 @@ #include -in vec3 v_pos; +layout(location = 0) in vec3 v_pos; -layout (std140) -uniform u_locals { - vec4 nul; -}; - -out vec3 f_pos; +layout(location = 0) out vec3 f_pos; void main() { - /* vec3 v_pos = v_pos; - v_pos.y = -v_pos.y; */ f_pos = v_pos; // TODO: Make this position-independent to avoid rounding error jittering + // NOTE: we may or may not want to use an infinite projection here + // + // Essentially: using any finite projection is likely wrong here if we want + // to project out to infinity, but since we want to perturb the skybox as we + // move and we have stars now, the "right" answer is heavily dependent on + // how we compute cloud position and stuff. + // + // Infinite projections of cubemaps are nice because they can be oriented + // but still extend infinitely far. gl_Position = - /* proj_mat * - view_mat * */ all_mat * - /* proj_mat * - view_mat * */ - vec4(/*100000 * */v_pos + cam_pos.xyz, 1); - // vec4(v_pos * (100000.0/* + 0.5*/) + cam_pos.xyz, 1); + vec4(v_pos + cam_pos.xyz, 1); // gl_Position = vec4(gl_Position.xy, sign(gl_Position.z) * gl_Position.w, gl_Position.w); - gl_Position.z = gl_Position.w; + gl_Position.z = 0; // gl_Position.z = gl_Position.w - 0.000001;//0.0; // gl_Position.z = 1.0; // gl_Position.z = -1.0; diff --git a/assets/voxygen/shaders/sprite-frag.glsl b/assets/voxygen/shaders/sprite-frag.glsl index 3d6ff65d74..6a9c97b0f2 100644 --- a/assets/voxygen/shaders/sprite-frag.glsl +++ b/assets/voxygen/shaders/sprite-frag.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -16,31 +16,18 @@ #include -in vec3 f_pos; -flat in vec3 f_norm; -flat in float f_select; -// flat in vec3 f_pos_norm; -in vec2 f_uv_pos; -in vec2 f_inst_light; -// flat in uint f_atlas_pos; -// in vec3 f_col; -// in float f_ao; -// in float f_light; -// in vec4 light_pos[2]; +layout(location = 0) in vec3 f_pos; +layout(location = 1) flat in vec3 f_norm; +layout(location = 2) flat in float f_select; +layout(location = 3) in vec2 f_uv_pos; +layout(location = 4) in vec2 f_inst_light; -uniform sampler2D t_col_light; +layout(set = 3, binding = 0) +uniform texture2D t_col_light; +layout(set = 3, binding = 1) +uniform sampler s_col_light; -//struct ShadowLocals { -// mat4 shadowMatrices; -// mat4 texture_mat; -//}; -// -//layout (std140) -//uniform u_light_shadows { -// ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192]; -//}; - -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; #include #include @@ -49,77 +36,31 @@ out vec4 tgt_color; const float FADE_DIST = 32.0; void main() { - /* if (f_uv_pos.x < 757) { - discard; - } */ - // vec2 f_uv_pos = vec2(768,1) + 0.5; - // vec2 f_uv_pos = vec2(760, 380);// + 0.5; - // vec2 f_uv_pos = vec2((uvec2(f_atlas_pos) >> uvec2(0, 16)) & uvec2(0xFFFFu, 0xFFFFu)) + 0.5; - /* if (f_uv_pos.x < 757) { - discard; - } */ - // vec3 du = dFdx(f_pos); - // vec3 dv = dFdy(f_pos); - // vec3 f_norm = normalize(cross(du, dv)); - float f_ao, f_glow; - vec3 f_col = greedy_extract_col_light_glow(t_col_light, f_uv_pos, f_ao, f_glow); + vec3 f_col = greedy_extract_col_light_glow(t_col_light, s_col_light, f_uv_pos, f_ao, f_glow); - // vec3 my_chunk_pos = f_pos_norm; - // tgt_color = vec4(hash(floor(vec4(my_chunk_pos.x, 0, 0, 0))), hash(floor(vec4(0, my_chunk_pos.y, 0, 1))), hash(floor(vec4(0, 0, my_chunk_pos.z, 2))), 1.0); - // tgt_color = vec4(f_uv_pos / texSize, 0.0, 1.0); - // tgt_color = vec4(f_col.rgb, 1.0); - // return; - // vec4 light_pos[2]; -//#if (SHADOW_MODE == SHADOW_MODE_MAP) -// // for (uint i = 0u; i < light_shadow_count.z; ++i) { -// // light_pos[i] = /*vec3(*/shadowMats[i].texture_mat * vec4(f_pos, 1.0)/*)*/; -// // } -// vec4 sun_pos = /*vec3(*/shadowMats[0].texture_mat * vec4(f_pos, 1.0)/*)*/; -//#elif (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_NONE) -// vec4 sun_pos = vec4(0.0); -//#endif vec3 cam_to_frag = normalize(f_pos - cam_pos.xyz); - // vec4 vert_pos4 = view_mat * vec4(f_pos, 1.0); - // vec3 view_dir = normalize(-vec3(vert_pos4)/* / vert_pos4.w*/); vec3 view_dir = -cam_to_frag; - /* vec3 sun_dir = get_sun_dir(time_of_day.x); - vec3 moon_dir = get_moon_dir(time_of_day.x); */ - // float sun_light = get_sun_brightness(sun_dir); - // float moon_light = get_moon_brightness(moon_dir); - #if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP || FLUID_MODE == FLUID_MODE_SHINY) float f_alt = alt_at(f_pos.xy); - // float f_alt = f_pos.z; #elif (SHADOW_MODE == SHADOW_MODE_NONE || FLUID_MODE == FLUID_MODE_CHEAP) float f_alt = f_pos.z; #endif #if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP) - vec4 f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy)); + vec4 f_shadow = textureBicubic(t_horizon, s_horizon, pos_to_tex(f_pos.xy)); float sun_shade_frac = horizon_at2(f_shadow, f_alt, f_pos, sun_dir); - // float sun_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, sun_dir); #elif (SHADOW_MODE == SHADOW_MODE_NONE) - float sun_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, sun_dir); + float sun_shade_frac = 1.0; #endif - float moon_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, moon_dir); - // float sun_shade_frac = horizon_at(f_pos, sun_dir); - // float moon_shade_frac = horizon_at(f_pos, moon_dir); - // Globbal illumination "estimate" used to light the faces of voxels which are parallel to the sun or moon (which is a very common occurrence). - // Will be attenuated by k_d, which is assumed to carry any additional ambient occlusion information (e.g. about shadowing). - // float ambient_sides = clamp(mix(0.5, 0.0, abs(dot(-f_norm, sun_dir)) * 10000.0), 0.0, 0.5); - // NOTE: current assumption is that moon and sun shouldn't be out at the sae time. - // This assumption is (or can at least easily be) wrong, but if we pretend it's true we avoids having to explicitly pass in a separate shadow - // for the sun and moon (since they have different brightnesses / colors so the shadows shouldn't attenuate equally). - // float shade_frac = sun_shade_frac + moon_shade_frac; + float moon_shade_frac = 1.0; - // DirectionalLight sun_info = get_sun_info(sun_dir, sun_shade_frac, light_pos); float point_shadow = shadow_at(f_pos, f_norm); - DirectionalLight sun_info = get_sun_info(sun_dir, point_shadow * sun_shade_frac, /*sun_pos*/f_pos); - DirectionalLight moon_info = get_moon_info(moon_dir, point_shadow * moon_shade_frac/*, light_pos*/); + DirectionalLight sun_info = get_sun_info(sun_dir, point_shadow * sun_shade_frac, f_pos); + DirectionalLight moon_info = get_moon_info(moon_dir, point_shadow * moon_shade_frac); - vec3 surf_color = /*srgb_to_linear*//*linear_to_srgb*/(f_col); + vec3 surf_color = f_col; float alpha = 1.0; const float n2 = 1.5; const float R_s2s0 = pow((1.0 - n2) / (1.0 + n2), 2); @@ -138,42 +79,11 @@ void main() { sun_info.block = f_inst_light.x; moon_info.block = f_inst_light.x; - // To account for prior saturation. - // float vert_light = pow(f_light, 1.5); - // vec3 light_frac = light_reflection_factor(f_norm/*vec3(0, 0, 1.0)*/, view_dir, vec3(0, 0, -1.0), vec3(1.0), vec3(R_s), alpha); - /* light_frac += light_reflection_factor(f_norm, view_dir, vec3(1.0, 0, 0.0), vec3(1.0), vec3(1.0), 2.0); - light_frac += light_reflection_factor(f_norm, view_dir, vec3(-1.0, 0, 0.0), vec3(1.0), vec3(1.0), 2.0); - light_frac += light_reflection_factor(f_norm, view_dir, vec3(0.0, -1.0, 0.0), vec3(1.0), vec3(1.0), 2.0); - light_frac += light_reflection_factor(f_norm, view_dir, vec3(0.0, 1.0, 0.0), vec3(1.0), vec3(1.0), 2.0); */ - - // vec3 light, diffuse_light, ambient_light; - // vec3 emitted_light, reflected_light; - // float point_shadow = shadow_at(f_pos,f_norm); - // vec3 point_light = light_at(f_pos, f_norm); - // vec3 surf_color = srgb_to_linear(vec3(0.2, 0.5, 1.0)); - // vec3 cam_to_frag = normalize(f_pos - cam_pos.xyz); float max_light = 0.0; - max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, /*time_of_day.x, *//*cam_to_frag*/view_dir, k_a/* * (shade_frac * 0.5 + light_frac * 0.5)*/, k_d, k_s, alpha, emitted_light, reflected_light); - // reflected_light *= /*vert_light * */point_shadow * shade_frac; - // emitted_light *= /*vert_light * */point_shadow * max(shade_frac, MIN_SHADOW); - // max_light *= /*vert_light * */point_shadow * shade_frac; - // emitted_light *= point_shadow; - // reflected_light *= point_shadow; - // max_light *= point_shadow; - // get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0); - // float point_shadow = shadow_at(f_pos, f_norm); - // diffuse_light *= f_light * point_shadow; - // ambient_light *= f_light * point_shadow; - // light += point_light; - // diffuse_light += point_light; - // reflected_light += point_light; + max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, view_dir, k_a, k_d, k_s, alpha, emitted_light, reflected_light); max_light += lights_at(f_pos, f_norm, view_dir, k_a, k_d, k_s, alpha, emitted_light, reflected_light); - /* vec3 point_light = light_at(f_pos, f_norm); - emitted_light += point_light; - reflected_light += point_light; */ - // float ao = /*pow(f_ao, 0.5)*/f_ao * 0.85 + 0.15; vec3 glow = pow(f_inst_light.y, 3) * 4 * glow_light(f_pos); emitted_light += glow; @@ -182,10 +92,9 @@ void main() { reflected_light *= ao; surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light); - // vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light); surf_color += f_select * (surf_color + 0.1) * vec3(0.15, 0.15, 0.15); - // tgt_color = vec4(color, 1.0); tgt_color = vec4(surf_color, 1.0 - clamp((distance(focus_pos.xy, f_pos.xy) - (sprite_render_distance - FADE_DIST)) / FADE_DIST, 0, 1)); + //tgt_color = vec4(-f_norm, 1.0); } diff --git a/assets/voxygen/shaders/sprite-vert.glsl b/assets/voxygen/shaders/sprite-vert.glsl index e22235a1d8..e7cfd44fc8 100644 --- a/assets/voxygen/shaders/sprite-vert.glsl +++ b/assets/voxygen/shaders/sprite-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -16,227 +16,114 @@ #include #include -in vec3 v_pos; -in uint v_atlas_pos; -// in uint v_col; -in uint v_norm_ao; -in uint inst_pos_ori; -in vec4 inst_mat0; -in vec4 inst_mat1; -in vec4 inst_mat2; -in vec4 inst_mat3; -in vec4 inst_light; -in float inst_wind_sway; +layout(location = 0) in vec4 inst_mat0; +layout(location = 1) in vec4 inst_mat1; +layout(location = 2) in vec4 inst_mat2; +layout(location = 3) in vec4 inst_mat3; +// TODO: is there a better way to pack the various vertex attributes? +// TODO: ori is unused +layout(location = 4) in uint inst_pos_ori; +layout(location = 5) in uint inst_vert_page; // NOTE: this could fit in less bits +// TODO: do we need this many bits for light and glow? +layout(location = 6) in float inst_light; +layout(location = 7) in float inst_glow; +layout(location = 8) in float model_wind_sway; // NOTE: this only varies per model +layout(location = 9) in float model_z_scale; // NOTE: this only varies per model -struct SpriteLocals { - mat4 mat; - vec4 wind_sway; - vec4 offs; +layout(set = 0, binding = 12) restrict readonly buffer sprite_verts { + uvec2 verts[]; }; -layout (std140) -uniform u_locals { - mat4 mat; - vec4 wind_sway; - vec4 offs; - // SpriteLocals sprites[8]; -}; - -// struct Instance { -// mat4 inst_mat; -// vec3 inst_col; -// float inst_wind_sway; -// }; -// -// layout (std140) -// uniform u_ibuf { -// Instance sprite_instances[/*MAX_LAYER_FACES*/512]; -// }; - -//struct ShadowLocals { -// mat4 shadowMatrices; -// mat4 texture_mat; -//}; -// -//layout (std140) -//uniform u_light_shadows { -// ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192]; -//}; - -layout (std140) +layout (std140, set = 2, binding = 0) uniform u_terrain_locals { vec3 model_offs; float load_time; ivec4 atlas_offs; }; -out vec3 f_pos; -flat out vec3 f_norm; -flat out float f_select; -// flat out vec3 f_pos_norm; -// out vec3 f_col; -// out float f_ao; -out vec2 f_uv_pos; -out vec2 f_inst_light; -// flat out uint f_atlas_pos; -// out vec3 light_pos[2]; -// out float f_light; +// TODO: consider grouping into vec4's +layout(location = 0) out vec3 f_pos; +layout(location = 1) flat out vec3 f_norm; +layout(location = 2) flat out float f_select; +layout(location = 3) out vec2 f_uv_pos; +layout(location = 4) out vec2 f_inst_light; const float SCALE = 1.0 / 11.0; const float SCALE_FACTOR = pow(SCALE, 1.3) * 0.2; const int EXTRA_NEG_Z = 32768; +const int VERT_EXTRA_NEG_Z = 128; +const uint VERT_PAGE_SIZE = 256; void main() { - // vec3 inst_chunk_pos = vec3(ivec3((uvec3(inst_pos_ori) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0xFFFFu)) - ivec3(0, 0, EXTRA_NEG_Z)); - // uint inst_ori = (inst_pos_ori >> 29) & 0x7u; - // SpriteLocals locals = sprites[inst_ori]; - // SpriteLocals locals = sprites; - // mat4 inst_mat = locals.mat; - // float inst_wind_sway = locals.wind_sway.w; - - // mat4 inst_mat = mat4(vec4(1, 0, 0, 0), vec4(0, 1, 0, 0), vec4(0, 0, 1, 0), vec4(5.5, 5.5, 0, 1)); - // float inst_wind_sway = 0.0; + // Matrix to transform this sprite instance from model space to chunk space mat4 inst_mat; inst_mat[0] = inst_mat0; inst_mat[1] = inst_mat1; inst_mat[2] = inst_mat2; inst_mat[3] = inst_mat3; - /* Instance instances = sprite_instances[gl_InstanceID & 1023]; - mat4 inst_mat = instances.inst_mat; - vec3 inst_col = instances.inst_col; - float inst_wind_sway = instances.inst_wind_sway; */ - vec3 inst_offs = model_offs - focus_off.xyz; - // mat3 inst_mat; - // inst_mat[0] = inst_mat0.xyz; - // inst_mat[1] = inst_mat1.xyz; - // inst_mat[2] = inst_mat2.xyz; - // /* Instance instances = sprite_instances[gl_InstanceID & 1023]; - // mat4 inst_mat = instances.inst_mat; - // vec3 inst_col = instances.inst_col; - // float inst_wind_sway = instances.inst_wind_sway; */ - // float inst_wind_sway = wind_sway.w; - // vec3 inst_offs = model_offs - focus_off.xyz; - f_inst_light = inst_light.xy; + // Worldpos of the chunk that this sprite is in + vec3 chunk_offs = model_offs - focus_off.xyz; - // vec3 sprite_pos = floor(inst_mat3.xyz * SCALE) + inst_offs; + f_inst_light = vec2(inst_light, inst_glow); - // f_pos_norm = v_pos; + // Index of the vertex data in the 1D vertex texture + int vertex_index = int(uint(gl_VertexIndex) % VERT_PAGE_SIZE + inst_vert_page * VERT_PAGE_SIZE); + uvec2 pos_atlas_pos_norm_ao = verts[vertex_index]; + uint v_pos_norm = pos_atlas_pos_norm_ao.x; + uint v_atlas_pos = pos_atlas_pos_norm_ao.y; - // vec3 sprite_pos = (inst_mat * vec4(0, 0, 0, 1)).xyz; - // vec3 sprite_pos = floor((inst_mat * vec4(0, 0, 0, 1)).xyz * SCALE/* - vec3(0.5, 0.5, 0.0)*/) + inst_offs; - // vec3 sprite_pos = /*round*/floor(((inst_mat * vec4(0, 0, 0, 1)).xyz - /* wind_sway.xyz * */offs.xyz) * SCALE/* - vec3(0.5, 0.5, 0.0)*/) - inst_offs; - // vec3 sprite_pos = /*round*/floor(((inst_mat * vec4(-offs.xyz, 1)).xyz) * SCALE/* - vec3(0.5, 0.5, 0.0)*/) + inst_offs; + // Expand the model vertex position bits into float values + vec3 v_pos = vec3(ivec3((uvec3(v_pos_norm) >> uvec3(0, 8, 16)) & uvec3(0xFFu, 0xFFu, 0x0FFFu)) - ivec3(0, 0, VERT_EXTRA_NEG_Z)); - // vec3 v_pos = vec3(gl_VertexID * 32, gl_VertexID % 32, 1.0); - // f_pos = v_pos + (model_offs - focus_off.xyz); - - // vec3 v_pos = /*inst_mat*//*locals.*/wind_sway.xyz * v_pos; - vec3 v_pos_ = /*inst_mat*//*locals.*//*sprites[0].*/wind_sway.xyz * v_pos; - // vec3 v_pos = (/*inst_mat*/locals.mat * vec4(v_pos, 1)).xyz + vec3(0.5, 0.5, 0.0); - // f_pos = v_pos * SCALE + (inst_chunk_pos + model_offs - focus_off.xyz); - - // vec3 v_pos_ = (inst_mat * vec4(v_pos/* * SCALE*/, 1)).xyz; - // vec3 v_pos = (inst_mat * vec4(v_pos, 1)).xyz; - // f_pos = v_pos + (model_offs - focus_off.xyz); - - f_pos = (inst_mat * vec4(v_pos_, 1.0)).xyz * SCALE + inst_offs; + // Transform into chunk space and scale + f_pos = (inst_mat * vec4(v_pos, 1.0)).xyz; + // Transform info world space + f_pos += chunk_offs; // Terrain 'pop-in' effect f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0)); - // f_pos = (inst_mat * v_pos_) * SCALE + sprite_pos; - // f_pos = (inst_mat * vec4(v_pos * SCALE, 1)).xyz + (model_offs - focus_off.xyz); - // f_pos = v_pos_ + (inst_chunk_pos + model_offs - focus_off.xyz + vec3(0.5, 0.5, 0.0)); - // f_pos.z -= min(32.0, 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0)); + // Wind sway effect + f_pos += model_wind_sway * vec3( + sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35), + sin(tick.x * 1.5 + f_pos.x * 0.1) * sin(tick.x * 0.25), + 0.0 + // NOTE: could potentially replace `v_pos.z * model_z_scale` with a calculation using `inst_chunk_pos` from below + //) * pow(abs(v_pos.z * model_z_scale), 1.3) * SCALE_FACTOR; + ) * v_pos.z * model_z_scale * SCALE_FACTOR; - // Wind waving - /* const float x_scale = sin(tick.x * 1.5 + f_pos.x * 0.1); - const float y_scale = sin(tick.x * 1.5 + f_pos.y * 0.1); - const float z_scale = pow(abs(v_pos_.z), 1.3) * SCALE_FACTOR; - const float xy_bias = sin(tick.x * 0.25); - const float z_bias = xy_bias * t_scale; - mat3 shear = mat4( - vec3(x_scale , 0.0, 0.0, 0.0), - vec3(0.0, y_scale, 0.0, 0.0), - vec3(0.0, 0.0, z_bias, 0.0), - vec3(0.0, 0.0, (1.0 / z_bias), 0.0) - ); */ - // const float x_scale = sin(tick.x * 1.5 + f_pos.x * 0.1); - // const float y_scale = sin(tick.x * 1.5 + f_pos.y * 0.1); - // const float z_scale = pow(abs(v_pos_.z), 1.3) * SCALE_FACTOR; - // const float xy_bias = sin(tick.x * 0.25); - // const float z_bias = xy_bias * t_scale; - // vec3 rotate = inst_wind_sway * vec3( - // sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35), - // sin(tick.x * 1.5 + f_pos.x * 0.1) * sin(tick.x * 0.25), - // 0.0 - // ) * pow(abs(v_pos_.z/* + sprites[0].offs.z*/)/* * SCALE*/, 1.3) * /*0.2;*/SCALE_FACTOR; - // - // mat3 shear = mat4( - // vec3(x_scale * , 0.0, 0.0, 0.0), - // vec3(0.0, y_scale, 0.0, 0.0), - // vec3(0.0, 0.0, z_bias, 0.0), - // vec3(0.0, 0.0, (1.0 / z_bias), 0.0) - // ); - /*if (wind_sway.w >= 0.4) */{ - f_pos += /*inst_wind_sway*/wind_sway.w * vec3( - sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35), - sin(tick.x * 1.5 + f_pos.x * 0.1) * sin(tick.x * 0.25), - 0.0 - ) * 4 * v_pos_.z * /*0.2;*/SCALE_FACTOR; + // Determine normal + // TODO: do changes here effect perf on vulkan + // TODO: dx12 doesn't like dynamic index + // TODO: use mix? + // Shader@0x000001AABD89BEE0(112,43-53): error X4576: Input array signature parameter cannot be indexed dynamically. + //vec3 norm = (inst_mat[(v_pos_norm >> 30u) & 3u].xyz); + uint index = v_pos_norm >> 30u & 3u; + vec3 norm; + if (index == 0) { + norm = (inst_mat[0].xyz); + } else if (index == 1) { + norm = (inst_mat[1].xyz); + } else { + norm = (inst_mat[2].xyz); } - // First 3 normals are negative, next 3 are positive - // vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1)); - // uint norm_idx = (v_norm_ao >> 0) & 0x7u; - // f_norm = (inst_mat * vec4(normals[], 0)).xyz; + f_norm = normalize(mix(-norm, norm, v_pos_norm >> 29u & 1u)); - // TODO: Consider adding a second, already-normalized (i.e. unscaled) matrix. - // vec3 norm = /*normalize*/(inst_mat/*locals.mat*/[(v_norm_ao >> 1u) & 3u].xyz); - // vec3 norm = /*normalize*/(inst_mat/*locals.mat*/[(v_norm_ao >> 1u) & 3u]); + // Expand atlas tex coords to floats + // NOTE: Could defer to fragment shader if we are vert heavy + f_uv_pos = vec2((uvec2(v_atlas_pos) >> uvec2(0, 16)) & uvec2(0xFFFFu, 0xFFFFu));; - // vec3 norm = bone_data.normals_mat[axis_idx].xyz; - // norm = normalize(norm); - // norm = norm / SCALE_FACTOR / locals.wind_sway.xyz; - // norm = norm / (norm.x + norm.y + norm.z); - // vec3 norm = norm_mat * vec4(uvec3(1 << axis_idx) & uvec3(0x1u, 0x3u, 0x7u), 1); - - // // Calculate normal here rather than for each pixel in the fragment shader - // f_norm = normalize(( - // combined_mat * - // vec4(norm, 0) - // ).xyz); - - vec3 norm = /*normalize*/(inst_mat/*locals.mat*/[(v_norm_ao >> 1u) & 3u].xyz); - f_norm = mix(-norm, norm, v_norm_ao & 1u); - - /* vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0; - f_col = srgb_to_linear(col) * srgb_to_linear(inst_col); - f_ao = float((v_norm_ao >> 3) & 0x3u) / 4.0; */ - f_uv_pos = vec2((uvec2(v_atlas_pos) >> uvec2(0, 16)) & uvec2(0xFFFFu, 0xFFFFu));/* + 0.5*/; - // f_atlas_pos = v_atlas_pos; - /* for (uint i = 0u; i < light_shadow_count.z; ++i) { - light_pos[i] = vec3(shadowMats[i].texture_mat * vec4(f_pos, 1.0)); - } */ - - // // Select glowing - // if (select_pos.w > 0 && select_pos.xyz == floor(sprite_pos)) { - // f_col *= 4.0; - // } - // f_light = 1.0; - // if (select_pos.w > 0) */{ - vec3 sprite_pos = /*round*/floor(((inst_mat * vec4(-offs.xyz, 1)).xyz) * SCALE/* - vec3(0.5, 0.5, 0.0)*/) + inst_offs; - f_select = (select_pos.w > 0 && select_pos.xyz == sprite_pos/* - vec3(0.5, 0.5, 0.0) * SCALE*/) ? 1.0 : 0.0; - // } + // Position of the sprite block in the chunk + // Used solely for highlighting the selected sprite + vec3 inst_chunk_pos = vec3(ivec3((uvec3(inst_pos_ori) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0xFFFFu)) - ivec3(0, 0, EXTRA_NEG_Z)); + // Select glowing + vec3 sprite_pos = inst_chunk_pos + chunk_offs; + f_select = (select_pos.w > 0 && select_pos.xyz == sprite_pos) ? 1.0 : 0.0; gl_Position = all_mat * vec4(f_pos, 1); - // gl_Position.z = -gl_Position.z; - // gl_Position.z = -gl_Position.z / gl_Position.w; - // gl_Position.z = -gl_Position.z / 100.0; - // gl_Position.z = -gl_Position.z / 100.0; - // gl_Position.z = -1000.0 / (gl_Position.z + 10000.0); } diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 12b4fb15af..d59bfa7964 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core // #extension GL_ARB_texture_storage : require #include @@ -22,12 +22,12 @@ #include #include -in vec3 f_pos; +layout(location = 0) in vec3 f_pos; // in float f_ao; // in vec3 f_chunk_pos; // #ifdef FLUID_MODE_SHINY -flat in uint f_pos_norm; -flat in float f_load_time; +layout(location = 1) flat in uint f_pos_norm; +layout(location = 2) flat in float f_load_time; // #else // const uint f_pos_norm = 0u; // #endif @@ -35,7 +35,7 @@ flat in float f_load_time; // in vec4 f_shadow; // in vec3 f_col; // in float f_light; -/*centroid */in vec2 f_uv_pos; +/*centroid */layout(location = 3) in vec2 f_uv_pos; // in vec3 light_pos[2]; // const vec3 light_pos[6] = vec3[](vec3(0), vec3(0), vec3(00), vec3(0), vec3(0), vec3(0)); @@ -45,16 +45,19 @@ in vec4 sun_pos; const vec4 sun_pos = vec4(0.0); #endif */ -uniform sampler2D t_col_light; +layout(set = 3, binding = 0) +uniform texture2D t_col_light; +layout(set = 3, binding = 1) +uniform sampler s_col_light; -layout (std140) +layout (std140, set = 2, binding = 0) uniform u_locals { vec3 model_offs; float load_time; ivec4 atlas_offs; }; -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; #include #include @@ -82,7 +85,7 @@ void main() { // vec4 f_col_light = textureProj(t_col_light, vec3(f_uv_pos + 0.5, textureSize(t_col_light, 0)));//(f_uv_pos/* + 0.5*/) / texSize); // float f_light = textureProj(t_col_light, vec3(f_uv_pos + 0.5, textureSize(t_col_light, 0))).a;//1.0;//f_col_light.a * 4.0;// f_light = float(v_col_light & 0x3Fu) / 64.0; float f_light, f_glow; - vec3 f_col = greedy_extract_col_light_glow(t_col_light, f_uv_pos, f_light, f_glow); + vec3 f_col = greedy_extract_col_light_glow(t_col_light, s_col_light, f_uv_pos, f_light, f_glow); //float f_light = (uint(texture(t_col_light, (f_uv_pos + 0.5) / textureSize(t_col_light, 0)).r * 255.0) & 0x1Fu) / 31.0; // vec2 texSize = textureSize(t_col_light, 0); // float f_light = texture(t_col_light, f_uv_pos/* + vec2(atlas_offs.xy)*/).a;//1.0;//f_col_light.a * 4.0;// f_light = float(v_col_light & 0x3Fu) / 64.0; @@ -216,7 +219,7 @@ void main() { // float f_alt = alt_at(f_pos.xy); // vec4 f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy)); #if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP) - vec4 f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy)); + vec4 f_shadow = textureBicubic(t_horizon, s_horizon, pos_to_tex(f_pos.xy)); float sun_shade_frac = horizon_at2(f_shadow, f_alt, f_pos, sun_dir); #elif (SHADOW_MODE == SHADOW_MODE_NONE) float sun_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, sun_dir); diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl index 821b380d94..7fac4a0d2e 100644 --- a/assets/voxygen/shaders/terrain-vert.glsl +++ b/assets/voxygen/shaders/terrain-vert.glsl @@ -1,4 +1,4 @@ -#version 330 core +#version 420 core #include @@ -24,14 +24,15 @@ #include -in uint v_pos_norm; +layout(location = 0) in uint v_pos_norm; // in uint v_col_light; -in uint v_atlas_pos; +layout(location = 1) in uint v_atlas_pos; -layout (std140) +layout (std140, set = 2, binding = 0) uniform u_locals { vec3 model_offs; float load_time; + // TODO: consider whether these need to be signed ivec4 atlas_offs; }; @@ -45,10 +46,10 @@ uniform u_locals { // ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192]; //}; -out vec3 f_pos; +layout(location = 0) out vec3 f_pos; // #ifdef FLUID_MODE_SHINY -flat out uint f_pos_norm; -flat out float f_load_time; +layout(location = 1) flat out uint f_pos_norm; +layout(location = 2) flat out float f_load_time; // #if (SHADOW_MODE == SHADOW_MODE_MAP) // out vec4 sun_pos; @@ -60,7 +61,7 @@ flat out float f_load_time; // out vec3 f_col; // out vec3 f_chunk_pos; // out float f_ao; -/*centroid */out vec2 f_uv_pos; +/*centroid */layout(location = 3) out vec2 f_uv_pos; // out vec3 light_pos[2]; // out float f_light; @@ -155,7 +156,7 @@ void main() { #ifdef HAS_SHADOW_MAPS gl_Position = - /*all_mat*/shadowMats[0].shadowMatrices/*texture_mat*/ * + /*all_mat*/shadowMatrices/*texture_mat*/ * vec4(f_pos/*newRay*/, 1); gl_Position.z = clamp(gl_Position.z, -abs(gl_Position.w), abs(gl_Position.w)); #else diff --git a/assets/voxygen/shaders/ui-frag.glsl b/assets/voxygen/shaders/ui-frag.glsl index 693926e07b..d9010a2c90 100644 --- a/assets/voxygen/shaders/ui-frag.glsl +++ b/assets/voxygen/shaders/ui-frag.glsl @@ -1,28 +1,31 @@ -#version 330 core +#version 420 core #include -in vec2 f_uv; -in vec4 f_color; -flat in uint f_mode; +layout(location = 0) in vec2 f_uv; +layout(location = 1) in vec4 f_color; +layout(location = 2) flat in uint f_mode; -layout (std140) +layout (std140, set = 1, binding = 0) uniform u_locals { vec4 w_pos; }; -uniform sampler2D u_tex; +layout(set = 2, binding = 0) +uniform texture2D t_tex; +layout(set = 2, binding = 1) +uniform sampler s_tex; -out vec4 tgt_color; +layout(location = 0) out vec4 tgt_color; void main() { // Text if (f_mode == uint(0)) { - tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a); + tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(sampler2D(t_tex, s_tex), f_uv).a); // Image // HACK: bit 0 is set for both ordinary and north-facing images. } else if ((f_mode & uint(1)) == uint(1)) { - tgt_color = f_color * texture(u_tex, f_uv); + tgt_color = f_color * texture(sampler2D(t_tex, s_tex), f_uv); // 2D Geometry } else if (f_mode == uint(2)) { tgt_color = f_color; diff --git a/assets/voxygen/shaders/ui-vert.glsl b/assets/voxygen/shaders/ui-vert.glsl index 9f52da73f4..7c25f01fd3 100644 --- a/assets/voxygen/shaders/ui-vert.glsl +++ b/assets/voxygen/shaders/ui-vert.glsl @@ -1,23 +1,26 @@ -#version 330 core +#version 420 core #include -in vec2 v_pos; -in vec2 v_uv; -in vec2 v_center; -in vec4 v_color; -in uint v_mode; +layout(location = 0) in vec2 v_pos; +layout(location = 1) in vec2 v_uv; +layout(location = 2) in vec4 v_color; +layout(location = 3) in vec2 v_center; +layout(location = 4) in uint v_mode; -layout (std140) +layout (std140, set = 1, binding = 0) uniform u_locals { vec4 w_pos; }; -uniform sampler2D u_tex; +layout(set = 2, binding = 0) +uniform texture2D t_tex; +layout(set = 2, binding = 1) +uniform sampler s_tex; -out vec2 f_uv; -flat out uint f_mode; -out vec4 f_color; +layout(location = 0) out vec2 f_uv; +layout(location = 1) out vec4 f_color; +layout(location = 2) flat out uint f_mode; void main() { f_color = v_color; @@ -30,13 +33,13 @@ void main() { f_uv = v_uv; // Fixed scale In-game element vec4 projected_pos = /*proj_mat * view_mat*/all_mat * vec4(w_pos.xyz - focus_off.xyz, 1.0); - gl_Position = vec4(projected_pos.xy / projected_pos.w + v_pos/* * projected_pos.w*/, -1.0, /*projected_pos.w*/1.0); + gl_Position = vec4(projected_pos.xy / projected_pos.w + v_pos/* * projected_pos.w*/, 0.5, /*projected_pos.w*/1.0); } else if (v_mode == uint(3)) { // HACK: North facing source rectangle. - gl_Position = vec4(v_pos, -1.0, 1.0); + gl_Position = vec4(v_pos, 0.5, 1.0); vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2])); // TODO: Consider cleaning up matrix to something more efficient (e.g. a mat3). - vec2 aspect_ratio = textureSize(u_tex, 0).yx; + vec2 aspect_ratio = textureSize(sampler2D(t_tex, s_tex), 0).yx; mat2 look_at = mat2(look_at_dir.y, look_at_dir.x, -look_at_dir.x, look_at_dir.y); vec2 v_centered = (v_uv - v_center) / aspect_ratio; vec2 v_rotated = look_at * v_centered; @@ -50,11 +53,12 @@ void main() { mat2 look_at = mat2(look_at_dir.y, -look_at_dir.x, look_at_dir.x, look_at_dir.y); vec2 v_centered = (v_pos - v_center) / aspect_ratio; vec2 v_rotated = look_at * v_centered; - gl_Position = vec4(aspect_ratio * v_rotated + v_center, -1.0, 1.0); + gl_Position = vec4(aspect_ratio * v_rotated + v_center, 0.5, 1.0); } else { // Interface element f_uv = v_uv; - gl_Position = vec4(v_pos, -1.0, 1.0); + gl_Position = vec4(v_pos, 0.5, 1.0); } + f_mode = v_mode; } diff --git a/assets/voxygen/texture/waves.png b/assets/voxygen/texture/waves.png deleted file mode 100644 index c635e805af..0000000000 --- a/assets/voxygen/texture/waves.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:929041d2f1e54a5f2960622bcc90abd6c8119e59b381ce0218e198803256ce02 -size 7797 diff --git a/common/base/src/lib.rs b/common/base/src/lib.rs index 4f9d8ed951..3325612858 100644 --- a/common/base/src/lib.rs +++ b/common/base/src/lib.rs @@ -58,7 +58,10 @@ macro_rules! span { }; } -pub struct DummySpan; +#[cfg(feature = "tracy")] +pub struct ProfSpan(pub tracy_client::Span); +#[cfg(not(feature = "tracy"))] +pub struct ProfSpan; /// Like the span macro but only used when profiling and not in regular tracing /// operations @@ -66,16 +69,16 @@ pub struct DummySpan; macro_rules! prof_span { ($guard_name:tt, $name:expr) => { #[cfg(feature = "tracy")] - let $guard_name = $crate::tracy_client::Span::new( + let $guard_name = $crate::ProfSpan($crate::tracy_client::Span::new( $name, "", module_path!(), line!(), // No callstack since this has significant overhead 0, - ); + )); #[cfg(not(feature = "tracy"))] - let $guard_name = $crate::DummySpan; + let $guard_name = $crate::ProfSpan; }; } diff --git a/common/frontend/src/lib.rs b/common/frontend/src/lib.rs index 40278de603..78e4a0950b 100644 --- a/common/frontend/src/lib.rs +++ b/common/frontend/src/lib.rs @@ -47,7 +47,6 @@ where // this crate would be veloren_voxygen=debug. let base_exceptions = |env: EnvFilter| { env.add_directive("dot_vox::parser=warn".parse().unwrap()) - .add_directive("gfx_device_gl=warn".parse().unwrap()) .add_directive("veloren_common::trade=info".parse().unwrap()) .add_directive("veloren_world::sim=info".parse().unwrap()) .add_directive("veloren_world::civ=info".parse().unwrap()) @@ -58,6 +57,7 @@ where .add_directive("h2=info".parse().unwrap()) .add_directive("tokio_util=info".parse().unwrap()) .add_directive("rustls=info".parse().unwrap()) + .add_directive("wgpu_core::device=warn".parse().unwrap()) .add_directive("veloren_network_protocol=info".parse().unwrap()) .add_directive("quinn_proto::connection=info".parse().unwrap()) .add_directive( diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 6bd924bd55..e90f313489 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -22,14 +22,13 @@ runtimeLibs = ["libGL", "xorg.libX11", "xorg.libXcursor", "xorg.libXrandr", "xor buildInputs = ["xorg.libxcb"] [features] -gl = ["gfx_device_gl", "gfx_gl"] hot-anim = ["anim/use-dyn-lib"] singleplayer = ["server"] simd = ["vek/platform_intrinsics"] -tracy = ["common/tracy", "common-ecs/tracy", "common-frontend/tracy", "common-net/tracy", "common-systems/tracy", "common-state/tracy", "client/tracy"] +tracy = ["profiling", "profiling/profile-with-tracy", "common/tracy", "common-ecs/tracy", "common-frontend/tracy", "common-net/tracy", "common-systems/tracy", "common-state/tracy", "client/tracy"] plugins = ["client/plugins"] -default = ["gl", "singleplayer", "native-dialog", "plugins", "simd"] +default = ["singleplayer", "native-dialog", "plugins", "simd"] [dependencies] client = {package = "veloren-client", path = "../client"} @@ -45,12 +44,11 @@ anim = {package = "veloren-voxygen-anim", path = "anim"} i18n = {package = "veloren-i18n", path = "i18n"} # Graphics -gfx = "0.18.2" -gfx_device_gl = {version = "0.16.2", optional = true} -gfx_gl = {version = "0.6.1", optional = true} -glutin = "0.26.0" -old_school_gfx_glutin_ext = "0.26" winit = {version = "0.24.0", features = ["serde"]} +wgpu = { version = "=0.8.0", features = ["trace", "cross"] } +wgpu-profiler = { git = "https://github.com/Imberflur/wgpu-profiler", tag = "wgpu-0.8" } +bytemuck = { version="1.4", features=["derive"] } +shaderc = "0.6.2" # Ui conrod_core = {git = "https://gitlab.com/veloren/conrod.git", branch="copypasta_0.7"} @@ -87,9 +85,7 @@ crossbeam-channel = "0.5" directories-next = "2.0" dot_vox = "4.0" enum-iterator = "0.6" -strum = "0.20" -strum_macros = "0.20" -glsl-include = "0.3.1" +futures-executor = "0.3" guillotiere = "0.6" hashbrown = {version = "0.9", features = ["rayon", "serde", "nightly"]} image = {version = "0.23.12", default-features = false, features = ["ico", "png"]} @@ -98,9 +94,12 @@ native-dialog = { version = "0.5.2", optional = true } num = "0.4" ordered-float = { version = "2.0.1", default-features = false } rand = "0.8" +rayon = "1.5" rodio = {version = "0.13", default-features = false, features = ["vorbis"]} ron = {version = "0.6", default-features = false} serde = {version = "1.0", features = [ "rc", "derive" ]} +strum = "0.20" +strum_macros = "0.20" treeculler = "0.2" tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] } num_cpus = "1.0" @@ -110,6 +109,7 @@ itertools = "0.10.0" # Tracy tracing = "0.1" +profiling = { version = "1.0.1", default-features = false, optional = true } [target.'cfg(target_os = "macos")'.dependencies] dispatch = "0.1.4" diff --git a/voxygen/anim/Cargo.toml b/voxygen/anim/Cargo.toml index d56153f062..68f9ee5290 100644 --- a/voxygen/anim/Cargo.toml +++ b/voxygen/anim/Cargo.toml @@ -20,3 +20,4 @@ libloading = {version = "0.7", optional = true} notify = {version = "5.0.0-pre.2", optional = true} tracing = {version = "0.1", optional = true} vek = {version = "=0.14.1", features = ["serde"]} +bytemuck = { version="1.4", features=["derive"] } diff --git a/voxygen/anim/src/lib.rs b/voxygen/anim/src/lib.rs index d401074bda..b11e1410ad 100644 --- a/voxygen/anim/src/lib.rs +++ b/voxygen/anim/src/lib.rs @@ -74,16 +74,19 @@ pub use dyn_lib::init; use std::ffi::CStr; use self::vek::*; +use bytemuck::{Pod, Zeroable}; type MatRaw = [[f32; 4]; 4]; -pub type FigureBoneData = (MatRaw, MatRaw); +#[repr(C)] +#[derive(Debug, Clone, Copy, Pod, Zeroable, Default)] +pub struct FigureBoneData(pub MatRaw, pub MatRaw); pub const MAX_BONE_COUNT: usize = 16; fn make_bone(mat: Mat4) -> FigureBoneData { let normal = mat.map_cols(Vec4::normalized); - (mat.into_col_arrays(), normal.into_col_arrays()) + FigureBoneData(mat.into_col_arrays(), normal.into_col_arrays()) } pub type Bone = Transform; diff --git a/voxygen/benches/meshing_benchmark.rs b/voxygen/benches/meshing_benchmark.rs index 0d1f0d135a..a1e7e3a9c5 100644 --- a/voxygen/benches/meshing_benchmark.rs +++ b/voxygen/benches/meshing_benchmark.rs @@ -5,7 +5,7 @@ use common::{ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use std::sync::Arc; use vek::*; -use veloren_voxygen::{mesh::Meshable, scene::terrain::BlocksOfInterest}; +use veloren_voxygen::{mesh::terrain::generate_mesh, scene::terrain::BlocksOfInterest}; use world::{sim, World}; const CENTER: Vec2 = Vec2 { x: 512, y: 512 }; @@ -142,11 +142,10 @@ pub fn criterion_benchmark(c: &mut Criterion) { let (volume, range) = sample(Vec2::new(x, y)); meshing_benches.bench_function(&format!("Terrain mesh {}, {}", x, y), move |b| { b.iter(|| { - volume.generate_mesh(black_box(( - range, - Vec2::new(8192, 8192), - &BlocksOfInterest::default(), - ))) + generate_mesh( + black_box(&volume), + black_box((range, Vec2::new(8192, 8192), &BlocksOfInterest::default())), + ) }) }); } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 434f1526bb..e3a9cb36ef 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -55,7 +55,7 @@ use crate::{ ecs::{comp as vcomp, comp::HpFloaterList}, hud::{img_ids::ImgsRot, prompt_dialog::DialogOutcomeEvent}, i18n::Localization, - render::{Consts, Globals, Renderer}, + render::UiDrawer, scene::camera::{self, Camera}, session::{ settings_change::{Chat as ChatChange, Interface as InterfaceChange, SettingsChange}, @@ -238,6 +238,7 @@ widget_ids! { num_lights, num_figures, num_particles, + gpu_timings[], // Game Version version, @@ -2195,6 +2196,33 @@ impl Hud { .font_size(self.fonts.cyri.scale(14)) .set(self.ids.num_particles, ui_widgets); + // GPU timing for different pipelines + let gpu_timings = global_state.window.renderer().timings(); + if !gpu_timings.is_empty() { + let num_timings = gpu_timings.len(); + // Make sure we have enough ids + if self.ids.gpu_timings.len() < num_timings { + self.ids + .gpu_timings + .resize(num_timings, &mut ui_widgets.widget_id_generator()); + } + for (i, timing) in gpu_timings.iter().enumerate() { + Text::new(&format!( + "{:16}{:.3} ms", + &format!("{}:", timing.1), + timing.2 * 1000.0, + )) + .color(TEXT_COLOR) + .down(5.0) + .x_place_on( + ui_widgets.window, + conrod_core::position::Place::Start(Some(5.0 + 10.0 * timing.0 as f64)), + ) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .set(self.ids.gpu_timings[i], ui_widgets); + } + } // Help Window if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) { Text::new( @@ -2203,7 +2231,7 @@ impl Hud { .replace("{key}", help_key.display_string(key_layout).as_str()), ) .color(TEXT_COLOR) - .down_from(self.ids.num_particles, 5.0) + .down(5.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(14)) .set(self.ids.help_info, ui_widgets); @@ -3620,11 +3648,11 @@ impl Hud { events } - pub fn render(&self, renderer: &mut Renderer, globals: &Consts) { + pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { span!(_guard, "render", "Hud::render"); // Don't show anything if the UI is toggled off. if self.show.ui { - self.ui.render(renderer, Some(globals)); + self.ui.render(drawer); } } diff --git a/voxygen/src/hud/settings_window/interface.rs b/voxygen/src/hud/settings_window/interface.rs index 0d67613e96..4a4c5cb32f 100644 --- a/voxygen/src/hud/settings_window/interface.rs +++ b/voxygen/src/hud/settings_window/interface.rs @@ -36,6 +36,8 @@ widget_ids! { load_tips_button_label, debug_button, debug_button_label, + hitboxes_button, + hitboxes_button_label, ch_title, ch_transp_slider, ch_transp_value, @@ -239,9 +241,33 @@ impl<'a> Widget for Interface<'a> { .color(TEXT_COLOR) .set(state.ids.debug_button_label, ui); + // Hitboxes + let show_hitboxes = ToggleButton::new( + self.global_state.settings.interface.toggle_hitboxes, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .w_h(18.0, 18.0) + .down_from(state.ids.debug_button, 8.0) + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .set(state.ids.hitboxes_button, ui); + + if self.global_state.settings.interface.toggle_hitboxes != show_hitboxes { + events.push(ToggleHitboxes(show_hitboxes)); + } + + Text::new(&self.localized_strings.get("hud.settings.show_hitboxes")) + .right_from(state.ids.hitboxes_button, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.hitboxes_button) + .color(TEXT_COLOR) + .set(state.ids.hitboxes_button_label, ui); + // Ui Scale Text::new(&self.localized_strings.get("hud.settings.ui_scale")) - .down_from(state.ids.debug_button, 20.0) + .down_from(state.ids.hitboxes_button, 20.0) .font_size(self.fonts.cyri.scale(18)) .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) diff --git a/voxygen/src/hud/settings_window/video.rs b/voxygen/src/hud/settings_window/video.rs index 835bb1eb69..c064da77de 100644 --- a/voxygen/src/hud/settings_window/video.rs +++ b/voxygen/src/hud/settings_window/video.rs @@ -7,10 +7,10 @@ use crate::{ }, i18n::Localization, render::{ - AaMode, CloudMode, FluidMode, LightingMode, RenderMode, ShadowMapMode, ShadowMode, - UpscaleMode, + AaMode, CloudMode, FluidMode, LightingMode, PresentMode, RenderMode, ShadowMapMode, + ShadowMode, UpscaleMode, }, - session::settings_change::{Graphics as GraphicsChange, Graphics::*}, + session::settings_change::Graphics as GraphicsChange, settings::Fps, ui::{fonts::Fonts, ImageSlider, ToggleButton}, window::{FullScreenSettings, FullscreenMode}, @@ -35,6 +35,7 @@ widget_ids! { window_scrollbar, reset_graphics_button, fps_counter, + pipeline_recreation_text, vd_slider, vd_text, vd_value, @@ -50,6 +51,8 @@ widget_ids! { max_fps_slider, max_fps_text, max_fps_value, + present_mode_text, + present_mode_list, fov_slider, fov_text, fov_value, @@ -80,6 +83,9 @@ widget_ids! { refresh_rate, refresh_rate_label, // + gpu_profiler_button, + gpu_profiler_label, + // particles_button, particles_label, lossy_terrain_compression_button, @@ -205,6 +211,24 @@ impl<'a> Widget for Video<'a> { .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(18)) .set(state.ids.fps_counter, ui); + + // Pipeline recreation status + if let Some((total, complete)) = self + .global_state + .window + .renderer() + .pipeline_recreation_status() + { + Text::new(&format!("Rebuilding pipelines: ({}/{})", complete, total)) + .down_from(state.ids.fps_counter, 10.0) + .align_right_of(state.ids.fps_counter) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + // TODO: make color pulse or something + .color(TEXT_COLOR) + .set(state.ids.pipeline_recreation_text, ui); + } + // View Distance Text::new(&self.localized_strings.get("hud.settings.view_distance")) .top_left_with_margins_on(state.ids.window, 10.0, 10.0) @@ -229,7 +253,7 @@ impl<'a> Widget for Video<'a> { .pad_track((5.0, 5.0)) .set(state.ids.vd_slider, ui) { - events.push(AdjustViewDistance(new_val)); + events.push(GraphicsChange::AdjustViewDistance(new_val)); } Text::new(&format!( @@ -267,7 +291,7 @@ impl<'a> Widget for Video<'a> { .pad_track((5.0, 5.0)) .set(state.ids.max_fps_slider, ui) { - events.push(ChangeMaxFPS(FPS_CHOICES[which])); + events.push(GraphicsChange::ChangeMaxFPS(FPS_CHOICES[which])); } Text::new(&self.global_state.settings.graphics.max_fps.to_string()) @@ -277,6 +301,53 @@ impl<'a> Widget for Video<'a> { .color(TEXT_COLOR) .set(state.ids.max_fps_value, ui); + // Get render mode + let render_mode = &self.global_state.settings.graphics.render_mode; + + // Present Mode + Text::new(&self.localized_strings.get("hud.settings.present_mode")) + .down_from(state.ids.vd_slider, 10.0) + .right_from(state.ids.max_fps_value, 30.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.present_mode_text, ui); + + let mode_list = [ + PresentMode::Fifo, + PresentMode::Mailbox, + PresentMode::Immediate, + ]; + let mode_label_list = [ + &self.localized_strings.get("hud.settings.present_mode.fifo"), + &self + .localized_strings + .get("hud.settings.present_mode.mailbox"), + &self + .localized_strings + .get("hud.settings.present_mode.immediate"), + ]; + + // Get which present mode is currently active + let selected = mode_list + .iter() + .position(|x| *x == render_mode.present_mode); + + if let Some(clicked) = DropDownList::new(&mode_label_list, selected) + .w_h(120.0, 22.0) + .color(MENU_BG) + .label_color(TEXT_COLOR) + .label_font_id(self.fonts.cyri.conrod_id) + .down_from(state.ids.present_mode_text, 8.0) + .align_middle_x() + .set(state.ids.present_mode_list, ui) + { + events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode { + present_mode: mode_list[clicked], + ..render_mode.clone() + }))); + } + // FOV Text::new(&self.localized_strings.get("hud.settings.fov")) .down_from(state.ids.max_fps_slider, 10.0) @@ -299,7 +370,7 @@ impl<'a> Widget for Video<'a> { .pad_track((5.0, 5.0)) .set(state.ids.fov_slider, ui) { - events.push(ChangeFOV(new_val)); + events.push(GraphicsChange::ChangeFOV(new_val)); } Text::new(&format!("{}", self.global_state.settings.graphics.fov)) @@ -332,7 +403,7 @@ impl<'a> Widget for Video<'a> { .pad_track((5.0, 5.0)) .set(state.ids.lod_detail_slider, ui) { - events.push(AdjustLodDetail( + events.push(GraphicsChange::AdjustLodDetail( (5.0f32.powf(new_val as f32 / 10.0) * 100.0) as u32, )); } @@ -369,7 +440,9 @@ impl<'a> Widget for Video<'a> { .pad_track((5.0, 5.0)) .set(state.ids.gamma_slider, ui) { - events.push(ChangeGamma(2.0f32.powf(new_val as f32 / 8.0))); + events.push(GraphicsChange::ChangeGamma( + 2.0f32.powf(new_val as f32 / 8.0), + )); } Text::new(&format!("{:.2}", self.global_state.settings.graphics.gamma)) @@ -394,7 +467,7 @@ impl<'a> Widget for Video<'a> { .pad_track((5.0, 5.0)) .set(state.ids.exposure_slider, ui) { - events.push(ChangeExposure(new_val as f32 / 16.0)); + events.push(GraphicsChange::ChangeExposure(new_val as f32 / 16.0)); } Text::new(&self.localized_strings.get("hud.settings.exposure")) @@ -432,7 +505,7 @@ impl<'a> Widget for Video<'a> { .pad_track((5.0, 5.0)) .set(state.ids.ambiance_slider, ui) { - events.push(ChangeAmbiance(new_val as f32)); + events.push(GraphicsChange::ChangeAmbiance(new_val as f32)); } Text::new(&self.localized_strings.get("hud.settings.ambiance")) .up_from(state.ids.ambiance_slider, 8.0) @@ -468,7 +541,7 @@ impl<'a> Widget for Video<'a> { .pad_track((5.0, 5.0)) .set(state.ids.sprite_dist_slider, ui) { - events.push(AdjustSpriteRenderDistance(new_val)); + events.push(GraphicsChange::AdjustSpriteRenderDistance(new_val)); } Text::new( &self @@ -508,7 +581,7 @@ impl<'a> Widget for Video<'a> { .pad_track((5.0, 5.0)) .set(state.ids.figure_dist_slider, ui) { - events.push(AdjustFigureLoDRenderDistance(new_val)); + events.push(GraphicsChange::AdjustFigureLoDRenderDistance(new_val)); } Text::new( &self @@ -534,8 +607,6 @@ impl<'a> Widget for Video<'a> { .color(TEXT_COLOR) .set(state.ids.figure_dist_value, ui); - let render_mode = &self.global_state.settings.graphics.render_mode; - // AaMode Text::new(&self.localized_strings.get("hud.settings.antialiasing_mode")) .down_from(state.ids.gamma_slider, 8.0) @@ -572,7 +643,7 @@ impl<'a> Widget for Video<'a> { .down_from(state.ids.aa_mode_text, 8.0) .set(state.ids.aa_mode_list, ui) { - events.push(ChangeRenderMode(Box::new(RenderMode { + events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode { aa: mode_list[clicked], ..render_mode.clone() }))); @@ -612,7 +683,7 @@ impl<'a> Widget for Video<'a> { .down_from(state.ids.upscale_factor_text, 8.0) .set(state.ids.upscale_factor_list, ui) { - events.push(ChangeRenderMode(Box::new(RenderMode { + events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode { upscale_mode: UpscaleMode { factor: upscale_factors[clicked], }, @@ -670,7 +741,7 @@ impl<'a> Widget for Video<'a> { .down_from(state.ids.cloud_mode_text, 8.0) .set(state.ids.cloud_mode_list, ui) { - events.push(ChangeRenderMode(Box::new(RenderMode { + events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode { cloud: mode_list[clicked], ..render_mode.clone() }))); @@ -709,7 +780,7 @@ impl<'a> Widget for Video<'a> { .down_from(state.ids.fluid_mode_text, 8.0) .set(state.ids.fluid_mode_list, ui) { - events.push(ChangeRenderMode(Box::new(RenderMode { + events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode { fluid: mode_list[clicked], ..render_mode.clone() }))); @@ -755,7 +826,7 @@ impl<'a> Widget for Video<'a> { .down_from(state.ids.lighting_mode_text, 8.0) .set(state.ids.lighting_mode_list, ui) { - events.push(ChangeRenderMode(Box::new(RenderMode { + events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode { lighting: mode_list[clicked], ..render_mode.clone() }))); @@ -802,7 +873,7 @@ impl<'a> Widget for Video<'a> { .down_from(state.ids.shadow_mode_text, 8.0) .set(state.ids.shadow_mode_list, ui) { - events.push(ChangeRenderMode(Box::new(RenderMode { + events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode { shadow: mode_list[clicked], ..render_mode.clone() }))); @@ -835,7 +906,7 @@ impl<'a> Widget for Video<'a> { .pad_track((5.0, 5.0)) .set(state.ids.shadow_mode_map_resolution_slider, ui) { - events.push(ChangeRenderMode(Box::new(RenderMode { + events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode { shadow: ShadowMode::Map(ShadowMapMode { resolution: 2.0f32.powf(f32::from(new_val) / 4.0), }), @@ -853,11 +924,37 @@ impl<'a> Widget for Video<'a> { .set(state.ids.shadow_mode_map_resolution_value, ui); } + // GPU Profiler + Text::new(&self.localized_strings.get("hud.settings.gpu_profiler")) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .down_from(state.ids.shadow_mode_list, 8.0) + .color(TEXT_COLOR) + .set(state.ids.gpu_profiler_label, ui); + + let gpu_profiler_enabled = ToggleButton::new( + render_mode.profiler_enabled, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .w_h(18.0, 18.0) + .right_from(state.ids.gpu_profiler_label, 10.0) + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .set(state.ids.gpu_profiler_button, ui); + + if render_mode.profiler_enabled != gpu_profiler_enabled { + events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode { + profiler_enabled: gpu_profiler_enabled, + ..render_mode.clone() + }))); + } + // Particles Text::new(&self.localized_strings.get("hud.settings.particles")) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) - .down_from(state.ids.shadow_mode_list, 8.0) + .down_from(state.ids.gpu_profiler_label, 8.0) .color(TEXT_COLOR) .set(state.ids.particles_label, ui); @@ -873,7 +970,7 @@ impl<'a> Widget for Video<'a> { .set(state.ids.particles_button, ui); if self.global_state.settings.graphics.particles_enabled != particles_enabled { - events.push(ToggleParticlesEnabled(particles_enabled)); + events.push(GraphicsChange::ToggleParticlesEnabled(particles_enabled)); } // Lossy terrain compression @@ -909,7 +1006,9 @@ impl<'a> Widget for Video<'a> { .lossy_terrain_compression != lossy_terrain_compression { - events.push(ToggleLossyTerrainCompression(lossy_terrain_compression)); + events.push(GraphicsChange::ToggleLossyTerrainCompression( + lossy_terrain_compression, + )); } // Resolution @@ -946,7 +1045,7 @@ impl<'a> Widget for Video<'a> { .down_from(state.ids.resolution_label, 10.0) .set(state.ids.resolution, ui) { - events.push(ChangeFullscreenMode(FullScreenSettings { + events.push(GraphicsChange::ChangeFullscreenMode(FullScreenSettings { resolution: resolutions[clicked], ..self.global_state.settings.graphics.fullscreen })); @@ -1010,7 +1109,7 @@ impl<'a> Widget for Video<'a> { .right_from(state.ids.resolution, 8.0) .set(state.ids.bit_depth, ui) { - events.push(ChangeFullscreenMode(FullScreenSettings { + events.push(GraphicsChange::ChangeFullscreenMode(FullScreenSettings { bit_depth: if clicked == 0 { None } else { @@ -1064,7 +1163,7 @@ impl<'a> Widget for Video<'a> { .right_from(state.ids.bit_depth, 8.0) .set(state.ids.refresh_rate, ui) { - events.push(ChangeFullscreenMode(FullScreenSettings { + events.push(GraphicsChange::ChangeFullscreenMode(FullScreenSettings { refresh_rate: if clicked == 0 { None } else { @@ -1094,7 +1193,7 @@ impl<'a> Widget for Video<'a> { .set(state.ids.fullscreen_button, ui); if self.global_state.settings.graphics.fullscreen.enabled != enabled { - events.push(ChangeFullscreenMode(FullScreenSettings { + events.push(GraphicsChange::ChangeFullscreenMode(FullScreenSettings { enabled, ..self.global_state.settings.graphics.fullscreen })); @@ -1131,7 +1230,7 @@ impl<'a> Widget for Video<'a> { .down_from(state.ids.fullscreen_mode_text, 8.0) .set(state.ids.fullscreen_mode_list, ui) { - events.push(ChangeFullscreenMode(FullScreenSettings { + events.push(GraphicsChange::ChangeFullscreenMode(FullScreenSettings { mode: mode_list[clicked], ..self.global_state.settings.graphics.fullscreen })); @@ -1151,7 +1250,7 @@ impl<'a> Widget for Video<'a> { .set(state.ids.save_window_size_button, ui) .was_clicked() { - events.push(AdjustWindowSize( + events.push(GraphicsChange::AdjustWindowSize( self.global_state .window .logical_size() @@ -1175,7 +1274,7 @@ impl<'a> Widget for Video<'a> { .set(state.ids.reset_graphics_button, ui) .was_clicked() { - events.push(ResetGraphicsSettings); + events.push(GraphicsChange::ResetGraphicsSettings); } events diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index ebf188850c..ae15b7a07d 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -2,7 +2,14 @@ #![allow(incomplete_features)] #![allow(clippy::option_map_unit_fn)] #![deny(clippy::clone_on_ref_ptr)] -#![feature(array_map, bool_to_option, const_generics, drain_filter, once_cell)] +#![feature( + array_map, + bool_to_option, + const_generics, + drain_filter, + once_cell, + trait_alias +)] #![recursion_limit = "2048"] #[macro_use] diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index ae3f7c0a32..2b9acd78b6 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -20,6 +20,7 @@ pub struct CharSelectionState { char_selection_ui: CharSelectionUi, client: Rc>, scene: Scene, + need_shadow_clear: bool, } impl CharSelectionState { @@ -36,6 +37,7 @@ impl CharSelectionState { char_selection_ui, client, scene, + need_shadow_clear: false, } } @@ -71,6 +73,9 @@ impl PlayState for CharSelectionState { // Set scale mode in case it was change self.char_selection_ui .set_scale_mode(global_state.settings.interface.ui_scale); + + // Clear shadow textures since we don't render to them here + self.need_shadow_clear = true; } fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { @@ -228,15 +233,39 @@ impl PlayState for CharSelectionState { fn name(&self) -> &'static str { "Character Selection" } fn render(&mut self, renderer: &mut Renderer, _: &Settings) { + let mut drawer = match renderer + .start_recording_frame(self.scene.global_bind_group()) + .expect("Unrecoverable render error when starting a new frame!") + { + Some(d) => d, + // Couldn't get swap chain texture this fime + None => return, + }; + + if self.need_shadow_clear { + drawer.clear_shadows(); + self.need_shadow_clear = false; + } + let client = self.client.borrow(); let (humanoid_body, loadout) = Self::get_humanoid_body_inventory(&self.char_selection_ui, &client); - // Render the scene. - self.scene - .render(renderer, client.get_tick(), humanoid_body, loadout); + if let Some(mut first_pass) = drawer.first_pass() { + self.scene + .render(&mut first_pass, client.get_tick(), humanoid_body, loadout); + } + // Clouds + if let Some(mut second_pass) = drawer.second_pass() { + second_pass.draw_clouds(); + } + // PostProcess and UI + let mut third_pass = drawer.third_pass(); + third_pass.draw_postprocess(); // Draw the UI to the screen. - self.char_selection_ui.render(renderer); + if let Some(mut ui_drawer) = third_pass.draw_ui() { + self.char_selection_ui.render(&mut ui_drawer); + }; } } diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs index 0ce849a58e..cc1c853910 100644 --- a/voxygen/src/menu/char_selection/ui/mod.rs +++ b/voxygen/src/menu/char_selection/ui/mod.rs @@ -1,6 +1,6 @@ use crate::{ i18n::{Localization, LocalizationHandle}, - render::Renderer, + render::UiDrawer, ui::{ self, fonts::IcedFonts as Fonts, @@ -1584,8 +1584,7 @@ impl CharSelectionUi { events } - // TODO: do we need globals? - pub fn render(&self, renderer: &mut Renderer) { self.ui.render(renderer); } + pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { self.ui.render(drawer); } } #[derive(Default)] diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index d03c45167b..0aa482913d 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -1,4 +1,5 @@ mod client_init; +mod scene; mod ui; use super::char_selection::CharSelectionState; @@ -11,20 +12,41 @@ use crate::{ use client::{ addr::ConnectionArgs, error::{InitProtocolError, NetworkConnectError, NetworkError}, - ServerInfo, + Client, ServerInfo, }; use client_init::{ClientInit, Error as InitError, Msg as InitMsg}; use common::comp; use common_base::span; +use scene::Scene; use std::sync::Arc; use tokio::runtime; use tracing::error; use ui::{Event as MainMenuEvent, MainMenuUi}; +// TODO: show status messages for waiting on server creation, client init, and +// pipeline creation (we can show progress of pipeline creation) +enum InitState { + None, + // Waiting on the client initialization + Client(ClientInit), + // Client initialized but still waiting on Renderer pipeline creation + Pipeline(Box), +} + +impl InitState { + fn client(&self) -> Option<&ClientInit> { + if let Self::Client(client_init) = &self { + Some(client_init) + } else { + None + } + } +} + pub struct MainMenuState { main_menu_ui: MainMenuUi, - // Used for client creation. - client_init: Option, + init: InitState, + scene: Scene, } impl MainMenuState { @@ -32,7 +54,8 @@ impl MainMenuState { pub fn new(global_state: &mut GlobalState) -> Self { Self { main_menu_ui: MainMenuUi::new(global_state), - client_init: None, + init: InitState::None, + scene: Scene::new(global_state.window.renderer_mut()), } } } @@ -74,14 +97,14 @@ impl PlayState for MainMenuState { "singleplayer".to_owned(), "".to_owned(), ConnectionArgs::Mpsc(14004), - &mut self.client_init, + &mut self.init, Some(runtime), ); }, Ok(Err(e)) => { error!(?e, "Could not start server"); global_state.singleplayer = None; - self.client_init = None; + self.init = InitState::None; self.main_menu_ui.cancel_connection(); self.main_menu_ui.show_info(format!("Error: {:?}", e)); }, @@ -104,19 +127,14 @@ impl PlayState for MainMenuState { } } // Poll client creation. - match self.client_init.as_ref().and_then(|init| init.poll()) { + match self.init.client().and_then(|init| init.poll()) { Some(InitMsg::Done(Ok(mut client))) => { - self.client_init = None; - self.main_menu_ui.connected(); // Register voxygen components / resources crate::ecs::init(client.state_mut().ecs_mut()); - return PlayStateResult::Push(Box::new(CharSelectionState::new( - global_state, - std::rc::Rc::new(std::cell::RefCell::new(client)), - ))); + self.init = InitState::Pipeline(Box::new(client)); }, Some(InitMsg::Done(Err(e))) => { - self.client_init = None; + self.init = InitState::None; tracing::trace!(?e, "raw Client Init error"); let e = get_client_msg_error(e, &global_state.i18n); // Log error for possible additional use later or incase that the error @@ -132,10 +150,7 @@ impl PlayState for MainMenuState { .contains(&auth_server) { // Can't fail since we just polled it, it must be Some - self.client_init - .as_ref() - .unwrap() - .auth_trust(auth_server, true); + self.init.client().unwrap().auth_trust(auth_server, true); } else { // Show warning that auth server is not trusted and prompt for approval self.main_menu_ui.auth_trust_prompt(auth_server); @@ -144,6 +159,64 @@ impl PlayState for MainMenuState { None => {}, } + // Tick the client to keep the connection alive if we are waiting on pipelines + let localized_strings = &global_state.i18n.read(); + if let InitState::Pipeline(client) = &mut self.init { + match client.tick( + comp::ControllerInputs::default(), + global_state.clock.dt(), + |_| {}, + ) { + Ok(events) => { + for event in events { + match event { + client::Event::SetViewDistance(vd) => { + global_state.settings.graphics.view_distance = vd; + global_state.settings.save_to_file_warn(); + }, + client::Event::Disconnect => { + global_state.info_message = Some( + localized_strings + .get("main.login.server_shut_down") + .to_owned(), + ); + self.init = InitState::None; + }, + _ => {}, + } + } + }, + Err(err) => { + global_state.info_message = + Some(localized_strings.get("common.connection_lost").to_owned()); + error!(?err, "[main menu] Failed to tick the client"); + self.init = InitState::None; + }, + } + } + + // Poll renderer pipeline creation + if let InitState::Pipeline(..) = &self.init { + // If not complete go to char select screen + if global_state + .window + .renderer() + .pipeline_creation_status() + .is_none() + { + // Always succeeds since we check above + if let InitState::Pipeline(client) = + core::mem::replace(&mut self.init, InitState::None) + { + self.main_menu_ui.connected(); + return PlayStateResult::Push(Box::new(CharSelectionState::new( + global_state, + std::rc::Rc::new(std::cell::RefCell::new(*client)), + ))); + } + } + } + // Maintain the UI. for event in self .main_menu_ui @@ -180,19 +253,19 @@ impl PlayState for MainMenuState { username, password, connection_args, - &mut self.client_init, + &mut self.init, None, ); }, MainMenuEvent::CancelLoginAttempt => { - // client_init contains Some(ClientInit), which spawns a thread which contains a - // TcpStream::connect() call This call is blocking - // TODO fix when the network rework happens + // init contains InitState::Client(ClientInit), which spawns a thread which + // contains a TcpStream::connect() call This call is + // blocking TODO fix when the network rework happens #[cfg(feature = "singleplayer")] { global_state.singleplayer = None; } - self.client_init = None; + self.init = InitState::None; self.main_menu_ui.cancel_connection(); }, MainMenuEvent::ChangeLanguage(new_language) => { @@ -228,8 +301,8 @@ impl PlayState for MainMenuState { .insert(auth_server.clone()); global_state.settings.save_to_file_warn(); } - self.client_init - .as_ref() + self.init + .client() .map(|init| init.auth_trust(auth_server, trust)); }, } @@ -245,8 +318,19 @@ impl PlayState for MainMenuState { fn name(&self) -> &'static str { "Title" } fn render(&mut self, renderer: &mut Renderer, _: &Settings) { + let mut drawer = match renderer + .start_recording_frame(self.scene.global_bind_group()) + .expect("Unrecoverable render error when starting a new frame!") + { + Some(d) => d, + // Couldn't get swap chain texture this frame + None => return, + }; + // Draw the UI to the screen. - self.main_menu_ui.render(renderer); + if let Some(mut ui_drawer) = drawer.third_pass().draw_ui() { + self.main_menu_ui.render(&mut ui_drawer); + }; } } @@ -354,7 +438,7 @@ fn attempt_login( username: String, password: String, connection_args: ConnectionArgs, - client_init: &mut Option, + init: &mut InitState, runtime: Option>, ) { if let Err(err) = comp::Player::alias_validate(&username) { @@ -363,8 +447,8 @@ fn attempt_login( } // Don't try to connect if there is already a connection in progress. - if client_init.is_none() { - *client_init = Some(ClientInit::new( + if let InitState::None = init { + *init = InitState::Client(ClientInit::new( connection_args, username, password, diff --git a/voxygen/src/menu/main/scene.rs b/voxygen/src/menu/main/scene.rs new file mode 100644 index 0000000000..77f5c709d0 --- /dev/null +++ b/voxygen/src/menu/main/scene.rs @@ -0,0 +1,28 @@ +use crate::render::{ + GlobalModel, Globals, GlobalsBindGroup, Light, LodData, PointLightMatrix, Renderer, Shadow, + ShadowLocals, +}; + +pub struct Scene { + bind_group: GlobalsBindGroup, +} + +impl Scene { + pub fn new(renderer: &mut Renderer) -> Self { + let global_data = GlobalModel { + globals: renderer.create_consts(&[Globals::default()]), + lights: renderer.create_consts(&[Light::default(); 32]), + shadows: renderer.create_consts(&[Shadow::default(); 32]), + shadow_mats: renderer.create_shadow_bound_locals(&[ShadowLocals::default()]), + point_light_matrices: Box::new([PointLightMatrix::default(); 126]), + }; + + let lod_data = LodData::dummy(renderer); + + let bind_group = renderer.bind_globals(&global_data, &lod_data); + + Self { bind_group } + } + + pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.bind_group } +} diff --git a/voxygen/src/menu/main/ui/mod.rs b/voxygen/src/menu/main/ui/mod.rs index 34de2acd5f..2df020dd57 100644 --- a/voxygen/src/menu/main/ui/mod.rs +++ b/voxygen/src/menu/main/ui/mod.rs @@ -6,7 +6,7 @@ mod servers; use crate::{ i18n::{LanguageMetadata, LocalizationHandle}, - render::Renderer, + render::UiDrawer, ui::{ self, fonts::IcedFonts as Fonts, @@ -477,7 +477,7 @@ pub struct MainMenuUi { controls: Controls, } -impl<'a> MainMenuUi { +impl MainMenuUi { pub fn new(global_state: &mut GlobalState) -> Self { // Load language let i18n = &global_state.i18n.read(); @@ -583,5 +583,5 @@ impl<'a> MainMenuUi { events } - pub fn render(&self, renderer: &mut Renderer) { self.ui.render(renderer); } + pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { self.ui.render(drawer); } } diff --git a/voxygen/src/mesh/greedy.rs b/voxygen/src/mesh/greedy.rs index f39094fb7a..ef8f4f5014 100644 --- a/voxygen/src/mesh/greedy.rs +++ b/voxygen/src/mesh/greedy.rs @@ -1,9 +1,7 @@ -use crate::render::{self, mesh::Quad, ColLightFmt, ColLightInfo, TerrainPipeline}; +use crate::render::{mesh::Quad, ColLightInfo, TerrainVertex, Vertex}; use common_base::span; use vek::*; -type TerrainVertex = ::Vertex; - type TodoRect = ( Vec3, Vec2>, @@ -123,7 +121,7 @@ impl<'a> GreedyMesh<'a> { small_size_threshold, large_size_threshold, }); - let col_lights_size = Vec2::new(1u16, 1u16); + let col_lights_size = Vec2::new(1, 1); Self { atlas, col_lights_size, @@ -152,7 +150,7 @@ impl<'a> GreedyMesh<'a> { FO: for<'r> FnMut(&'r mut D, Vec3) -> bool + 'a, FS: for<'r> FnMut(&'r mut D, Vec3, Vec3, Vec2>) -> Option<(bool, M)>, FP: FnMut(Vec2, Vec2>, Vec3, Vec2>, Vec3, &M), - FT: for<'r> FnMut(&'r mut D, Vec3, u8, u8) -> <::Surface as gfx::format::SurfaceTyped>::DataType + 'a, + FT: for<'r> FnMut(&'r mut D, Vec3, u8, u8) -> [u8; 4] + 'a, { span!(_guard, "push", "GreedyMesh::push"); let cont = greedy_mesh( @@ -178,7 +176,7 @@ impl<'a> GreedyMesh<'a> { let cur_size = self.col_lights_size; let col_lights = vec![ TerrainVertex::make_col_light(254, 0, Rgb::broadcast(254)); - usize::from(cur_size.x) * usize::from(cur_size.y) + cur_size.x as usize * cur_size.y as usize ]; let mut col_lights_info = (col_lights, cur_size); self.suspended.into_iter().for_each(|cont| { @@ -213,7 +211,7 @@ where FO: for<'r> FnMut(&'r mut D, Vec3) -> bool + 'a, FS: for<'r> FnMut(&'r mut D, Vec3, Vec3, Vec2>) -> Option<(bool, M)>, FP: FnMut(Vec2, Vec2>, Vec3, Vec2>, Vec3, &M), - FT: for<'r> FnMut(&'r mut D, Vec3, u8, u8) -> <::Surface as gfx::format::SurfaceTyped>::DataType + 'a, + FT: for<'r> FnMut(&'r mut D, Vec3, u8, u8) -> [u8; 4] + 'a, { span!(_guard, "greedy_mesh"); // TODO: Collect information to see if we can choose a good value here. @@ -507,7 +505,7 @@ fn draw_col_lights( mut get_light: impl FnMut(&mut D, Vec3) -> f32, mut get_glow: impl FnMut(&mut D, Vec3) -> f32, mut get_opacity: impl FnMut(&mut D, Vec3) -> bool, - mut make_face_texel: impl FnMut(&mut D, Vec3, u8, u8) -> <::Surface as gfx::format::SurfaceTyped>::DataType, + mut make_face_texel: impl FnMut(&mut D, Vec3, u8, u8) -> [u8; 4], ) { todo_rects.into_iter().for_each(|(pos, uv, rect, delta)| { // NOTE: Conversions are safe because width, height, and offset must be @@ -520,7 +518,7 @@ fn draw_col_lights( let uv = uv.map(|e| e.map(i32::from)); let pos = pos + draw_delta; (0..height).for_each(|v| { - let start = usize::from(cur_size.x) * usize::from(top + v) + usize::from(left); + let start = cur_size.x as usize * usize::from(top + v) + usize::from(left); (0..width) .zip(&mut col_lights[start..start + usize::from(width)]) .for_each(|(u, col_light)| { @@ -622,14 +620,14 @@ fn create_quad_greedy( push_quad(atlas_pos, dim, origin, draw_dim, norm, meta); } -pub fn create_quad( +pub fn create_quad( atlas_pos: Vec2, dim: Vec2>, origin: Vec3, draw_dim: Vec2>, norm: Vec3, meta: &M, - create_vertex: impl Fn(Vec2, Vec3, Vec3, &M) -> O::Vertex, + create_vertex: impl Fn(Vec2, Vec3, Vec3, &M) -> O, ) -> Quad { Quad::new( create_vertex(atlas_pos, origin, norm, meta), diff --git a/voxygen/src/mesh/mod.rs b/voxygen/src/mesh/mod.rs index 657d635e32..b8c1ed8af9 100644 --- a/voxygen/src/mesh/mod.rs +++ b/voxygen/src/mesh/mod.rs @@ -2,25 +2,6 @@ pub mod greedy; pub mod segment; pub mod terrain; -use crate::render::{self, Mesh}; +use crate::render::Mesh; -pub type MeshGen = ( - Mesh<>::Pipeline>, - Mesh<>::TranslucentPipeline>, - Mesh<>::ShadowPipeline>, - >::Result, -); - -/// FIXME: Remove this whole trait at some point. This "abstraction" is never -/// abstracted over, and is organized completely differently from how we -/// actually mesh things nowadays. -pub trait Meshable { - type Pipeline: render::Pipeline; - type TranslucentPipeline: render::Pipeline; - type ShadowPipeline: render::Pipeline; - type Supplement; - type Result; - - // Generate meshes - one opaque, one translucent, one shadow - fn generate_mesh(self, supp: Self::Supplement) -> MeshGen; -} +pub type MeshGen = (Mesh, Mesh, Mesh, R); diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index eca4cc9d62..0de1b21701 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -1,12 +1,9 @@ use crate::{ mesh::{ greedy::{self, GreedyConfig, GreedyMesh}, - MeshGen, Meshable, - }, - render::{ - self, FigurePipeline, Mesh, ParticlePipeline, ShadowPipeline, SpritePipeline, - TerrainPipeline, + MeshGen, }, + render::{Mesh, ParticleVertex, SpriteVertex, TerrainVertex}, scene::math, }; use common::{ @@ -16,353 +13,315 @@ use common::{ use core::convert::TryFrom; use vek::*; -type SpriteVertex = ::Vertex; -type TerrainVertex = ::Vertex; -type ParticleVertex = ::Vertex; - -impl<'a: 'b, 'b, V: 'a> Meshable> for V -where - V: BaseVol + ReadVol + SizedVol, - /* TODO: Use VolIterator instead of manually iterating - * &'a V: IntoVolIterator<'a> + IntoFullVolIterator<'a>, - * &'a V: BaseVol, */ -{ - type Pipeline = TerrainPipeline; - type Result = math::Aabb; - type ShadowPipeline = ShadowPipeline; - /// NOTE: bone_idx must be in [0, 15] (may be bumped to [0, 31] at some - /// point). - type Supplement = ( +// /// NOTE: bone_idx must be in [0, 15] (may be bumped to [0, 31] at some +// /// point). +#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 +// TODO: this function name... +pub fn generate_mesh_base_vol_terrain<'a: 'b, 'b, V: 'a>( + vol: V, + (greedy, opaque_mesh, offs, scale, bone_idx): ( &'b mut GreedyMesh<'a>, - &'b mut Mesh, + &'b mut Mesh, Vec3, Vec3, u8, - ); - type TranslucentPipeline = FigurePipeline; - - #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 - fn generate_mesh( - self, - (greedy, opaque_mesh, offs, scale, bone_idx): Self::Supplement, - ) -> MeshGen, Self> { - assert!(bone_idx <= 15, "Bone index for figures must be in [0, 15]"); - - let max_size = greedy.max_size(); - // NOTE: Required because we steal two bits from the normal in the shadow uint - // in order to store the bone index. The two bits are instead taken out - // of the atlas coordinates, which is why we "only" allow 1 << 15 per - // coordinate instead of 1 << 16. - assert!(max_size.width.max(max_size.height) < 1 << 15); - - let lower_bound = self.lower_bound(); - let upper_bound = self.upper_bound(); - assert!( - lower_bound.x <= upper_bound.x - && lower_bound.y <= upper_bound.y - && lower_bound.z <= upper_bound.z - ); - // NOTE: Figure sizes should be no more than 512 along each axis. - let greedy_size = upper_bound - lower_bound + 1; - assert!(greedy_size.x <= 512 && greedy_size.y <= 512 && greedy_size.z <= 512); - // NOTE: Cast to usize is safe because of previous check, since all values fit - // into u16 which is safe to cast to usize. - let greedy_size = greedy_size.as_::(); - let greedy_size_cross = greedy_size; - let draw_delta = lower_bound; - - let get_light = |vol: &mut V, pos: Vec3| { - if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { - 1.0 - } else { - 0.0 - } - }; - let get_glow = |_vol: &mut V, _pos: Vec3| 0.0; - let get_opacity = - |vol: &mut V, pos: Vec3| vol.get(pos).map_or(true, |vox| vox.is_empty()); - let should_draw = |vol: &mut V, pos: Vec3, delta: Vec3, uv| { - should_draw_greedy(pos, delta, uv, |vox| { - vol.get(vox).map(|vox| *vox).unwrap_or(Cell::empty()) - }) - }; - let create_opaque = |atlas_pos, pos, norm| { - TerrainVertex::new_figure(atlas_pos, (pos + offs) * scale, norm, bone_idx) - }; - - greedy.push(GreedyConfig { - data: self, - draw_delta, - greedy_size, - greedy_size_cross, - get_light, - get_glow, - get_opacity, - should_draw, - push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &()| { - opaque_mesh.push_quad(greedy::create_quad( - atlas_origin, - dim, - origin, - draw_dim, - norm, - meta, - |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm), - )); - }, - make_face_texel: |vol: &mut V, pos, light, _| { - let cell = vol.get(pos).ok(); - let (glowy, shiny) = cell - .map(|c| (c.is_glowy(), c.is_shiny())) - .unwrap_or_default(); - let col = cell.and_then(|vox| vox.get_color()).unwrap_or(Rgb::zero()); - TerrainVertex::make_col_light_figure(light, glowy, shiny, col) - }, - }); - let bounds = math::Aabb { - // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16. - min: math::Vec3::from((lower_bound.as_::() + offs) * scale), - max: math::Vec3::from((upper_bound.as_::() + offs) * scale), - } - .made_valid(); - - (Mesh::new(), Mesh::new(), Mesh::new(), bounds) - } -} - -impl<'a: 'b, 'b, V: 'a> Meshable> for V + ), +) -> MeshGen> where V: BaseVol + ReadVol + SizedVol, - /* TODO: Use VolIterator instead of manually iterating - * &'a V: IntoVolIterator<'a> + IntoFullVolIterator<'a>, - * &'a V: BaseVol, */ { - type Pipeline = SpritePipeline; - type Result = (); - type ShadowPipeline = ShadowPipeline; - type Supplement = (&'b mut GreedyMesh<'a>, &'b mut Mesh, bool); - type TranslucentPipeline = SpritePipeline; + assert!(bone_idx <= 15, "Bone index for figures must be in [0, 15]"); + let max_size = greedy.max_size(); + // NOTE: Required because we steal two bits from the normal in the shadow uint + // in order to store the bone index. The two bits are instead taken out + // of the atlas coordinates, which is why we "only" allow 1 << 15 per + // coordinate instead of 1 << 16. + assert!(max_size.width.max(max_size.height) < 1 << 15); - #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 - fn generate_mesh( - self, - (greedy, opaque_mesh, vertical_stripes): Self::Supplement, - ) -> MeshGen, Self> { - let max_size = greedy.max_size(); - // NOTE: Required because we steal two bits from the normal in the shadow uint - // in order to store the bone index. The two bits are instead taken out - // of the atlas coordinates, which is why we "only" allow 1 << 15 per - // coordinate instead of 1 << 16. - assert!(max_size.width.max(max_size.height) < 1 << 16); + let lower_bound = vol.lower_bound(); + let upper_bound = vol.upper_bound(); + assert!( + lower_bound.x <= upper_bound.x + && lower_bound.y <= upper_bound.y + && lower_bound.z <= upper_bound.z + ); + // NOTE: Figure sizes should be no more than 512 along each axis. + let greedy_size = upper_bound - lower_bound + 1; + assert!(greedy_size.x <= 512 && greedy_size.y <= 512 && greedy_size.z <= 512); + // NOTE: Cast to usize is safe because of previous check, since all values fit + // into u16 which is safe to cast to usize. + let greedy_size = greedy_size.as_::(); + let greedy_size_cross = greedy_size; + let draw_delta = lower_bound; - let lower_bound = self.lower_bound(); - let upper_bound = self.upper_bound(); - assert!( - lower_bound.x <= upper_bound.x - && lower_bound.y <= upper_bound.y - && lower_bound.z <= upper_bound.z - ); - // Lower bound coordinates must fit in an i16 (which means upper bound - // coordinates fit as integers in a f23). - assert!( - i16::try_from(lower_bound.x).is_ok() - && i16::try_from(lower_bound.y).is_ok() - && i16::try_from(lower_bound.z).is_ok(), - "Sprite offsets should fit in i16", - ); - let greedy_size = upper_bound - lower_bound + 1; - // TODO: Should this be 16, 16, 64? - assert!( - greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 64, - "Sprite size out of bounds: {:?} ≤ (31, 31, 63)", - greedy_size - 1 - ); + let get_light = |vol: &mut V, pos: Vec3| { + if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { + 1.0 + } else { + 0.0 + } + }; + let get_glow = |_vol: &mut V, _pos: Vec3| 0.0; + let get_opacity = |vol: &mut V, pos: Vec3| vol.get(pos).map_or(true, |vox| vox.is_empty()); + let should_draw = |vol: &mut V, pos: Vec3, delta: Vec3, uv| { + should_draw_greedy(pos, delta, uv, |vox| { + vol.get(vox).map(|vox| *vox).unwrap_or(Cell::empty()) + }) + }; + let create_opaque = |atlas_pos, pos, norm| { + TerrainVertex::new_figure(atlas_pos, (pos + offs) * scale, norm, bone_idx) + }; - let (flat, flat_get) = { - let (w, h, d) = (greedy_size + 2).into_tuple(); - let flat = { - let vol = self; + greedy.push(GreedyConfig { + data: vol, + draw_delta, + greedy_size, + greedy_size_cross, + get_light, + get_glow, + get_opacity, + should_draw, + push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &()| { + opaque_mesh.push_quad(greedy::create_quad( + atlas_origin, + dim, + origin, + draw_dim, + norm, + meta, + |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm), + )); + }, + make_face_texel: |vol: &mut V, pos, light, _| { + let cell = vol.get(pos).ok(); + let (glowy, shiny) = cell + .map(|c| (c.is_glowy(), c.is_shiny())) + .unwrap_or_default(); + let col = cell.and_then(|vox| vox.get_color()).unwrap_or(Rgb::zero()); + TerrainVertex::make_col_light_figure(light, glowy, shiny, col) + }, + }); + let bounds = math::Aabb { + // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16. + min: math::Vec3::from((lower_bound.as_::() + offs) * scale), + max: math::Vec3::from((upper_bound.as_::() + offs) * scale), + } + .made_valid(); - let mut flat = vec![Cell::empty(); (w * h * d) as usize]; - let mut i = 0; - for x in -1..greedy_size.x + 1 { - for y in -1..greedy_size.y + 1 { - for z in -1..greedy_size.z + 1 { - let wpos = lower_bound + Vec3::new(x, y, z); - let block = vol.get(wpos).map(|b| *b).unwrap_or(Cell::empty()); - flat[i] = block; - i += 1; - } + (Mesh::new(), Mesh::new(), Mesh::new(), bounds) +} + +#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 +pub fn generate_mesh_base_vol_sprite<'a: 'b, 'b, V: 'a>( + vol: V, + (greedy, opaque_mesh, vertical_stripes): ( + &'b mut GreedyMesh<'a>, + &'b mut Mesh, + bool, + ), +) -> MeshGen +where + V: BaseVol + ReadVol + SizedVol, +{ + let max_size = greedy.max_size(); + // NOTE: Required because we steal two bits from the normal in the shadow uint + // in order to store the bone index. The two bits are instead taken out + // of the atlas coordinates, which is why we "only" allow 1 << 15 per + // coordinate instead of 1 << 16. + assert!(max_size.width.max(max_size.height) < 1 << 16); + + let lower_bound = vol.lower_bound(); + let upper_bound = vol.upper_bound(); + assert!( + lower_bound.x <= upper_bound.x + && lower_bound.y <= upper_bound.y + && lower_bound.z <= upper_bound.z + ); + // Lower bound coordinates must fit in an i16 (which means upper bound + // coordinates fit as integers in a f23). + assert!( + i16::try_from(lower_bound.x).is_ok() + && i16::try_from(lower_bound.y).is_ok() + && i16::try_from(lower_bound.z).is_ok(), + "Sprite offsets should fit in i16", + ); + let greedy_size = upper_bound - lower_bound + 1; + // TODO: Should this be 16, 16, 64? + assert!( + greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 64, + "Sprite size out of bounds: {:?} ≤ (31, 31, 63)", + greedy_size - 1 + ); + + let (flat, flat_get) = { + let (w, h, d) = (greedy_size + 2).into_tuple(); + let flat = { + let mut flat = vec![Cell::empty(); (w * h * d) as usize]; + let mut i = 0; + for x in -1..greedy_size.x + 1 { + for y in -1..greedy_size.y + 1 { + for z in -1..greedy_size.z + 1 { + let wpos = lower_bound + Vec3::new(x, y, z); + let block = vol.get(wpos).map(|b| *b).unwrap_or(Cell::empty()); + flat[i] = block; + i += 1; } } - flat - }; - - let flat_get = move |flat: &Vec, Vec3 { x, y, z }| match flat - .get((x * h * d + y * d + z) as usize) - .copied() - { - Some(b) => b, - None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h), - }; - - (flat, flat_get) - }; - - // NOTE: Cast to usize is safe because of previous check, since all values fit - // into u16 which is safe to cast to usize. - let greedy_size = greedy_size.as_::(); - - let greedy_size_cross = greedy_size; - let draw_delta = Vec3::new(1, 1, 1); - - let get_light = move |flat: &mut _, pos: Vec3| { - if flat_get(flat, pos).is_empty() { - 1.0 - } else { - 0.0 } - }; - let get_glow = |_flat: &mut _, _pos: Vec3| 0.0; - let get_color = move |flat: &mut _, pos: Vec3| { - flat_get(flat, pos).get_color().unwrap_or(Rgb::zero()) - }; - let get_opacity = move |flat: &mut _, pos: Vec3| flat_get(flat, pos).is_empty(); - let should_draw = move |flat: &mut _, pos: Vec3, delta: Vec3, uv| { - should_draw_greedy_ao(vertical_stripes, pos, delta, uv, |vox| flat_get(flat, vox)) - }; - // NOTE: Fits in i16 (much lower actually) so f32 is no problem (and the final - // position, pos + mesh_delta, is guaranteed to fit in an f32). - let mesh_delta = lower_bound.as_::(); - let create_opaque = |atlas_pos, pos: Vec3, norm, _meta| { - SpriteVertex::new(atlas_pos, pos + mesh_delta, norm) + flat }; - greedy.push(GreedyConfig { - data: flat, - draw_delta, - greedy_size, - greedy_size_cross, - get_light, - get_glow, - get_opacity, - should_draw, - push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &bool| { - opaque_mesh.push_quad(greedy::create_quad( - atlas_origin, - dim, - origin, - draw_dim, - norm, - meta, - |atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta), - )); - }, - make_face_texel: move |flat: &mut _, pos, light, glow| { - TerrainVertex::make_col_light(light, glow, get_color(flat, pos)) - }, - }); + let flat_get = move |flat: &Vec, Vec3 { x, y, z }| match flat + .get((x * h * d + y * d + z) as usize) + .copied() + { + Some(b) => b, + None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h), + }; - (Mesh::new(), Mesh::new(), Mesh::new(), ()) - } + (flat, flat_get) + }; + + // NOTE: Cast to usize is safe because of previous check, since all values fit + // into u16 which is safe to cast to usize. + let greedy_size = greedy_size.as_::(); + + let greedy_size_cross = greedy_size; + let draw_delta = Vec3::new(1, 1, 1); + + let get_light = move |flat: &mut _, pos: Vec3| { + if flat_get(flat, pos).is_empty() { + 1.0 + } else { + 0.0 + } + }; + let get_glow = |_flat: &mut _, _pos: Vec3| 0.0; + let get_color = + move |flat: &mut _, pos: Vec3| flat_get(flat, pos).get_color().unwrap_or(Rgb::zero()); + let get_opacity = move |flat: &mut _, pos: Vec3| flat_get(flat, pos).is_empty(); + let should_draw = move |flat: &mut _, pos: Vec3, delta: Vec3, uv| { + should_draw_greedy_ao(vertical_stripes, pos, delta, uv, |vox| flat_get(flat, vox)) + }; + // NOTE: Fits in i16 (much lower actually) so f32 is no problem (and the final + // position, pos + mesh_delta, is guaranteed to fit in an f32). + let mesh_delta = lower_bound.as_::(); + let create_opaque = |atlas_pos, pos: Vec3, norm, _meta| { + SpriteVertex::new(atlas_pos, pos + mesh_delta, norm) + }; + + greedy.push(GreedyConfig { + data: flat, + draw_delta, + greedy_size, + greedy_size_cross, + get_light, + get_glow, + get_opacity, + should_draw, + push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &bool| { + opaque_mesh.push_quad(greedy::create_quad( + atlas_origin, + dim, + origin, + draw_dim, + norm, + meta, + |atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta), + )); + }, + make_face_texel: move |flat: &mut _, pos, light, glow| { + TerrainVertex::make_col_light(light, glow, get_color(flat, pos)) + }, + }); + + (Mesh::new(), Mesh::new(), Mesh::new(), ()) } -impl<'a: 'b, 'b, V: 'a> Meshable> for V +#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 +pub fn generate_mesh_base_vol_particle<'a: 'b, 'b, V: 'a>( + vol: V, + greedy: &'b mut GreedyMesh<'a>, +) -> MeshGen where V: BaseVol + ReadVol + SizedVol, - /* TODO: Use VolIterator instead of manually iterating - * &'a V: IntoVolIterator<'a> + IntoFullVolIterator<'a>, - * &'a V: BaseVol, */ { - type Pipeline = ParticlePipeline; - type Result = (); - type ShadowPipeline = ShadowPipeline; - type Supplement = &'b mut GreedyMesh<'a>; - type TranslucentPipeline = ParticlePipeline; + let max_size = greedy.max_size(); + // NOTE: Required because we steal two bits from the normal in the shadow uint + // in order to store the bone index. The two bits are instead taken out + // of the atlas coordinates, which is why we "only" allow 1 << 15 per + // coordinate instead of 1 << 16. + assert!(max_size.width.max(max_size.height) < 1 << 16); - #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 - fn generate_mesh( - self, - greedy: Self::Supplement, - ) -> MeshGen, Self> { - let max_size = greedy.max_size(); - // NOTE: Required because we steal two bits from the normal in the shadow uint - // in order to store the bone index. The two bits are instead taken out - // of the atlas coordinates, which is why we "only" allow 1 << 15 per - // coordinate instead of 1 << 16. - assert!(max_size.width.max(max_size.height) < 1 << 16); + let lower_bound = vol.lower_bound(); + let upper_bound = vol.upper_bound(); + assert!( + lower_bound.x <= upper_bound.x + && lower_bound.y <= upper_bound.y + && lower_bound.z <= upper_bound.z + ); + let greedy_size = upper_bound - lower_bound + 1; + assert!( + greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64, + "Particle size out of bounds: {:?} ≤ (15, 15, 63)", + greedy_size - 1 + ); + // NOTE: Cast to usize is safe because of previous check, since all values fit + // into u16 which is safe to cast to usize. + let greedy_size = greedy_size.as_::(); - let lower_bound = self.lower_bound(); - let upper_bound = self.upper_bound(); - assert!( - lower_bound.x <= upper_bound.x - && lower_bound.y <= upper_bound.y - && lower_bound.z <= upper_bound.z - ); - let greedy_size = upper_bound - lower_bound + 1; - assert!( - greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64, - "Particle size out of bounds: {:?} ≤ (15, 15, 63)", - greedy_size - 1 - ); - // NOTE: Cast to usize is safe because of previous check, since all values fit - // into u16 which is safe to cast to usize. - let greedy_size = greedy_size.as_::(); + let greedy_size_cross = greedy_size; + let draw_delta = lower_bound; - let greedy_size_cross = greedy_size; - let draw_delta = lower_bound; + let get_light = |vol: &mut V, pos: Vec3| { + if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { + 1.0 + } else { + 0.0 + } + }; + let get_glow = |_vol: &mut V, _pos: Vec3| 0.0; + let get_color = |vol: &mut V, pos: Vec3| { + vol.get(pos) + .ok() + .and_then(|vox| vox.get_color()) + .unwrap_or(Rgb::zero()) + }; + let get_opacity = |vol: &mut V, pos: Vec3| vol.get(pos).map_or(true, |vox| vox.is_empty()); + let should_draw = |vol: &mut V, pos: Vec3, delta: Vec3, uv| { + should_draw_greedy(pos, delta, uv, |vox| { + vol.get(vox).map(|vox| *vox).unwrap_or(Cell::empty()) + }) + }; + let create_opaque = |_atlas_pos, pos: Vec3, norm| ParticleVertex::new(pos, norm); - let get_light = |vol: &mut V, pos: Vec3| { - if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { - 1.0 - } else { - 0.0 - } - }; - let get_glow = |_vol: &mut V, _pos: Vec3| 0.0; - let get_color = |vol: &mut V, pos: Vec3| { - vol.get(pos) - .ok() - .and_then(|vox| vox.get_color()) - .unwrap_or(Rgb::zero()) - }; - let get_opacity = - |vol: &mut V, pos: Vec3| vol.get(pos).map_or(true, |vox| vox.is_empty()); - let should_draw = |vol: &mut V, pos: Vec3, delta: Vec3, uv| { - should_draw_greedy(pos, delta, uv, |vox| { - vol.get(vox).map(|vox| *vox).unwrap_or(Cell::empty()) - }) - }; - let create_opaque = |_atlas_pos, pos: Vec3, norm| ParticleVertex::new(pos, norm); + let mut opaque_mesh = Mesh::new(); + greedy.push(GreedyConfig { + data: vol, + draw_delta, + greedy_size, + greedy_size_cross, + get_light, + get_glow, + get_opacity, + should_draw, + push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &()| { + opaque_mesh.push_quad(greedy::create_quad( + atlas_origin, + dim, + origin, + draw_dim, + norm, + meta, + |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm), + )); + }, + make_face_texel: move |vol: &mut V, pos, light, glow| { + TerrainVertex::make_col_light(light, glow, get_color(vol, pos)) + }, + }); - let mut opaque_mesh = Mesh::new(); - greedy.push(GreedyConfig { - data: self, - draw_delta, - greedy_size, - greedy_size_cross, - get_light, - get_glow, - get_opacity, - should_draw, - push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &()| { - opaque_mesh.push_quad(greedy::create_quad( - atlas_origin, - dim, - origin, - draw_dim, - norm, - meta, - |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm), - )); - }, - make_face_texel: move |vol: &mut V, pos, light, glow| { - TerrainVertex::make_col_light(light, glow, get_color(vol, pos)) - }, - }); - - (opaque_mesh, Mesh::new(), Mesh::new(), ()) - } + (opaque_mesh, Mesh::new(), Mesh::new(), ()) } fn should_draw_greedy( diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index 682e798dd6..b90f5e440b 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -3,9 +3,9 @@ use crate::{ mesh::{ greedy::{self, GreedyConfig, GreedyMesh}, - MeshGen, Meshable, + MeshGen, }, - render::{self, ColLightInfo, FluidPipeline, Mesh, ShadowPipeline, TerrainPipeline}, + render::{ColLightInfo, FluidVertex, Mesh, TerrainVertex}, scene::terrain::BlocksOfInterest, }; use common::{ @@ -19,9 +19,6 @@ use std::{collections::VecDeque, fmt::Debug, sync::Arc}; use tracing::error; use vek::*; -type TerrainVertex = ::Vertex; -type FluidVertex = ::Vertex; - #[derive(Clone, Copy, PartialEq)] enum FaceKind { /// Opaque face that is facing something non-opaque; either @@ -227,243 +224,234 @@ fn calc_light + ReadVol + Debug>( } } -impl<'a, V: RectRasterableVol + ReadVol + Debug + 'static> - Meshable for &'a VolGrid2d -{ - type Pipeline = TerrainPipeline; - #[allow(clippy::type_complexity)] - type Result = ( +#[allow(clippy::collapsible_if)] +#[allow(clippy::many_single_char_names)] +#[allow(clippy::type_complexity)] +#[allow(clippy::needless_range_loop)] // TODO: Pending review in #587 +#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 +pub fn generate_mesh<'a, V: RectRasterableVol + ReadVol + Debug + 'static>( + vol: &'a VolGrid2d, + (range, max_texture_size, _boi): (Aabb, Vec2, &'a BlocksOfInterest), +) -> MeshGen< + TerrainVertex, + FluidVertex, + TerrainVertex, + ( Aabb, ColLightInfo, Arc) -> f32 + Send + Sync>, Arc) -> f32 + Send + Sync>, + ), +> { + span!( + _guard, + "generate_mesh", + "<&VolGrid2d as Meshable<_, _>>::generate_mesh" ); - type ShadowPipeline = ShadowPipeline; - type Supplement = (Aabb, Vec2, &'a BlocksOfInterest); - type TranslucentPipeline = FluidPipeline; - #[allow(clippy::collapsible_if)] - #[allow(clippy::many_single_char_names)] - #[allow(clippy::needless_range_loop)] // TODO: Pending review in #587 - #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 - fn generate_mesh( - self, - (range, max_texture_size, _boi): Self::Supplement, - ) -> MeshGen { - span!( - _guard, - "generate_mesh", - "<&VolGrid2d as Meshable<_, _>>::generate_mesh" - ); + // Find blocks that should glow + // TODO: Search neighbouring chunks too! + // let glow_blocks = boi.lights + // .iter() + // .map(|(pos, glow)| (*pos + range.min.xy(), *glow)); + /* DefaultVolIterator::new(vol, range.min - MAX_LIGHT_DIST, range.max + MAX_LIGHT_DIST) + .filter_map(|(pos, block)| block.get_glow().map(|glow| (pos, glow))); */ - // Find blocks that should glow - // TODO: Search neighbouring chunks too! - // let glow_blocks = boi.lights - // .iter() - // .map(|(pos, glow)| (*pos + range.min.xy(), *glow)); - /* DefaultVolIterator::new(self, range.min - MAX_LIGHT_DIST, range.max + MAX_LIGHT_DIST) - .filter_map(|(pos, block)| block.get_glow().map(|glow| (pos, glow))); */ + let mut glow_blocks = Vec::new(); - let mut glow_blocks = Vec::new(); - - // TODO: This expensive, use BlocksOfInterest instead - let mut volume = self.cached(); - for x in -MAX_LIGHT_DIST..range.size().w + MAX_LIGHT_DIST { - for y in -MAX_LIGHT_DIST..range.size().h + MAX_LIGHT_DIST { - for z in -1..range.size().d + 1 { - let wpos = range.min + Vec3::new(x, y, z); - volume - .get(wpos) - .ok() - .and_then(|b| b.get_glow()) - .map(|glow| glow_blocks.push((wpos, glow))); - } + // TODO: This expensive, use BlocksOfInterest instead + let mut volume = vol.cached(); + for x in -MAX_LIGHT_DIST..range.size().w + MAX_LIGHT_DIST { + for y in -MAX_LIGHT_DIST..range.size().h + MAX_LIGHT_DIST { + for z in -1..range.size().d + 1 { + let wpos = range.min + Vec3::new(x, y, z); + volume + .get(wpos) + .ok() + .and_then(|b| b.get_glow()) + .map(|glow| glow_blocks.push((wpos, glow))); } } + } - // Calculate chunk lighting (sunlight defaults to 1.0, glow to 0.0) - let light = calc_light(true, SUNLIGHT, range, self, core::iter::empty()); - let glow = calc_light(false, 0, range, self, glow_blocks.into_iter()); + // Calculate chunk lighting (sunlight defaults to 1.0, glow to 0.0) + let light = calc_light(true, SUNLIGHT, range, vol, core::iter::empty()); + let glow = calc_light(false, 0, range, vol, glow_blocks.into_iter()); - let mut opaque_limits = None::; - let mut fluid_limits = None::; - let mut air_limits = None::; - let flat_get = { - span!(_guard, "copy to flat array"); - let (w, h, d) = range.size().into_tuple(); - // z can range from -1..range.size().d + 1 - let d = d + 2; - let flat = { - let mut volume = self.cached(); - - const AIR: Block = Block::air(common::terrain::sprite::SpriteKind::Empty); - - // TODO: Once we can manage it sensibly, consider using something like - // Option instead of just assuming air. - let mut flat = vec![AIR; (w * h * d) as usize]; - let mut i = 0; - for x in 0..range.size().w { - for y in 0..range.size().h { - for z in -1..range.size().d + 1 { - let wpos = range.min + Vec3::new(x, y, z); - let block = volume - .get(wpos) - .map(|b| *b) - // TODO: Replace with None or some other more reasonable value, - // since it's not clear this will work properly with liquid. - .unwrap_or(AIR); - if block.is_opaque() { - opaque_limits = opaque_limits - .map(|l| l.including(z)) - .or_else(|| Some(Limits::from_value(z))); - } else if block.is_liquid() { - fluid_limits = fluid_limits - .map(|l| l.including(z)) - .or_else(|| Some(Limits::from_value(z))); - } else { - // Assume air - air_limits = air_limits - .map(|l| l.including(z)) - .or_else(|| Some(Limits::from_value(z))); - }; - flat[i] = block; - i += 1; - } + let mut opaque_limits = None::; + let mut fluid_limits = None::; + let mut air_limits = None::; + let flat_get = { + span!(_guard, "copy to flat array"); + let (w, h, d) = range.size().into_tuple(); + // z can range from -1..range.size().d + 1 + let d = d + 2; + let flat = { + let mut volume = vol.cached(); + const AIR: Block = Block::air(common::terrain::sprite::SpriteKind::Empty); + // TODO: Once we can manage it sensibly, consider using something like + // Option instead of just assuming air. + let mut flat = vec![AIR; (w * h * d) as usize]; + let mut i = 0; + for x in 0..range.size().w { + for y in 0..range.size().h { + for z in -1..range.size().d + 1 { + let wpos = range.min + Vec3::new(x, y, z); + let block = volume + .get(wpos) + .map(|b| *b) + // TODO: Replace with None or some other more reasonable value, + // since it's not clear this will work properly with liquid. + .unwrap_or(AIR); + if block.is_opaque() { + opaque_limits = opaque_limits + .map(|l| l.including(z)) + .or_else(|| Some(Limits::from_value(z))); + } else if block.is_liquid() { + fluid_limits = fluid_limits + .map(|l| l.including(z)) + .or_else(|| Some(Limits::from_value(z))); + } else { + // Assume air + air_limits = air_limits + .map(|l| l.including(z)) + .or_else(|| Some(Limits::from_value(z))); + }; + flat[i] = block; + i += 1; } } - flat - }; - - move |Vec3 { x, y, z }| { - // z can range from -1..range.size().d + 1 - let z = z + 1; - match flat.get((x * h * d + y * d + z) as usize).copied() { - Some(b) => b, - None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h), - } } + flat }; - // Constrain iterated area - let (z_start, z_end) = match (air_limits, fluid_limits, opaque_limits) { - (Some(air), Some(fluid), Some(opaque)) => air.three_way_intersection(fluid, opaque), - (Some(air), Some(fluid), None) => air.intersection(fluid), - (Some(air), None, Some(opaque)) => air.intersection(opaque), - (None, Some(fluid), Some(opaque)) => fluid.intersection(opaque), - // No interfaces (Note: if there are multiple fluid types this could change) - (Some(_), None, None) | (None, Some(_), None) | (None, None, Some(_)) => None, - (None, None, None) => { - error!("Impossible unless given an input AABB that has a height of zero"); - None - }, + move |Vec3 { x, y, z }| { + // z can range from -1..range.size().d + 1 + let z = z + 1; + match flat.get((x * h * d + y * d + z) as usize).copied() { + Some(b) => b, + None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h), + } } - .map_or((0, 0), |limits| { - let (start, end) = limits.into_tuple(); - let start = start.max(0); - let end = end.min(range.size().d - 1).max(start); - (start, end) - }); + }; - let max_size = - guillotiere::Size::new(i32::from(max_texture_size.x), i32::from(max_texture_size.y)); - assert!(z_end >= z_start); - let greedy_size = Vec3::new(range.size().w - 2, range.size().h - 2, z_end - z_start + 1); - // NOTE: Terrain sizes are limited to 32 x 32 x 16384 (to fit in 24 bits: 5 + 5 - // + 14). FIXME: Make this function fallible, since the terrain - // information might be dynamically generated which would make this hard - // to enforce. - assert!(greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 16384); - // NOTE: Cast is safe by prior assertion on greedy_size; it fits into a u16, - // which always fits into a f32. - let max_bounds: Vec3 = greedy_size.as_::(); - // NOTE: Cast is safe by prior assertion on greedy_size; it fits into a u16, - // which always fits into a usize. - let greedy_size = greedy_size.as_::(); - let greedy_size_cross = Vec3::new(greedy_size.x - 1, greedy_size.y - 1, greedy_size.z); - let draw_delta = Vec3::new(1, 1, z_start); - - let get_light = |_: &mut (), pos: Vec3| { - if flat_get(pos).is_opaque() { - 0.0 - } else { - light(pos + range.min) - } - }; - let get_glow = |_: &mut (), pos: Vec3| glow(pos + range.min); - let get_color = - |_: &mut (), pos: Vec3| flat_get(pos).get_color().unwrap_or(Rgb::zero()); - let get_opacity = |_: &mut (), pos: Vec3| !flat_get(pos).is_opaque(); - let flat_get = |pos| flat_get(pos); - let should_draw = |_: &mut (), pos: Vec3, delta: Vec3, _uv| { - should_draw_greedy(pos, delta, flat_get) - }; - // NOTE: Conversion to f32 is fine since this i32 is actually in bounds for u16. - let mesh_delta = Vec3::new(0.0, 0.0, (z_start + range.min.z) as f32); - let create_opaque = |atlas_pos, pos, norm, meta| { - TerrainVertex::new(atlas_pos, pos + mesh_delta, norm, meta) - }; - let create_transparent = |_atlas_pos, pos, norm| FluidVertex::new(pos + mesh_delta, norm); - - let mut greedy = GreedyMesh::new(max_size); - let mut opaque_mesh = Mesh::new(); - let mut fluid_mesh = Mesh::new(); - greedy.push(GreedyConfig { - data: (), - draw_delta, - greedy_size, - greedy_size_cross, - get_light, - get_glow, - get_opacity, - should_draw, - push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &FaceKind| match meta { - FaceKind::Opaque(meta) => { - opaque_mesh.push_quad(greedy::create_quad( - atlas_origin, - dim, - origin, - draw_dim, - norm, - meta, - |atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta), - )); - }, - FaceKind::Fluid => { - fluid_mesh.push_quad(greedy::create_quad( - atlas_origin, - dim, - origin, - draw_dim, - norm, - &(), - |atlas_pos, pos, norm, &_meta| create_transparent(atlas_pos, pos, norm), - )); - }, - }, - make_face_texel: |data: &mut (), pos, light, glow| { - TerrainVertex::make_col_light(light, glow, get_color(data, pos)) - }, - }); - - let min_bounds = mesh_delta; - let bounds = Aabb { - min: min_bounds, - max: max_bounds + min_bounds, - }; - let (col_lights, col_lights_size) = greedy.finalize(); - - ( - opaque_mesh, - fluid_mesh, - Mesh::new(), - ( - bounds, - (col_lights, col_lights_size), - Arc::new(light), - Arc::new(glow), - ), - ) + // Constrain iterated area + let (z_start, z_end) = match (air_limits, fluid_limits, opaque_limits) { + (Some(air), Some(fluid), Some(opaque)) => air.three_way_intersection(fluid, opaque), + (Some(air), Some(fluid), None) => air.intersection(fluid), + (Some(air), None, Some(opaque)) => air.intersection(opaque), + (None, Some(fluid), Some(opaque)) => fluid.intersection(opaque), + // No interfaces (Note: if there are multiple fluid types this could change) + (Some(_), None, None) | (None, Some(_), None) | (None, None, Some(_)) => None, + (None, None, None) => { + error!("Impossible unless given an input AABB that has a height of zero"); + None + }, } + .map_or((0, 0), |limits| { + let (start, end) = limits.into_tuple(); + let start = start.max(0); + let end = end.min(range.size().d - 1).max(start); + (start, end) + }); + + let max_size = + guillotiere::Size::new(i32::from(max_texture_size.x), i32::from(max_texture_size.y)); + assert!(z_end >= z_start); + let greedy_size = Vec3::new(range.size().w - 2, range.size().h - 2, z_end - z_start + 1); + // NOTE: Terrain sizes are limited to 32 x 32 x 16384 (to fit in 24 bits: 5 + 5 + // + 14). FIXME: Make this function fallible, since the terrain + // information might be dynamically generated which would make this hard + // to enforce. + assert!(greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 16384); + // NOTE: Cast is safe by prior assertion on greedy_size; it fits into a u16, + // which always fits into a f32. + let max_bounds: Vec3 = greedy_size.as_::(); + // NOTE: Cast is safe by prior assertion on greedy_size; it fits into a u16, + // which always fits into a usize. + let greedy_size = greedy_size.as_::(); + let greedy_size_cross = Vec3::new(greedy_size.x - 1, greedy_size.y - 1, greedy_size.z); + let draw_delta = Vec3::new(1, 1, z_start); + + let get_light = |_: &mut (), pos: Vec3| { + if flat_get(pos).is_opaque() { + 0.0 + } else { + light(pos + range.min) + } + }; + let get_glow = |_: &mut (), pos: Vec3| glow(pos + range.min); + let get_color = |_: &mut (), pos: Vec3| flat_get(pos).get_color().unwrap_or(Rgb::zero()); + let get_opacity = |_: &mut (), pos: Vec3| !flat_get(pos).is_opaque(); + let flat_get = |pos| flat_get(pos); + let should_draw = |_: &mut (), pos: Vec3, delta: Vec3, _uv| { + should_draw_greedy(pos, delta, flat_get) + }; + // NOTE: Conversion to f32 is fine since this i32 is actually in bounds for u16. + let mesh_delta = Vec3::new(0.0, 0.0, (z_start + range.min.z) as f32); + let create_opaque = + |atlas_pos, pos, norm, meta| TerrainVertex::new(atlas_pos, pos + mesh_delta, norm, meta); + let create_transparent = |_atlas_pos, pos, norm| FluidVertex::new(pos + mesh_delta, norm); + + let mut greedy = GreedyMesh::new(max_size); + let mut opaque_mesh = Mesh::new(); + let mut fluid_mesh = Mesh::new(); + greedy.push(GreedyConfig { + data: (), + draw_delta, + greedy_size, + greedy_size_cross, + get_light, + get_glow, + get_opacity, + should_draw, + push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &FaceKind| match meta { + FaceKind::Opaque(meta) => { + opaque_mesh.push_quad(greedy::create_quad( + atlas_origin, + dim, + origin, + draw_dim, + norm, + meta, + |atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta), + )); + }, + FaceKind::Fluid => { + fluid_mesh.push_quad(greedy::create_quad( + atlas_origin, + dim, + origin, + draw_dim, + norm, + &(), + |atlas_pos, pos, norm, &_meta| create_transparent(atlas_pos, pos, norm), + )); + }, + }, + make_face_texel: |data: &mut (), pos, light, glow| { + TerrainVertex::make_col_light(light, glow, get_color(data, pos)) + }, + }); + + let min_bounds = mesh_delta; + let bounds = Aabb { + min: min_bounds, + max: max_bounds + min_bounds, + }; + let (col_lights, col_lights_size) = greedy.finalize(); + + ( + opaque_mesh, + fluid_mesh, + Mesh::new(), + ( + bounds, + (col_lights, col_lights_size), + Arc::new(light), + Arc::new(glow), + ), + ) } /// NOTE: Make sure to reflect any changes to how meshing is performanced in diff --git a/voxygen/src/render/bound.rs b/voxygen/src/render/bound.rs new file mode 100644 index 0000000000..e601c0adb1 --- /dev/null +++ b/voxygen/src/render/bound.rs @@ -0,0 +1,14 @@ +pub struct Bound { + pub(super) bind_group: wgpu::BindGroup, + pub(super) with: T, +} + +impl std::ops::Deref for Bound { + type Target = T; + + fn deref(&self) -> &Self::Target { &self.with } +} + +impl std::ops::DerefMut for Bound { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.with } +} diff --git a/voxygen/src/render/buffer.rs b/voxygen/src/render/buffer.rs new file mode 100644 index 0000000000..f4d80a51cc --- /dev/null +++ b/voxygen/src/render/buffer.rs @@ -0,0 +1,63 @@ +use bytemuck::Pod; +use wgpu::util::DeviceExt; + +pub struct Buffer { + pub(super) buf: wgpu::Buffer, + // Size in number of elements + // TODO: determine if this is a good name + len: usize, + phantom_data: std::marker::PhantomData, +} + +impl Buffer { + pub fn new(device: &wgpu::Device, usage: wgpu::BufferUsage, data: &[T]) -> Self { + let contents = bytemuck::cast_slice(data); + + Self { + buf: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents, + usage, + }), + len: data.len(), + phantom_data: std::marker::PhantomData, + } + } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { self.len } +} + +pub struct DynamicBuffer(Buffer); + +impl DynamicBuffer { + pub fn new(device: &wgpu::Device, len: usize, usage: wgpu::BufferUsage) -> Self { + let buffer = Buffer { + buf: device.create_buffer(&wgpu::BufferDescriptor { + label: None, + mapped_at_creation: false, + size: len as u64 * std::mem::size_of::() as u64, + usage: usage | wgpu::BufferUsage::COPY_DST, + }), + len, + phantom_data: std::marker::PhantomData, + }; + Self(buffer) + } + + pub fn update(&self, queue: &wgpu::Queue, vals: &[T], offset: usize) { + if !vals.is_empty() { + queue.write_buffer( + &self.buf, + offset as u64 * std::mem::size_of::() as u64, + bytemuck::cast_slice(vals), + ) + } + } +} + +impl std::ops::Deref for DynamicBuffer { + type Target = Buffer; + + fn deref(&self) -> &Self::Target { &self.0 } +} diff --git a/voxygen/src/render/consts.rs b/voxygen/src/render/consts.rs index 0eded13428..3259c7d1aa 100644 --- a/voxygen/src/render/consts.rs +++ b/voxygen/src/render/consts.rs @@ -1,36 +1,26 @@ -use super::{gfx_backend, RenderError}; -use gfx::{self, traits::FactoryExt}; +use super::buffer::DynamicBuffer; +use bytemuck::Pod; /// A handle to a series of constants sitting on the GPU. This is used to hold /// information used in the rendering process that does not change throughout a /// single render pass. -#[derive(Clone)] -pub struct Consts { - pub buf: gfx::handle::Buffer, +pub struct Consts { + buf: DynamicBuffer, } -impl Consts { +impl Consts { /// Create a new `Const`. - pub fn new(factory: &mut gfx_backend::Factory, len: usize) -> Self { + pub fn new(device: &wgpu::Device, len: usize) -> Self { Self { - buf: factory.create_constant_buffer(len), + // TODO: examine if all our consts need to be updateable + buf: DynamicBuffer::new(device, len, wgpu::BufferUsage::UNIFORM), } } /// Update the GPU-side value represented by this constant handle. - - pub fn update( - &mut self, - encoder: &mut gfx::Encoder, - vals: &[T], - offset: usize, - ) -> Result<(), RenderError> { - if vals.is_empty() { - Ok(()) - } else { - encoder - .update_buffer(&self.buf, vals, offset) - .map_err(RenderError::UpdateError) - } + pub fn update(&mut self, queue: &wgpu::Queue, vals: &[T], offset: usize) { + self.buf.update(queue, vals, offset) } + + pub fn buf(&self) -> &wgpu::Buffer { &self.buf.buf } } diff --git a/voxygen/src/render/error.rs b/voxygen/src/render/error.rs index 8c9352857d..d3736216c4 100644 --- a/voxygen/src/render/error.rs +++ b/voxygen/src/render/error.rs @@ -1,74 +1,54 @@ /// Used to represent one of many possible errors that may be omitted by the /// rendering subsystem. -#[derive(Debug)] pub enum RenderError { - PipelineError(gfx::PipelineStateError), - UpdateError(gfx::UpdateError), - TexUpdateError(gfx::UpdateError<[u16; 3]>), - CombinedError(gfx::CombinedError), - BufferCreationError(gfx::buffer::CreationError), - IncludeError(glsl_include::Error), - MappingError(gfx::mapping::Error), - CopyError(gfx::CopyError<[u16; 3], usize>), + RequestDeviceError(wgpu::RequestDeviceError), + MappingError(wgpu::BufferAsyncError), + SwapChainError(wgpu::SwapChainError), CustomError(String), + CouldNotFindAdapter, + ErrorInitializingCompiler, + ShaderError(String, shaderc::Error), } -impl From> for RenderError { - fn from(err: gfx::PipelineStateError) -> Self { Self::PipelineError(err) } -} - -impl From> for RenderError { - fn from(err: gfx::PipelineStateError<&str>) -> Self { - match err { - gfx::PipelineStateError::DescriptorInit(err) => { - gfx::PipelineStateError::DescriptorInit(err) +use std::fmt; +impl fmt::Debug for RenderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::RequestDeviceError(err) => { + f.debug_tuple("RequestDeviceError").field(err).finish() }, - err => err, + Self::MappingError(err) => f.debug_tuple("MappingError").field(err).finish(), + Self::SwapChainError(err) => f + .debug_tuple("SwapChainError") + // Use Display formatting for this error since they have nice descriptions + .field(&format!("{}", err)) + .finish(), + Self::CustomError(err) => f.debug_tuple("CustomError").field(err).finish(), + Self::CouldNotFindAdapter => f.debug_tuple("CouldNotFindAdapter").finish(), + Self::ErrorInitializingCompiler => f.debug_tuple("ErrorInitializingCompiler").finish(), + Self::ShaderError(shader_name, err) => write!( + f, + "\"{}\" shader failed to compile due to the following error: {}", + shader_name, err + ), } - .into() } } -impl From for RenderError { - fn from(err: gfx::shade::ProgramError) -> Self { - gfx::PipelineStateError::::Program(err).into() + +impl From for RenderError { + fn from(err: wgpu::RequestDeviceError) -> Self { Self::RequestDeviceError(err) } +} + +impl From for RenderError { + fn from(err: wgpu::BufferAsyncError) -> Self { Self::MappingError(err) } +} + +impl From for RenderError { + fn from(err: wgpu::SwapChainError) -> Self { Self::SwapChainError(err) } +} + +impl From<(&str, shaderc::Error)> for RenderError { + fn from((shader_name, err): (&str, shaderc::Error)) -> Self { + Self::ShaderError(shader_name.into(), err) } } -impl From> for RenderError { - fn from(err: gfx::UpdateError) -> Self { Self::UpdateError(err) } -} - -impl From> for RenderError { - fn from(err: gfx::UpdateError<[u16; 3]>) -> Self { Self::TexUpdateError(err) } -} - -impl From for RenderError { - fn from(err: gfx::CombinedError) -> Self { Self::CombinedError(err) } -} - -impl From for RenderError { - fn from(err: gfx::TargetViewError) -> Self { Self::CombinedError(err.into()) } -} - -impl From for RenderError { - fn from(err: gfx::ResourceViewError) -> Self { Self::CombinedError(err.into()) } -} - -impl From for RenderError { - fn from(err: gfx::texture::CreationError) -> Self { Self::CombinedError(err.into()) } -} - -impl From for RenderError { - fn from(err: gfx::buffer::CreationError) -> Self { Self::BufferCreationError(err) } -} - -impl From for RenderError { - fn from(err: glsl_include::Error) -> Self { Self::IncludeError(err) } -} - -impl From for RenderError { - fn from(err: gfx::mapping::Error) -> Self { Self::MappingError(err) } -} - -impl From> for RenderError { - fn from(err: gfx::CopyError<[u16; 3], usize>) -> Self { Self::CopyError(err) } -} diff --git a/voxygen/src/render/instances.rs b/voxygen/src/render/instances.rs index c53d5ee2c2..0638dddbd6 100644 --- a/voxygen/src/render/instances.rs +++ b/voxygen/src/render/instances.rs @@ -1,34 +1,26 @@ -use super::{gfx_backend, RenderError}; -use gfx::{ - self, - buffer::Role, - memory::{Bind, Usage}, - Factory, -}; +use super::buffer::DynamicBuffer; +use bytemuck::Pod; /// Represents a mesh that has been sent to the GPU. -pub struct Instances { - pub ibuf: gfx::handle::Buffer, +pub struct Instances { + buf: DynamicBuffer, } -impl Instances { - pub fn new(factory: &mut gfx_backend::Factory, len: usize) -> Result { - Ok(Self { - ibuf: factory - .create_buffer(len, Role::Vertex, Usage::Dynamic, Bind::empty()) - .map_err(RenderError::BufferCreationError)?, - }) +impl Instances { + pub fn new(device: &wgpu::Device, len: usize) -> Self { + Self { + // TODO: examine if we have Instances that are not updated (e.g. sprites) and if there + // would be any gains from separating those out + buf: DynamicBuffer::new(device, len, wgpu::BufferUsage::VERTEX), + } } - pub fn count(&self) -> usize { self.ibuf.len() } + // TODO: count vs len naming scheme?? + pub fn count(&self) -> usize { self.buf.len() } - pub fn update( - &mut self, - encoder: &mut gfx::Encoder, - instances: &[T], - ) -> Result<(), RenderError> { - encoder - .update_buffer(&self.ibuf, instances, 0) - .map_err(RenderError::UpdateError) + pub fn update(&mut self, queue: &wgpu::Queue, vals: &[T], offset: usize) { + self.buf.update(queue, vals, offset) } + + pub fn buf(&self) -> &wgpu::Buffer { &self.buf.buf } } diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs index a8c6b6445e..b0a2401884 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -1,15 +1,12 @@ -use super::Pipeline; +use super::Vertex; use core::{iter::FromIterator, ops::Range}; /// A `Vec`-based mesh structure used to store mesh data on the CPU. -pub struct Mesh { - verts: Vec, +pub struct Mesh { + verts: Vec, } -impl Clone for Mesh

-where - P::Vertex: Clone, -{ +impl Clone for Mesh { fn clone(&self) -> Self { Self { verts: self.verts.clone(), @@ -17,7 +14,7 @@ where } } -impl Mesh

{ +impl Mesh { /// Create a new `Mesh`. #[allow(clippy::new_without_default)] // TODO: Pending review in #587 pub fn new() -> Self { Self { verts: Vec::new() } } @@ -26,83 +23,103 @@ impl Mesh

{ pub fn clear(&mut self) { self.verts.clear(); } /// Get a slice referencing the vertices of this mesh. - pub fn vertices(&self) -> &[P::Vertex] { &self.verts } + pub fn vertices(&self) -> &[V] { &self.verts } /// Get a mutable slice referencing the vertices of this mesh. - pub fn vertices_mut(&mut self) -> &mut [P::Vertex] { &mut self.verts } + pub fn vertices_mut(&mut self) -> &mut [V] { &mut self.verts } + + /// Get a mutable vec referencing the vertices of this mesh. + pub fn vertices_mut_vec(&mut self) -> &mut Vec { &mut self.verts } /// Push a new vertex onto the end of this mesh. - pub fn push(&mut self, vert: P::Vertex) { self.verts.push(vert); } + pub fn push(&mut self, vert: V) { self.verts.push(vert); } /// Push a new polygon onto the end of this mesh. - pub fn push_tri(&mut self, tri: Tri

) { + pub fn push_tri(&mut self, tri: Tri) { self.verts.push(tri.a); self.verts.push(tri.b); self.verts.push(tri.c); } /// Push a new quad onto the end of this mesh. - pub fn push_quad(&mut self, quad: Quad

) { + pub fn push_quad(&mut self, quad: Quad) { // A quad is composed of two triangles. The code below converts the former to // the latter. + if V::QUADS_INDEX.is_some() { + // 0, 1, 2, 2, 1, 3 + // b, c, a, a, c, d + self.verts.push(quad.b); + self.verts.push(quad.c); + self.verts.push(quad.a); + self.verts.push(quad.d); + } else { + // Tri 1 + self.verts.push(quad.a); + self.verts.push(quad.b); + self.verts.push(quad.c); - // Tri 1 - self.verts.push(quad.a.clone()); - self.verts.push(quad.b); - self.verts.push(quad.c.clone()); - - // Tri 2 - self.verts.push(quad.c); - self.verts.push(quad.d); - self.verts.push(quad.a); + // Tri 2 + self.verts.push(quad.c); + self.verts.push(quad.d); + self.verts.push(quad.a); + } } /// Overwrite a quad - pub fn replace_quad(&mut self, index: usize, quad: Quad

) { - debug_assert!(index % 3 == 0); - assert!(index + 5 < self.verts.len()); - // Tri 1 - self.verts[index] = quad.a.clone(); - self.verts[index + 1] = quad.b; - self.verts[index + 2] = quad.c.clone(); + pub fn replace_quad(&mut self, index: usize, quad: Quad) { + if V::QUADS_INDEX.is_some() { + debug_assert!(index % 4 == 0); + assert!(index + 3 < self.verts.len()); + self.verts[index] = quad.b; + self.verts[index + 1] = quad.c; + self.verts[index + 2] = quad.a; + self.verts[index + 3] = quad.d; + } else { + debug_assert!(index % 3 == 0); + assert!(index + 5 < self.verts.len()); + // Tri 1 + self.verts[index] = quad.a; + self.verts[index + 1] = quad.b; + self.verts[index + 2] = quad.c; - // Tri 2 - self.verts[index + 3] = quad.c; - self.verts[index + 4] = quad.d; - self.verts[index + 5] = quad.a; + // Tri 2 + self.verts[index + 3] = quad.c; + self.verts[index + 4] = quad.d; + self.verts[index + 5] = quad.a; + } } /// Push the vertices of another mesh onto the end of this mesh. - pub fn push_mesh(&mut self, other: &Mesh

) { self.verts.extend_from_slice(other.vertices()); } + pub fn push_mesh(&mut self, other: &Mesh) { self.verts.extend_from_slice(other.vertices()); } /// Map and push the vertices of another mesh onto the end of this mesh. - pub fn push_mesh_map P::Vertex>(&mut self, other: &Mesh

, mut f: F) { + pub fn push_mesh_map V>(&mut self, other: &Mesh, mut f: F) { // Reserve enough space in our Vec. This isn't necessary, but it tends to reduce // the number of required (re)allocations. self.verts.reserve(other.vertices().len()); for vert in other.vertices() { - self.verts.push(f(vert.clone())); + self.verts.push(f(*vert)); } } - pub fn iter(&self) -> std::slice::Iter { self.verts.iter() } + pub fn iter(&self) -> std::slice::Iter { self.verts.iter() } /// NOTE: Panics if vertex_range is out of bounds of vertices. - pub fn iter_mut(&mut self, vertex_range: Range) -> std::slice::IterMut { + pub fn iter_mut(&mut self, vertex_range: Range) -> std::slice::IterMut { self.verts[vertex_range].iter_mut() } } -impl IntoIterator for Mesh

{ - type IntoIter = std::vec::IntoIter; - type Item = P::Vertex; +impl IntoIterator for Mesh { + type IntoIter = std::vec::IntoIter; + type Item = V; fn into_iter(self) -> Self::IntoIter { self.verts.into_iter() } } -impl FromIterator> for Mesh

{ - fn from_iter>>(tris: I) -> Self { +impl FromIterator> for Mesh { + fn from_iter>>(tris: I) -> Self { tris.into_iter().fold(Self::new(), |mut this, tri| { this.push_tri(tri); this @@ -110,8 +127,8 @@ impl FromIterator> for Mesh

{ } } -impl FromIterator> for Mesh

{ - fn from_iter>>(quads: I) -> Self { +impl FromIterator> for Mesh { + fn from_iter>>(quads: I) -> Self { quads.into_iter().fold(Self::new(), |mut this, quad| { this.push_quad(quad); this @@ -120,40 +137,35 @@ impl FromIterator> for Mesh

{ } /// Represents a triangle stored on the CPU. -pub struct Tri { - a: P::Vertex, - b: P::Vertex, - c: P::Vertex, +pub struct Tri { + a: V, + b: V, + c: V, } -impl Tri

{ - pub fn new(a: P::Vertex, b: P::Vertex, c: P::Vertex) -> Self { Self { a, b, c } } +impl Tri { + pub fn new(a: V, b: V, c: V) -> Self { Self { a, b, c } } } /// Represents a quad stored on the CPU. -pub struct Quad { - a: P::Vertex, - b: P::Vertex, - c: P::Vertex, - d: P::Vertex, +pub struct Quad { + a: V, + b: V, + c: V, + d: V, } -impl Quad

{ - pub fn new(a: P::Vertex, b: P::Vertex, c: P::Vertex, d: P::Vertex) -> Self { - Self { a, b, c, d } - } +impl Quad { + pub fn new(a: V, b: V, c: V, d: V) -> Self { Self { a, b, c, d } } - pub fn rotated_by(self, n: usize) -> Self - where - P::Vertex: Clone, - { + pub fn rotated_by(self, n: usize) -> Self { let verts = [self.a, self.b, self.c, self.d]; Self { - a: verts[n % 4].clone(), - b: verts[(1 + n) % 4].clone(), - c: verts[(2 + n) % 4].clone(), - d: verts[(3 + n) % 4].clone(), + a: verts[n % 4], + b: verts[(1 + n) % 4], + c: verts[(2 + n) % 4], + d: verts[(3 + n) % 4], } } } diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 2c96ec0020..c4af1c02ad 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -1,3 +1,5 @@ +pub mod bound; +mod buffer; #[allow(clippy::single_component_path_imports)] // TODO: Pending review in #587 pub mod consts; mod error; @@ -10,58 +12,55 @@ pub mod texture; // Reexports pub use self::{ + bound::Bound, + buffer::Buffer, consts::Consts, error::RenderError, instances::Instances, mesh::{Mesh, Quad, Tri}, - model::{DynamicModel, Model}, + model::{DynamicModel, Model, SubModel}, pipelines::{ - clouds::{create_mesh as create_clouds_mesh, CloudsPipeline, Locals as CloudsLocals}, + clouds::Locals as CloudsLocals, + debug::{DebugPipeline, Locals as DebugLocals, Vertex as DebugVertex}, figure::{ - BoneData as FigureBoneData, BoneMeshes, FigureModel, FigurePipeline, + BoneData as FigureBoneData, BoneMeshes, FigureLayout, FigureModel, Locals as FigureLocals, }, - fluid::FluidPipeline, - lod_terrain::{Locals as LodTerrainLocals, LodData, LodTerrainPipeline}, - particle::{Instance as ParticleInstance, ParticlePipeline}, - postprocess::{ - create_mesh as create_pp_mesh, Locals as PostProcessLocals, PostProcessPipeline, + fluid::Vertex as FluidVertex, + lod_terrain::{LodData, Vertex as LodTerrainVertex}, + particle::{Instance as ParticleInstance, Vertex as ParticleVertex}, + postprocess::Locals as PostProcessLocals, + shadow::{Locals as ShadowLocals, PointLightMatrix}, + skybox::{create_mesh as create_skybox_mesh, Vertex as SkyboxVertex}, + sprite::{ + Instance as SpriteInstance, SpriteGlobalsBindGroup, SpriteVerts, + Vertex as SpriteVertex, VERT_PAGE_SIZE as SPRITE_VERT_PAGE_SIZE, }, - shadow::{Locals as ShadowLocals, ShadowPipeline}, - skybox::{create_mesh as create_skybox_mesh, Locals as SkyboxLocals, SkyboxPipeline}, - sprite::{Instance as SpriteInstance, Locals as SpriteLocals, SpritePipeline}, - terrain::{Locals as TerrainLocals, TerrainPipeline}, + terrain::{Locals as TerrainLocals, TerrainLayout, Vertex as TerrainVertex}, ui::{ create_quad as create_ui_quad, create_quad_vert_gradient as create_ui_quad_vert_gradient, create_tri as create_ui_tri, - Locals as UiLocals, Mode as UiMode, UiPipeline, + BoundLocals as UiBoundLocals, Locals as UiLocals, Mode as UiMode, + TextureBindGroup as UiTextureBindGroup, Vertex as UiVertex, }, - GlobalModel, Globals, Light, Shadow, + GlobalModel, Globals, GlobalsBindGroup, GlobalsLayouts, Light, Shadow, }, renderer::{ - ColLightFmt, ColLightInfo, LodAltFmt, LodColorFmt, LodTextureFmt, Renderer, - ShadowDepthStencilFmt, TgtColorFmt, TgtDepthStencilFmt, WinColorFmt, WinDepthFmt, + drawer::{ + DebugDrawer, Drawer, FigureDrawer, FigureShadowDrawer, FirstPassDrawer, ParticleDrawer, + PreparedUiDrawer, SecondPassDrawer, ShadowPassDrawer, SpriteDrawer, TerrainDrawer, + TerrainShadowDrawer, ThirdPassDrawer, UiDrawer, + }, + ColLightInfo, Renderer, }, texture::Texture, }; -pub use gfx::texture::{FilterMethod, WrapMode}; +pub use wgpu::{AddressMode, FilterMode}; -#[cfg(feature = "gl")] -use gfx_device_gl as gfx_backend; - -/// Used to represent a specific rendering configuration. -/// -/// Note that pipelines are tied to the -/// rendering backend, and as such it is necessary to modify the rendering -/// subsystem when adding new pipelines - custom pipelines are not currently an -/// objective of the rendering subsystem. -/// -/// # Examples -/// -/// - `SkyboxPipeline` -/// - `FigurePipeline` -pub trait Pipeline { - type Vertex: Clone + gfx::traits::Pod + gfx::pso::buffer::Structure; +pub trait Vertex: Clone + bytemuck::Pod { + const STRIDE: wgpu::BufferAddress; + // Whether these types of verts use the quad index buffer for drawing them + const QUADS_INDEX: Option; } use serde::{Deserialize, Serialize}; @@ -243,6 +242,30 @@ impl Default for UpscaleMode { fn default() -> Self { Self { factor: 1.0 } } } +/// Present modes +/// See https://docs.rs/wgpu/0.7.0/wgpu/enum.PresentMode.html +#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)] +pub enum PresentMode { + Fifo, + Mailbox, + #[serde(other)] + Immediate, +} + +impl Default for PresentMode { + fn default() -> Self { Self::Immediate } +} + +impl From for wgpu::PresentMode { + fn from(mode: PresentMode) -> Self { + match mode { + PresentMode::Fifo => wgpu::PresentMode::Fifo, + PresentMode::Mailbox => wgpu::PresentMode::Mailbox, + PresentMode::Immediate => wgpu::PresentMode::Immediate, + } + } +} + /// Render modes #[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)] #[serde(default)] @@ -253,4 +276,6 @@ pub struct RenderMode { pub lighting: LightingMode, pub shadow: ShadowMode, pub upscale_mode: UpscaleMode, + pub present_mode: PresentMode, + pub profiler_enabled: bool, } diff --git a/voxygen/src/render/model.rs b/voxygen/src/render/model.rs index 119f314feb..7ba2dbb401 100644 --- a/voxygen/src/render/model.rs +++ b/voxygen/src/render/model.rs @@ -1,69 +1,89 @@ -use super::{gfx_backend, mesh::Mesh, Pipeline, RenderError}; -use gfx::{ - buffer::Role, - memory::{Bind, Usage}, - traits::FactoryExt, - Factory, +use super::{ + buffer::{Buffer, DynamicBuffer}, + mesh::Mesh, + Vertex, }; use std::ops::Range; /// Represents a mesh that has been sent to the GPU. -pub struct Model { - pub vbuf: gfx::handle::Buffer, +pub struct SubModel<'a, V: Vertex> { pub vertex_range: Range, + buf: &'a wgpu::Buffer, + phantom_data: std::marker::PhantomData, } -impl Model

{ - pub fn new(factory: &mut gfx_backend::Factory, mesh: &Mesh

) -> Self { - Self { - vbuf: factory.create_vertex_buffer(mesh.vertices()), - vertex_range: 0..mesh.vertices().len() as u32, - } +impl<'a, V: Vertex> SubModel<'a, V> { + pub(super) fn buf(&self) -> wgpu::BufferSlice<'a> { + let start = self.vertex_range.start as wgpu::BufferAddress * V::STRIDE; + let end = self.vertex_range.end as wgpu::BufferAddress * V::STRIDE; + self.buf.slice(start..end) } - pub fn vertex_range(&self) -> Range { self.vertex_range.clone() } + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> u32 { self.vertex_range.end - self.vertex_range.start } +} - /// Create a model with a slice of a portion of this model to send to the - /// renderer. - pub fn submodel(&self, vertex_range: Range) -> Model

{ - Model { - vbuf: self.vbuf.clone(), - vertex_range, +/// Represents a mesh that has been sent to the GPU. +pub struct Model { + vbuf: Buffer, +} + +impl Model { + /// Returns None if the provided mesh is empty + pub fn new(device: &wgpu::Device, mesh: &Mesh) -> Option { + if mesh.vertices().is_empty() { + return None; } - } -} -/// Represents a mesh on the GPU which can be updated dynamically. -pub struct DynamicModel { - pub vbuf: gfx::handle::Buffer, -} - -impl DynamicModel

{ - pub fn new(factory: &mut gfx_backend::Factory, size: usize) -> Result { - Ok(Self { - vbuf: factory - .create_buffer(size, Role::Vertex, Usage::Dynamic, Bind::empty()) - .map_err(RenderError::BufferCreationError)?, + Some(Self { + vbuf: Buffer::new(device, wgpu::BufferUsage::VERTEX, mesh.vertices()), }) } /// Create a model with a slice of a portion of this model to send to the /// renderer. - pub fn submodel(&self, vertex_range: Range) -> Model

{ - Model { - vbuf: self.vbuf.clone(), + pub fn submodel(&self, vertex_range: Range) -> SubModel { + SubModel { vertex_range, + buf: self.buf(), + phantom_data: std::marker::PhantomData, } } - pub fn update( - &self, - encoder: &mut gfx::Encoder, - mesh: &Mesh

, - offset: usize, - ) -> Result<(), RenderError> { - encoder - .update_buffer(&self.vbuf, mesh.vertices(), offset) - .map_err(RenderError::UpdateError) - } + pub(super) fn buf(&self) -> &wgpu::Buffer { &self.vbuf.buf } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { self.vbuf.len() } +} + +/// Represents a mesh that has been sent to the GPU. +pub struct DynamicModel { + vbuf: DynamicBuffer, +} + +impl DynamicModel { + pub fn new(device: &wgpu::Device, size: usize) -> Self { + Self { + vbuf: DynamicBuffer::new(device, size, wgpu::BufferUsage::VERTEX), + } + } + + pub fn update(&self, queue: &wgpu::Queue, mesh: &Mesh, offset: usize) { + self.vbuf.update(queue, mesh.vertices(), offset) + } + + /// Create a model with a slice of a portion of this model to send to the + /// renderer. + pub fn submodel(&self, vertex_range: Range) -> SubModel { + SubModel { + vertex_range, + buf: self.buf(), + phantom_data: std::marker::PhantomData, + } + } + + pub fn buf(&self) -> &wgpu::Buffer { &self.vbuf.buf } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { self.vbuf.len() } } diff --git a/voxygen/src/render/pipelines/blit.rs b/voxygen/src/render/pipelines/blit.rs new file mode 100644 index 0000000000..fc7050f9c7 --- /dev/null +++ b/voxygen/src/render/pipelines/blit.rs @@ -0,0 +1,123 @@ +pub struct BindGroup { + pub(in super::super) bind_group: wgpu::BindGroup, +} + +pub struct BlitLayout { + pub layout: wgpu::BindGroupLayout, +} + +impl BlitLayout { + pub fn new(device: &wgpu::Device) -> Self { + Self { + layout: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // Color source + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + ], + }), + } + } + + pub fn bind( + &self, + device: &wgpu::Device, + src_color: &wgpu::TextureView, + sampler: &wgpu::Sampler, + ) -> BindGroup { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(src_color), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sampler), + }, + ], + }); + + BindGroup { bind_group } + } +} + +pub struct BlitPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl BlitPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + sc_desc: &wgpu::SwapChainDescriptor, + layout: &BlitLayout, + ) -> Self { + common_base::span!(_guard, "BlitPipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Blit pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&layout.layout], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Blit pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: sc_desc.format, + blend: None, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } +} diff --git a/voxygen/src/render/pipelines/clouds.rs b/voxygen/src/render/pipelines/clouds.rs index 7ad0c782b9..7796f47689 100644 --- a/voxygen/src/render/pipelines/clouds.rs +++ b/voxygen/src/render/pipelines/clouds.rs @@ -1,40 +1,15 @@ use super::{ - super::{Mesh, Pipeline, TgtColorFmt, TgtDepthStencilFmt, Tri}, - Globals, -}; -use gfx::{ - self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, - gfx_pipeline_inner, gfx_vertex_struct_meta, + super::{AaMode, Consts}, + GlobalsLayouts, }; +use bytemuck::{Pod, Zeroable}; use vek::*; -gfx_defines! { - vertex Vertex { - pos: [f32; 2] = "v_pos", - } - - constant Locals { - proj_mat_inv: [[f32; 4]; 4] = "proj_mat_inv", - view_mat_inv: [[f32; 4]; 4] = "view_mat_inv", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer = (), - - locals: gfx::ConstantBuffer = "u_locals", - globals: gfx::ConstantBuffer = "u_globals", - - map: gfx::TextureSampler<[f32; 4]> = "t_map", - alt: gfx::TextureSampler<[f32; 2]> = "t_alt", - horizon: gfx::TextureSampler<[f32; 4]> = "t_horizon", - - color_sampler: gfx::TextureSampler<::View> = "src_color", - depth_sampler: gfx::TextureSampler<::View> = "src_depth", - - noise: gfx::TextureSampler = "t_noise", - - tgt_color: gfx::RenderTarget = "tgt_color", - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Locals { + proj_mat_inv: [[f32; 4]; 4], + view_mat_inv: [[f32; 4]; 4], } impl Default for Locals { @@ -50,28 +25,180 @@ impl Locals { } } -pub struct CloudsPipeline; - -impl Pipeline for CloudsPipeline { - type Vertex = Vertex; +pub struct BindGroup { + pub(in super::super) bind_group: wgpu::BindGroup, } -pub fn create_mesh() -> Mesh { - let mut mesh = Mesh::new(); - - #[rustfmt::skip] - mesh.push_tri(Tri::new( - Vertex { pos: [ 1.0, -1.0] }, - Vertex { pos: [-1.0, 1.0] }, - Vertex { pos: [-1.0, -1.0] }, - )); - - #[rustfmt::skip] - mesh.push_tri(Tri::new( - Vertex { pos: [1.0, -1.0] }, - Vertex { pos: [1.0, 1.0] }, - Vertex { pos: [-1.0, 1.0] }, - )); - - mesh +pub struct CloudsLayout { + pub layout: wgpu::BindGroupLayout, +} + +impl CloudsLayout { + pub fn new(device: &wgpu::Device) -> Self { + Self { + layout: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // Color source + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + // Depth source + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + // Locals + wgpu::BindGroupLayoutEntry { + binding: 4, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }), + } + } + + pub fn bind( + &self, + device: &wgpu::Device, + src_color: &wgpu::TextureView, + src_depth: &wgpu::TextureView, + sampler: &wgpu::Sampler, + depth_sampler: &wgpu::Sampler, + locals: &Consts, + ) -> BindGroup { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(src_color), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(src_depth), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Sampler(depth_sampler), + }, + wgpu::BindGroupEntry { + binding: 4, + resource: locals.buf().as_entire_binding(), + }, + ], + }); + + BindGroup { bind_group } + } +} + +pub struct CloudsPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl CloudsPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + layout: &CloudsLayout, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "CloudsPipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Clouds pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&global_layout.globals, &layout.layout], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Clouds pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba16Float, + blend: None, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } } diff --git a/voxygen/src/render/pipelines/debug.rs b/voxygen/src/render/pipelines/debug.rs new file mode 100644 index 0000000000..e096510dc4 --- /dev/null +++ b/voxygen/src/render/pipelines/debug.rs @@ -0,0 +1,172 @@ +use super::super::{AaMode, Bound, Consts, GlobalsLayouts, Vertex as VertexTrait}; +use bytemuck::{Pod, Zeroable}; +use std::mem; +use vek::*; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Vertex { + pub pos: [f32; 3], +} + +impl Vertex { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: Self::STRIDE, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }], + } + } +} + +impl VertexTrait for Vertex { + const QUADS_INDEX: Option = None; + const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Locals { + /// pos is [f32; 4] instead of [f32; 3] so that Locals's size is a multiple + /// of 8 bytes (which is required by gfx), the last component is ignored + /// by the shader + pub pos: [f32; 4], + pub color: [f32; 4], +} + +pub type BoundLocals = Bound>; + +impl From> for Vertex { + fn from(pos: Vec3) -> Vertex { + Vertex { + pos: [pos.x, pos.y, pos.z], + } + } +} + +pub struct DebugPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl DebugPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + global_layouts: &GlobalsLayouts, + layout: &DebugLayout, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "DebugPipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Debug pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&global_layouts.globals, &layout.locals], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Debug pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Vertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::GreaterEqual, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba16Float, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } +} + +pub struct DebugLayout { + pub locals: wgpu::BindGroupLayout, +} + +impl DebugLayout { + pub fn new(device: &wgpu::Device) -> Self { + Self { + locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }), + } + } + + pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts) -> BoundLocals { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.locals, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: locals.buf().as_entire_binding(), + }], + }); + + BoundLocals { + bind_group, + with: locals, + } + } +} diff --git a/voxygen/src/render/pipelines/figure.rs b/voxygen/src/render/pipelines/figure.rs index d9f5f45b74..662f5db4c6 100644 --- a/voxygen/src/render/pipelines/figure.rs +++ b/voxygen/src/render/pipelines/figure.rs @@ -1,58 +1,32 @@ use super::{ - super::{Mesh, Model, Pipeline, TerrainPipeline, TgtColorFmt, TgtDepthStencilFmt}, - shadow, Globals, Light, Shadow, + super::{AaMode, Bound, Consts, GlobalsLayouts, Mesh, Model}, + terrain::Vertex, }; use crate::mesh::greedy::GreedyMesh; -use gfx::{ - self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, - gfx_pipeline_inner, state::ColorMask, -}; +use bytemuck::{Pod, Zeroable}; use vek::*; -gfx_defines! { - constant Locals { - model_mat: [[f32; 4]; 4] = "model_mat", - highlight_col: [f32; 4] = "highlight_col", - model_light: [f32; 4] = "model_light", - model_glow: [f32; 4] = "model_glow", - atlas_offs: [i32; 4] = "atlas_offs", - model_pos: [f32; 3] = "model_pos", - flags: u32 = "flags", - } - - constant BoneData { - bone_mat: [[f32; 4]; 4] = "bone_mat", - normals_mat: [[f32; 4]; 4] = "normals_mat", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer<::Vertex> = (), - // abuf: gfx::VertexBuffer<::Vertex> = (), - col_lights: gfx::TextureSampler<[f32; 4]> = "t_col_light", - - locals: gfx::ConstantBuffer = "u_locals", - globals: gfx::ConstantBuffer = "u_globals", - bones: gfx::ConstantBuffer = "u_bones", - lights: gfx::ConstantBuffer = "u_lights", - shadows: gfx::ConstantBuffer = "u_shadows", - - point_shadow_maps: gfx::TextureSampler = "t_point_shadow_maps", - directed_shadow_maps: gfx::TextureSampler = "t_directed_shadow_maps", - - alt: gfx::TextureSampler<[f32; 2]> = "t_alt", - horizon: gfx::TextureSampler<[f32; 4]> = "t_horizon", - - noise: gfx::TextureSampler = "t_noise", - - // Shadow stuff - light_shadows: gfx::ConstantBuffer = "u_light_shadows", - - tgt_color: gfx::BlendTarget = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA), - tgt_depth_stencil: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, - // tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Replace))), - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Locals { + model_mat: [[f32; 4]; 4], + highlight_col: [f32; 4], + model_light: [f32; 4], + model_glow: [f32; 4], + atlas_offs: [i32; 4], + model_pos: [f32; 3], + flags: u32, } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct BoneData { + bone_mat: [[f32; 4]; 4], + normals_mat: [[f32; 4]; 4], +} + +pub type BoundLocals = Bound<(Consts, Consts)>; + impl Locals { pub fn new( model_mat: anim::vek::Mat4, @@ -105,14 +79,8 @@ impl Default for BoneData { fn default() -> Self { Self::new(anim::vek::Mat4::identity(), anim::vek::Mat4::identity()) } } -pub struct FigurePipeline; - -impl Pipeline for FigurePipeline { - type Vertex = ::Vertex; -} - pub struct FigureModel { - pub opaque: Model, + pub opaque: Model, /* TODO: Consider using mipmaps instead of storing multiple texture atlases for different * LOD levels. */ } @@ -129,4 +97,157 @@ impl FigureModel { } } -pub type BoneMeshes = (Mesh, anim::vek::Aabb); +pub type BoneMeshes = (Mesh, anim::vek::Aabb); + +pub struct FigureLayout { + pub locals: wgpu::BindGroupLayout, +} + +impl FigureLayout { + pub fn new(device: &wgpu::Device) -> Self { + Self { + locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // locals + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // bone data + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }), + } + } + + pub fn bind_locals( + &self, + device: &wgpu::Device, + locals: Consts, + bone_data: Consts, + ) -> BoundLocals { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.locals, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: locals.buf().as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: bone_data.buf().as_entire_binding(), + }, + ], + }); + + BoundLocals { + bind_group, + with: (locals, bone_data), + } + } +} + +pub struct FigurePipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl FigurePipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + layout: &FigureLayout, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "FigurePipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Figure pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[ + &global_layout.globals, + &global_layout.shadow_textures, + &layout.locals, + &global_layout.col_light, + ], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Figure pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Vertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::GreaterEqual, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba16Float, + blend: None, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } +} diff --git a/voxygen/src/render/pipelines/fluid.rs b/voxygen/src/render/pipelines/fluid.rs index a871aa3c4a..3d49fb95c3 100644 --- a/voxygen/src/render/pipelines/fluid.rs +++ b/voxygen/src/render/pipelines/fluid.rs @@ -1,42 +1,12 @@ -use super::{ - super::{Pipeline, TerrainLocals, TgtColorFmt, TgtDepthStencilFmt}, - shadow, Globals, Light, Shadow, -}; -use gfx::{ - self, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, - gfx_vertex_struct_meta, state::ColorMask, -}; +use super::super::{AaMode, GlobalsLayouts, TerrainLayout, Vertex as VertexTrait}; +use bytemuck::{Pod, Zeroable}; +use std::mem; use vek::*; -gfx_defines! { - vertex Vertex { - pos_norm: u32 = "v_pos_norm", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer = (), - - locals: gfx::ConstantBuffer = "u_locals", - globals: gfx::ConstantBuffer = "u_globals", - lights: gfx::ConstantBuffer = "u_lights", - shadows: gfx::ConstantBuffer = "u_shadows", - - point_shadow_maps: gfx::TextureSampler = "t_point_shadow_maps", - directed_shadow_maps: gfx::TextureSampler = "t_directed_shadow_maps", - - alt: gfx::TextureSampler<[f32; 2]> = "t_alt", - horizon: gfx::TextureSampler<[f32; 4]> = "t_horizon", - - noise: gfx::TextureSampler = "t_noise", - waves: gfx::TextureSampler<[f32; 4]> = "t_waves", - - // Shadow stuff - light_shadows: gfx::ConstantBuffer = "u_light_shadows", - - tgt_color: gfx::BlendTarget = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA), - tgt_depth_stencil: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_TEST, - // tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_TEST,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Vertex { + pos_norm: u32, } impl Vertex { @@ -61,10 +31,116 @@ impl Vertex { | (norm_bits & 0x7) << 29, } } + + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + const ATTRIBUTES: [wgpu::VertexAttribute; 1] = wgpu::vertex_attr_array![0 => Uint32]; + wgpu::VertexBufferLayout { + array_stride: Self::STRIDE, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &ATTRIBUTES, + } + } } -pub struct FluidPipeline; - -impl Pipeline for FluidPipeline { - type Vertex = Vertex; +impl VertexTrait for Vertex { + const QUADS_INDEX: Option = Some(wgpu::IndexFormat::Uint16); + const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; +} + +pub struct FluidPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl FluidPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + terrain_layout: &TerrainLayout, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "FluidPipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Fluid pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[ + &global_layout.globals, + &global_layout.shadow_textures, + &terrain_layout.locals, + ], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Fluid pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Vertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::GreaterEqual, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba16Float, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } } diff --git a/voxygen/src/render/pipelines/lod_terrain.rs b/voxygen/src/render/pipelines/lod_terrain.rs index 9003525dc5..4257428dbd 100644 --- a/voxygen/src/render/pipelines/lod_terrain.rs +++ b/voxygen/src/render/pipelines/lod_terrain.rs @@ -1,40 +1,12 @@ -use super::{ - super::{ - LodAltFmt, LodColorFmt, LodTextureFmt, Pipeline, Renderer, Texture, TgtColorFmt, - TgtDepthStencilFmt, - }, - Globals, -}; -use gfx::{ - self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, - gfx_pipeline_inner, gfx_vertex_struct_meta, texture::SamplerInfo, -}; +use super::super::{AaMode, GlobalsLayouts, Renderer, Texture, Vertex as VertexTrait}; +use bytemuck::{Pod, Zeroable}; +use std::mem; use vek::*; -gfx_defines! { - vertex Vertex { - pos: [f32; 2] = "v_pos", - } - - constant Locals { - nul: [f32; 4] = "nul", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer = (), - - locals: gfx::ConstantBuffer = "u_locals", - globals: gfx::ConstantBuffer = "u_globals", - map: gfx::TextureSampler<[f32; 4]> = "t_map", - alt: gfx::TextureSampler<[f32; 2]> = "t_alt", - horizon: gfx::TextureSampler<[f32; 4]> = "t_horizon", - - noise: gfx::TextureSampler = "t_noise", - - tgt_color: gfx::RenderTarget = "tgt_color", - tgt_depth_stencil: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, - // tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Vertex { + pos: [f32; 2], } impl Vertex { @@ -43,75 +15,210 @@ impl Vertex { pos: pos.into_array(), } } + + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + const ATTRIBUTES: [wgpu::VertexAttribute; 1] = wgpu::vertex_attr_array![0 => Float32x2]; + wgpu::VertexBufferLayout { + array_stride: Self::STRIDE, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &ATTRIBUTES, + } + } } -impl Locals { - pub fn default() -> Self { Self { nul: [0.0; 4] } } -} - -pub struct LodTerrainPipeline; - -impl Pipeline for LodTerrainPipeline { - type Vertex = Vertex; +impl VertexTrait for Vertex { + const QUADS_INDEX: Option = Some(wgpu::IndexFormat::Uint32); + const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; } pub struct LodData { - pub map: Texture, - pub alt: Texture, - pub horizon: Texture, + pub map: Texture, + pub alt: Texture, + pub horizon: Texture, pub tgt_detail: u32, } impl LodData { + pub fn dummy(renderer: &mut Renderer) -> Self { + let map_size = Vec2::new(1, 1); + //let map_border = [0.0, 0.0, 0.0, 0.0]; + let map_image = [0]; + let alt_image = [0]; + let horizon_image = [0x_00_01_00_01]; + + Self::new( + renderer, + map_size, + &map_image, + &alt_image, + &horizon_image, + 1, + //map_border.into(), + ) + } + pub fn new( renderer: &mut Renderer, - map_size: Vec2, + map_size: Vec2, lod_base: &[u32], lod_alt: &[u32], lod_horizon: &[u32], tgt_detail: u32, - border_color: gfx::texture::PackedColor, + //border_color: gfx::texture::PackedColor, ) -> Self { - let kind = gfx::texture::Kind::D2(map_size.x, map_size.y, gfx::texture::AaMode::Single); - let info = gfx::texture::SamplerInfo::new( - gfx::texture::FilterMethod::Bilinear, - gfx::texture::WrapMode::Border, + let mut create_texture = |format, data, filter| { + let texture_info = wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: map_size.x, + height: map_size.y, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }; + + let sampler_info = wgpu::SamplerDescriptor { + label: None, + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: filter, + min_filter: filter, + mipmap_filter: wgpu::FilterMode::Nearest, + border_color: Some(wgpu::SamplerBorderColor::TransparentBlack), + ..Default::default() + }; + + let view_info = wgpu::TextureViewDescriptor { + label: None, + format: Some(format), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }; + + renderer.create_texture_with_data_raw( + &texture_info, + &view_info, + &sampler_info, + bytemuck::cast_slice(data), + ) + }; + let map = create_texture( + wgpu::TextureFormat::Rgba8UnormSrgb, + lod_base, + wgpu::FilterMode::Linear, ); + // SamplerInfo { + // border: border_color, + let alt = create_texture( + wgpu::TextureFormat::Rgba8Unorm, + lod_alt, + wgpu::FilterMode::Linear, + ); + // SamplerInfo { + // border: [0.0, 0.0, 0.0, 0.0].into(), + let horizon = create_texture( + wgpu::TextureFormat::Rgba8Unorm, + lod_horizon, + wgpu::FilterMode::Linear, + ); + // SamplerInfo { + // border: [1.0, 0.0, 1.0, 0.0].into(), + Self { - map: renderer - .create_texture_immutable_raw( - kind, - gfx::texture::Mipmap::Provided, - &[gfx::memory::cast_slice(lod_base)], - SamplerInfo { - border: border_color, - ..info - }, - ) - .expect("Failed to generate map texture"), - alt: renderer - .create_texture_immutable_raw( - kind, - gfx::texture::Mipmap::Provided, - &[gfx::memory::cast_slice(lod_alt)], - SamplerInfo { - border: [0.0, 0.0, 0.0, 0.0].into(), - ..info - }, - ) - .expect("Failed to generate alt texture"), - horizon: renderer - .create_texture_immutable_raw( - kind, - gfx::texture::Mipmap::Provided, - &[gfx::memory::cast_slice(lod_horizon)], - SamplerInfo { - border: [1.0, 0.0, 1.0, 0.0].into(), - ..info - }, - ) - .expect("Failed to generate horizon texture"), + map, + alt, + horizon, tgt_detail, } } } + +pub struct LodTerrainPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl LodTerrainPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + aa_mode: AaMode, + ) -> Self { + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Lod terrain pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&global_layout.globals, &global_layout.shadow_textures], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Lod terrain pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Vertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::GreaterEqual, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba16Float, + blend: None, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } +} diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index ac23906e00..846a4d7339 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -1,4 +1,6 @@ +pub mod blit; pub mod clouds; +pub mod debug; pub mod figure; pub mod fluid; pub mod lod_terrain; @@ -10,56 +12,70 @@ pub mod sprite; pub mod terrain; pub mod ui; -use super::Consts; +use super::{Consts, Texture}; use crate::scene::camera::CameraMode; +use bytemuck::{Pod, Zeroable}; use common::terrain::BlockKind; -use gfx::{self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta}; use vek::*; -pub const MAX_POINT_LIGHT_COUNT: usize = 31; +// TODO: auto insert these into shaders +pub const MAX_POINT_LIGHT_COUNT: usize = 20; pub const MAX_FIGURE_SHADOW_COUNT: usize = 24; pub const MAX_DIRECTED_LIGHT_COUNT: usize = 6; -gfx_defines! { - constant Globals { - view_mat: [[f32; 4]; 4] = "view_mat", - proj_mat: [[f32; 4]; 4] = "proj_mat", - all_mat: [[f32; 4]; 4] = "all_mat", - cam_pos: [f32; 4] = "cam_pos", - focus_off: [f32; 4] = "focus_off", - focus_pos: [f32; 4] = "focus_pos", - /// NOTE: view_distance.x is the horizontal view distance, view_distance.y is the LOD - /// detail, view_distance.z is the - /// minimum height over any land chunk (i.e. the sea level), and view_distance.w is the - /// maximum height over this minimum height. - /// - /// TODO: Fix whatever alignment issue requires these uniforms to be aligned. - view_distance: [f32; 4] = "view_distance", - time_of_day: [f32; 4] = "time_of_day", // TODO: Make this f64. - sun_dir: [f32; 4] = "sun_dir", - moon_dir: [f32; 4] = "moon_dir", - tick: [f32; 4] = "tick", - /// x, y represent the resolution of the screen; - /// w, z represent the near and far planes of the shadow map. - screen_res: [f32; 4] = "screen_res", - light_shadow_count: [u32; 4] = "light_shadow_count", - shadow_proj_factors: [f32; 4] = "shadow_proj_factors", - medium: [u32; 4] = "medium", - select_pos: [i32; 4] = "select_pos", - gamma_exposure: [f32; 4] = "gamma_exposure", - ambiance: f32 = "ambiance", - cam_mode: u32 = "cam_mode", - sprite_render_distance: f32 = "sprite_render_distance", - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Globals { + /// Transformation from world coordinate space (with focus_off as the + /// origin) to the camera space + view_mat: [[f32; 4]; 4], + proj_mat: [[f32; 4]; 4], + /// proj_mat * view_mat + all_mat: [[f32; 4]; 4], + /// Offset of the camera from the focus position + cam_pos: [f32; 4], + /// Integer portion of the focus position in world coordinates + focus_off: [f32; 4], + /// Fractions portion of the focus position + focus_pos: [f32; 4], + /// NOTE: view_distance.x is the horizontal view distance, view_distance.y + /// is the LOD detail, view_distance.z is the + /// minimum height over any land chunk (i.e. the sea level), and + /// view_distance.w is the maximum height over this minimum height. + /// + /// TODO: Fix whatever alignment issue requires these uniforms to be + /// aligned. + view_distance: [f32; 4], + time_of_day: [f32; 4], // TODO: Make this f64. + sun_dir: [f32; 4], + moon_dir: [f32; 4], + tick: [f32; 4], + /// x, y represent the resolution of the screen; + /// w, z represent the near and far planes of the shadow map. + screen_res: [f32; 4], + light_shadow_count: [u32; 4], + shadow_proj_factors: [f32; 4], + medium: [u32; 4], + select_pos: [i32; 4], + gamma_exposure: [f32; 4], + ambiance: f32, + cam_mode: u32, + sprite_render_distance: f32, + /// To keep 16-byte-aligned. + globals_dummy: f32, +} - constant Light { - pos: [f32; 4] = "light_pos", - col: [f32; 4] = "light_col", - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Light { + pub pos: [f32; 4], + pub col: [f32; 4], +} - constant Shadow { - pos_radius: [f32; 4] = "shadow_pos_radius", - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Shadow { + pos_radius: [f32; 4], } impl Globals { @@ -108,15 +124,16 @@ impl Globals { shadow_planes.x, shadow_planes.y, ], + // TODO: why do we accept values greater than the max? light_shadow_count: [ - (light_count % (MAX_POINT_LIGHT_COUNT + 1)) as u32, - (shadow_count % (MAX_FIGURE_SHADOW_COUNT + 1)) as u32, - (directed_light_count % (MAX_DIRECTED_LIGHT_COUNT + 1)) as u32, + usize::min(light_count, MAX_POINT_LIGHT_COUNT) as u32, + usize::min(shadow_count, MAX_FIGURE_SHADOW_COUNT) as u32, + usize::min(directed_light_count, MAX_DIRECTED_LIGHT_COUNT) as u32, 0, ], shadow_proj_factors: [ - (shadow_planes.y + shadow_planes.x) / (shadow_planes.y - shadow_planes.x), - (2.0 * shadow_planes.y * shadow_planes.x) / (shadow_planes.y - shadow_planes.x), + shadow_planes.y / (shadow_planes.y - shadow_planes.x), + shadow_planes.y * shadow_planes.x / (shadow_planes.y - shadow_planes.x), 0.0, 0.0, ], @@ -129,6 +146,7 @@ impl Globals { ambiance, cam_mode: cam_mode as u32, sprite_render_distance, + globals_dummy: 0.0, } } @@ -214,8 +232,388 @@ impl Default for Shadow { // Global scene data spread across several arrays. pub struct GlobalModel { + // TODO: enforce that these are the lengths in the shaders?? pub globals: Consts, pub lights: Consts, pub shadows: Consts, - pub shadow_mats: Consts, + pub shadow_mats: shadow::BoundLocals, + pub point_light_matrices: Box<[shadow::PointLightMatrix; 126]>, +} + +pub struct GlobalsBindGroup { + pub(super) bind_group: wgpu::BindGroup, +} + +pub struct ShadowTexturesBindGroup { + pub(super) bind_group: wgpu::BindGroup, +} + +pub struct GlobalsLayouts { + pub globals: wgpu::BindGroupLayout, + pub col_light: wgpu::BindGroupLayout, + pub shadow_textures: wgpu::BindGroupLayout, +} + +pub struct ColLights { + pub(super) bind_group: wgpu::BindGroup, + pub texture: Texture, + phantom: std::marker::PhantomData, +} + +impl GlobalsLayouts { + pub fn base_globals_layout() -> Vec { + vec![ + // Global uniform + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // Noise tex + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + // Light uniform + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // Shadow uniform + wgpu::BindGroupLayoutEntry { + binding: 4, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // Alt texture + wgpu::BindGroupLayoutEntry { + binding: 5, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 6, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + // Horizon texture + wgpu::BindGroupLayoutEntry { + binding: 7, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 8, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + // light shadows (ie shadows from a light?) + wgpu::BindGroupLayoutEntry { + binding: 9, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + // TODO: is this relevant? + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // lod map (t_map) + wgpu::BindGroupLayoutEntry { + binding: 10, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 11, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + ] + } + + pub fn new(device: &wgpu::Device) -> Self { + let globals = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Globals layout"), + entries: &Self::base_globals_layout(), + }); + + let col_light = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // col lights + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + ], + }); + + let shadow_textures = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // point shadow_maps + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Depth, + view_dimension: wgpu::TextureViewDimension::Cube, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: true, + }, + count: None, + }, + // directed shadow maps + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Depth, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: true, + }, + count: None, + }, + ], + }); + + Self { + globals, + col_light, + shadow_textures, + } + } + + // Note: this allocation serves the purpose of not having to duplicate code + pub fn bind_base_globals<'a>( + global_model: &'a GlobalModel, + lod_data: &'a lod_terrain::LodData, + noise: &'a Texture, + ) -> Vec> { + vec![ + // Global uniform + wgpu::BindGroupEntry { + binding: 0, + resource: global_model.globals.buf().as_entire_binding(), + }, + // Noise tex + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&noise.view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&noise.sampler), + }, + // Light uniform + wgpu::BindGroupEntry { + binding: 3, + resource: global_model.lights.buf().as_entire_binding(), + }, + // Shadow uniform + wgpu::BindGroupEntry { + binding: 4, + resource: global_model.shadows.buf().as_entire_binding(), + }, + // Alt texture + wgpu::BindGroupEntry { + binding: 5, + resource: wgpu::BindingResource::TextureView(&lod_data.alt.view), + }, + wgpu::BindGroupEntry { + binding: 6, + resource: wgpu::BindingResource::Sampler(&lod_data.alt.sampler), + }, + // Horizon texture + wgpu::BindGroupEntry { + binding: 7, + resource: wgpu::BindingResource::TextureView(&lod_data.horizon.view), + }, + wgpu::BindGroupEntry { + binding: 8, + resource: wgpu::BindingResource::Sampler(&lod_data.horizon.sampler), + }, + // light shadows + wgpu::BindGroupEntry { + binding: 9, + resource: global_model.shadow_mats.buf().as_entire_binding(), + }, + // lod map (t_map) + wgpu::BindGroupEntry { + binding: 10, + resource: wgpu::BindingResource::TextureView(&lod_data.map.view), + }, + wgpu::BindGroupEntry { + binding: 11, + resource: wgpu::BindingResource::Sampler(&lod_data.map.sampler), + }, + ] + } + + pub fn bind( + &self, + device: &wgpu::Device, + global_model: &GlobalModel, + lod_data: &lod_terrain::LodData, + noise: &Texture, + ) -> GlobalsBindGroup { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.globals, + entries: &Self::bind_base_globals(global_model, lod_data, noise), + }); + + GlobalsBindGroup { bind_group } + } + + pub fn bind_shadow_textures( + &self, + device: &wgpu::Device, + point_shadow_map: &Texture, + directed_shadow_map: &Texture, + ) -> ShadowTexturesBindGroup { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.shadow_textures, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&point_shadow_map.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&point_shadow_map.sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(&directed_shadow_map.view), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Sampler(&directed_shadow_map.sampler), + }, + ], + }); + + ShadowTexturesBindGroup { bind_group } + } + + pub fn bind_col_light( + &self, + device: &wgpu::Device, + col_light: Texture, + ) -> ColLights { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.col_light, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&col_light.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&col_light.sampler), + }, + ], + }); + + ColLights { + texture: col_light, + bind_group, + phantom: std::marker::PhantomData, + } + } } diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 6205425fa1..37832a7e7d 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -1,79 +1,18 @@ -use super::{ - super::{Pipeline, TgtColorFmt, TgtDepthStencilFmt}, - shadow, Globals, Light, Shadow, -}; -use gfx::{ - self, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, - gfx_vertex_struct_meta, state::ColorMask, -}; +use super::super::{AaMode, GlobalsLayouts, Vertex as VertexTrait}; +use bytemuck::{Pod, Zeroable}; +use std::mem; use vek::*; -gfx_defines! { - vertex Vertex { - pos: [f32; 3] = "v_pos", - // ____BBBBBBBBGGGGGGGGRRRRRRRR - // col: u32 = "v_col", - // ...AANNN - // A = AO - // N = Normal - norm_ao: u32 = "v_norm_ao", - } - - vertex Instance { - // created_at time, so we can calculate time relativity, needed for relative animation. - // can save 32 bits per instance, for particles that are not relatively animated. - inst_time: f32 = "inst_time", - - // The lifespan in seconds of the particle - inst_lifespan: f32 = "inst_lifespan", - - // a seed value for randomness - // can save 32 bits per instance, for particles that don't need randomness/uniqueness. - inst_entropy: f32 = "inst_entropy", - - // modes should probably be seperate shaders, as a part of scaling and optimisation efforts. - // can save 32 bits per instance, and have cleaner tailor made code. - inst_mode: i32 = "inst_mode", - - // A direction for particles to move in - inst_dir: [f32; 3] = "inst_dir", - - // a triangle is: f32 x 3 x 3 x 1 = 288 bits - // a quad is: f32 x 3 x 3 x 2 = 576 bits - // a cube is: f32 x 3 x 3 x 12 = 3456 bits - // this vec is: f32 x 3 x 1 x 1 = 96 bits (per instance!) - // consider using a throw-away mesh and - // positioning the vertex vertices instead, - // if we have: - // - a triangle mesh, and 3 or more instances. - // - a quad mesh, and 6 or more instances. - // - a cube mesh, and 36 or more instances. - inst_pos: [f32; 3] = "inst_pos", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer = (), - ibuf: gfx::InstanceBuffer = (), - - globals: gfx::ConstantBuffer = "u_globals", - lights: gfx::ConstantBuffer = "u_lights", - shadows: gfx::ConstantBuffer = "u_shadows", - - point_shadow_maps: gfx::TextureSampler = "t_point_shadow_maps", - directed_shadow_maps: gfx::TextureSampler = "t_directed_shadow_maps", - - alt: gfx::TextureSampler<[f32; 2]> = "t_alt", - horizon: gfx::TextureSampler<[f32; 4]> = "t_horizon", - - noise: gfx::TextureSampler = "t_noise", - - // Shadow stuff - light_shadows: gfx::ConstantBuffer = "u_light_shadows", - - tgt_color: gfx::BlendTarget = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA), - tgt_depth_stencil: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, - // tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Vertex { + pub pos: [f32; 3], + // ____BBBBBBBBGGGGGGGGRRRRRRRR + // col: u32 = "v_col", + // ...AANNN + // A = AO + // N = Normal + norm_ao: u32, } impl Vertex { @@ -92,6 +31,21 @@ impl Vertex { norm_ao: norm_bits, } } + + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + const ATTRIBUTES: [wgpu::VertexAttribute; 2] = + wgpu::vertex_attr_array![0 => Float32x3, 1 => Uint32]; + wgpu::VertexBufferLayout { + array_stride: Self::STRIDE, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &ATTRIBUTES, + } + } +} + +impl VertexTrait for Vertex { + const QUADS_INDEX: Option = Some(wgpu::IndexFormat::Uint16); + const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; } #[derive(Copy, Clone)] @@ -131,6 +85,40 @@ impl ParticleMode { pub fn into_uint(self) -> u32 { self as u32 } } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Instance { + // created_at time, so we can calculate time relativity, needed for relative animation. + // can save 32 bits per instance, for particles that are not relatively animated. + inst_time: f32, + + // The lifespan in seconds of the particle + inst_lifespan: f32, + + // a seed value for randomness + // can save 32 bits per instance, for particles that don't need randomness/uniqueness. + inst_entropy: f32, + + // modes should probably be seperate shaders, as a part of scaling and optimisation efforts. + // can save 32 bits per instance, and have cleaner tailor made code. + inst_mode: i32, + + // A direction for particles to move in + inst_dir: [f32; 3], + + // a triangle is: f32 x 3 x 3 x 1 = 288 bits + // a quad is: f32 x 3 x 3 x 2 = 576 bits + // a cube is: f32 x 3 x 3 x 12 = 3456 bits + // this vec is: f32 x 3 x 1 x 1 = 96 bits (per instance!) + // consider using a throw-away mesh and + // positioning the vertex verticies instead, + // if we have: + // - a triangle mesh, and 3 or more instances. + // - a quad mesh, and 6 or more instances. + // - a cube mesh, and 36 or more instances. + inst_pos: [f32; 3], +} + impl Instance { pub fn new( inst_time: f64, @@ -166,14 +154,111 @@ impl Instance { inst_dir: (inst_pos2 - inst_pos).into_array(), } } + + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + const ATTRIBUTES: [wgpu::VertexAttribute; 6] = wgpu::vertex_attr_array![2 => Float32, 3 => Float32, 4 => Float32, 5 => Sint32, 6 => Float32x3, 7 => Float32x3]; + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Instance, + attributes: &ATTRIBUTES, + } + } } impl Default for Instance { fn default() -> Self { Self::new(0.0, 0.0, ParticleMode::CampfireSmoke, Vec3::zero()) } } -pub struct ParticlePipeline; - -impl Pipeline for ParticlePipeline { - type Vertex = Vertex; +pub struct ParticlePipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl ParticlePipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "ParticlePipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Particle pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&global_layout.globals, &global_layout.shadow_textures], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Particle pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Vertex::desc(), Instance::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::GreaterEqual, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + // TODO: use a constant and/or pass in this format on pipeline construction + format: wgpu::TextureFormat::Rgba16Float, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } } diff --git a/voxygen/src/render/pipelines/postprocess.rs b/voxygen/src/render/pipelines/postprocess.rs index cda084ca8d..18c982f50c 100644 --- a/voxygen/src/render/pipelines/postprocess.rs +++ b/voxygen/src/render/pipelines/postprocess.rs @@ -1,40 +1,12 @@ -use super::{ - super::{Mesh, Pipeline, TgtColorFmt, TgtDepthStencilFmt, Tri, WinColorFmt}, - Globals, -}; -use gfx::{ - self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, - gfx_pipeline_inner, gfx_vertex_struct_meta, -}; +use super::super::{Consts, GlobalsLayouts}; +use bytemuck::{Pod, Zeroable}; use vek::*; -gfx_defines! { - vertex Vertex { - pos: [f32; 2] = "v_pos", - } - - constant Locals { - proj_mat_inv: [[f32; 4]; 4] = "proj_mat_inv", - view_mat_inv: [[f32; 4]; 4] = "view_mat_inv", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer = (), - - locals: gfx::ConstantBuffer = "u_locals", - globals: gfx::ConstantBuffer = "u_globals", - - map: gfx::TextureSampler<[f32; 4]> = "t_map", - alt: gfx::TextureSampler<[f32; 2]> = "t_alt", - horizon: gfx::TextureSampler<[f32; 4]> = "t_horizon", - - color_sampler: gfx::TextureSampler<::View> = "src_color", - depth_sampler: gfx::TextureSampler<::View> = "src_depth", - - noise: gfx::TextureSampler = "t_noise", - - tgt_color: gfx::RenderTarget = "tgt_color", - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Locals { + proj_mat_inv: [[f32; 4]; 4], + view_mat_inv: [[f32; 4]; 4], } impl Default for Locals { @@ -50,28 +22,143 @@ impl Locals { } } -pub struct PostProcessPipeline; - -impl Pipeline for PostProcessPipeline { - type Vertex = Vertex; +pub struct BindGroup { + pub(in super::super) bind_group: wgpu::BindGroup, } -pub fn create_mesh() -> Mesh { - let mut mesh = Mesh::new(); - - #[rustfmt::skip] - mesh.push_tri(Tri::new( - Vertex { pos: [ 1.0, -1.0] }, - Vertex { pos: [-1.0, 1.0] }, - Vertex { pos: [-1.0, -1.0] }, - )); - - #[rustfmt::skip] - mesh.push_tri(Tri::new( - Vertex { pos: [1.0, -1.0] }, - Vertex { pos: [1.0, 1.0] }, - Vertex { pos: [-1.0, 1.0] }, - )); - - mesh +pub struct PostProcessLayout { + pub layout: wgpu::BindGroupLayout, +} + +impl PostProcessLayout { + pub fn new(device: &wgpu::Device) -> Self { + Self { + layout: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // src color + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + // Locals + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }), + } + } + + pub fn bind( + &self, + device: &wgpu::Device, + src_color: &wgpu::TextureView, + sampler: &wgpu::Sampler, + locals: &Consts, + ) -> BindGroup { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(src_color), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: locals.buf().as_entire_binding(), + }, + ], + }); + + BindGroup { bind_group } + } +} + +pub struct PostProcessPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl PostProcessPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + sc_desc: &wgpu::SwapChainDescriptor, + global_layout: &GlobalsLayouts, + layout: &PostProcessLayout, + ) -> Self { + common_base::span!(_guard, "PostProcessPipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Post process pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&global_layout.globals, &layout.layout], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Post process pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: sc_desc.format, + blend: None, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } } diff --git a/voxygen/src/render/pipelines/shadow.rs b/voxygen/src/render/pipelines/shadow.rs index 1f1069b4b6..e51069c28d 100644 --- a/voxygen/src/render/pipelines/shadow.rs +++ b/voxygen/src/render/pipelines/shadow.rs @@ -1,54 +1,15 @@ -use super::{ - super::{ - ColLightFmt, ColLightInfo, Pipeline, RenderError, Renderer, ShadowDepthStencilFmt, - TerrainLocals, Texture, - }, - figure, terrain, Globals, -}; -use gfx::{ - self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, - gfx_pipeline_inner, +use super::super::{ + AaMode, Bound, ColLightInfo, Consts, FigureLayout, GlobalsLayouts, Renderer, TerrainLayout, + TerrainVertex, Texture, }; +use bytemuck::{Pod, Zeroable}; use vek::*; -gfx_defines! { - constant Locals { - shadow_matrices: [[f32; 4]; 4] = "shadowMatrices", - texture_mats: [[f32; 4]; 4] = "texture_mat", - } - - pipeline pipe { - // Terrain vertex stuff - vbuf: gfx::VertexBuffer = (), - - locals: gfx::ConstantBuffer = "u_locals", - globals: gfx::ConstantBuffer = "u_globals", - - // Shadow stuff - light_shadows: gfx::ConstantBuffer = "u_light_shadows", - - tgt_depth_stencil: gfx::DepthTarget = gfx::state::Depth { - fun: gfx::state::Comparison::Less, - write: true, - }, - } - - pipeline figure_pipe { - // Terrain vertex stuff - vbuf: gfx::VertexBuffer = (), - - locals: gfx::ConstantBuffer = "u_locals", - bones: gfx::ConstantBuffer = "u_bones", - globals: gfx::ConstantBuffer = "u_globals", - - // Shadow stuff - light_shadows: gfx::ConstantBuffer = "u_light_shadows", - - tgt_depth_stencil: gfx::DepthTarget = gfx::state::Depth { - fun: gfx::state::Comparison::Less, - write: true, - }, - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Locals { + shadow_matrices: [[f32; 4]; 4], + texture_mats: [[f32; 4]; 4], } impl Locals { @@ -62,29 +23,326 @@ impl Locals { pub fn default() -> Self { Self::new(Mat4::identity(), Mat4::identity()) } } -pub struct ShadowPipeline; +pub type BoundLocals = Bound>; -impl ShadowPipeline { - pub fn create_col_lights( - renderer: &mut Renderer, - (col_lights, col_lights_size): &ColLightInfo, - ) -> Result, RenderError> { - renderer.create_texture_immutable_raw( - gfx::texture::Kind::D2( - col_lights_size.x, - col_lights_size.y, - gfx::texture::AaMode::Single, - ), - gfx::texture::Mipmap::Provided, - &[col_lights], - gfx::texture::SamplerInfo::new( - gfx::texture::FilterMethod::Bilinear, - gfx::texture::WrapMode::Clamp, - ), - ) +pub struct ShadowLayout { + pub locals: wgpu::BindGroupLayout, +} + +impl ShadowLayout { + pub fn new(device: &wgpu::Device) -> Self { + Self { + locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }), + } + } + + pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts) -> BoundLocals { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.locals, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: locals.buf().as_entire_binding(), + }], + }); + + BoundLocals { + bind_group, + with: locals, + } } } -impl Pipeline for ShadowPipeline { - type Vertex = terrain::Vertex; +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct PointLightMatrix([[f32; 4]; 4]); + +impl PointLightMatrix { + pub fn new(shadow_mat: Mat4) -> Self { Self(shadow_mat.into_col_arrays()) } + + pub fn default() -> Self { Self::new(Mat4::identity()) } +} + +pub fn create_col_lights( + renderer: &mut Renderer, + (col_lights, col_lights_size): &ColLightInfo, +) -> Texture { + let texture_info = wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: u32::from(col_lights_size.x), + height: u32::from(col_lights_size.y), + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }; + + let sampler_info = wgpu::SamplerDescriptor { + label: None, + 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::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + border_color: Some(wgpu::SamplerBorderColor::TransparentBlack), + ..Default::default() + }; + + let view_info = wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Rgba8Unorm), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }; + + renderer.create_texture_with_data_raw( + &texture_info, + &view_info, + &sampler_info, + bytemuck::cast_slice(&col_lights), + ) +} + +pub struct ShadowFigurePipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl ShadowFigurePipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + figure_layout: &FigureLayout, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "new"); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Directed figure shadow pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&global_layout.globals, &figure_layout.locals], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Directed shadow figure pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[TerrainVertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Front), + clamp_depth: true, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth24Plus, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: None, + }); + + Self { + pipeline: render_pipeline, + } + } +} + +pub struct ShadowPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl ShadowPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + terrain_layout: &TerrainLayout, + aa_mode: AaMode, + ) -> Self { + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Directed shadow pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&global_layout.globals, &terrain_layout.locals], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Directed shadow pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[TerrainVertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Front), + clamp_depth: true, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth24Plus, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: None, + }); + + Self { + pipeline: render_pipeline, + } + } +} +pub struct PointShadowPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl PointShadowPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + terrain_layout: &TerrainLayout, + aa_mode: AaMode, + ) -> Self { + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Point shadow pipeline layout"), + push_constant_ranges: &[wgpu::PushConstantRange { + stages: wgpu::ShaderStage::all(), + range: 0..64, + }], + bind_group_layouts: &[&global_layout.globals, &terrain_layout.locals], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Point shadow pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[TerrainVertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth24Plus, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: None, + }); + + Self { + pipeline: render_pipeline, + } + } } diff --git a/voxygen/src/render/pipelines/skybox.rs b/voxygen/src/render/pipelines/skybox.rs index 9d2b43be3a..76dc159614 100644 --- a/voxygen/src/render/pipelines/skybox.rs +++ b/voxygen/src/render/pipelines/skybox.rs @@ -1,53 +1,120 @@ -use super::{ - super::{Mesh, Pipeline, Quad, TgtColorFmt, TgtDepthStencilFmt}, - Globals, -}; -use gfx::{ - self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, - gfx_pipeline_inner, gfx_vertex_struct_meta, -}; +use super::super::{AaMode, GlobalsLayouts, Mesh, Quad, Vertex as VertexTrait}; +use bytemuck::{Pod, Zeroable}; +use std::mem; -gfx_defines! { - vertex Vertex { - pos: [f32; 3] = "v_pos", - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Vertex { + pub pos: [f32; 3], +} - constant Locals { - nul: [f32; 4] = "nul", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer = (), - - locals: gfx::ConstantBuffer = "u_locals", - globals: gfx::ConstantBuffer = "u_globals", - - alt: gfx::TextureSampler<[f32; 2]> = "t_alt", - horizon: gfx::TextureSampler<[f32; 4]> = "t_horizon", - - noise: gfx::TextureSampler = "t_noise", - - tgt_color: gfx::RenderTarget = "tgt_color", - tgt_depth_stencil: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_TEST, - // tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), +impl Vertex { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: Self::STRIDE, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }], + } } } -impl Locals { - pub fn default() -> Self { Self { nul: [0.0; 4] } } +impl VertexTrait for Vertex { + const QUADS_INDEX: Option = None; + const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; } -pub struct SkyboxPipeline; - -impl Pipeline for SkyboxPipeline { - type Vertex = Vertex; +// TODO: does skybox still do anything with new cloud shaders? +pub struct SkyboxPipeline { + pub pipeline: wgpu::RenderPipeline, } -pub fn create_mesh() -> Mesh { +impl SkyboxPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + layouts: &GlobalsLayouts, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "SkyboxPipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Skybox pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&layouts.globals, &layouts.shadow_textures], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Skybox pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Vertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::GreaterEqual, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba16Float, + blend: None, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } +} + +#[rustfmt::skip] +pub fn create_mesh() -> Mesh { let mut mesh = Mesh::new(); // -x - #[rustfmt::skip] mesh.push_quad(Quad::new( Vertex { pos: [-1.0, -1.0, -1.0] }, Vertex { pos: [-1.0, 1.0, -1.0] }, @@ -55,7 +122,6 @@ pub fn create_mesh() -> Mesh { Vertex { pos: [-1.0, -1.0, 1.0] }, )); // +x - #[rustfmt::skip] mesh.push_quad(Quad::new( Vertex { pos: [ 1.0, -1.0, 1.0] }, Vertex { pos: [ 1.0, 1.0, 1.0] }, @@ -63,7 +129,6 @@ pub fn create_mesh() -> Mesh { Vertex { pos: [ 1.0, -1.0, -1.0] }, )); // -y - #[rustfmt::skip] mesh.push_quad(Quad::new( Vertex { pos: [ 1.0, -1.0, -1.0] }, Vertex { pos: [-1.0, -1.0, -1.0] }, @@ -71,7 +136,6 @@ pub fn create_mesh() -> Mesh { Vertex { pos: [ 1.0, -1.0, 1.0] }, )); // +y - #[rustfmt::skip] mesh.push_quad(Quad::new( Vertex { pos: [ 1.0, 1.0, 1.0] }, Vertex { pos: [-1.0, 1.0, 1.0] }, @@ -79,7 +143,6 @@ pub fn create_mesh() -> Mesh { Vertex { pos: [ 1.0, 1.0, -1.0] }, )); // -z - #[rustfmt::skip] mesh.push_quad(Quad::new( Vertex { pos: [-1.0, -1.0, -1.0] }, Vertex { pos: [ 1.0, -1.0, -1.0] }, @@ -87,7 +150,6 @@ pub fn create_mesh() -> Mesh { Vertex { pos: [-1.0, 1.0, -1.0] }, )); // +z - #[rustfmt::skip] mesh.push_quad(Quad::new( Vertex { pos: [-1.0, 1.0, 1.0] }, Vertex { pos: [ 1.0, 1.0, 1.0] }, diff --git a/voxygen/src/render/pipelines/sprite.rs b/voxygen/src/render/pipelines/sprite.rs index 21a8157585..6a98efcd96 100644 --- a/voxygen/src/render/pipelines/sprite.rs +++ b/voxygen/src/render/pipelines/sprite.rs @@ -1,98 +1,49 @@ use super::{ - super::{Pipeline, TgtColorFmt, TgtDepthStencilFmt}, - shadow, terrain, Globals, Light, Shadow, -}; -use core::fmt; -use gfx::{ - self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, - gfx_pipeline_inner, gfx_vertex_struct_meta, state::ColorMask, + super::{ + buffer::Buffer, AaMode, GlobalsLayouts, Mesh, TerrainLayout, Texture, Vertex as VertexTrait, + }, + lod_terrain, GlobalModel, }; +use bytemuck::{Pod, Zeroable}; +use std::mem; use vek::*; -gfx_defines! { - vertex Vertex { - pos: [f32; 3] = "v_pos", - // Because we try to restrict terrain sprite data to a 128×128 block - // we need an offset into the texture atlas. - atlas_pos: u32 = "v_atlas_pos", - // ____BBBBBBBBGGGGGGGGRRRRRRRR - // col: u32 = "v_col", - // ...AANNN - // A = AO - // N = Normal - norm_ao: u32 = "v_norm_ao", - } +pub const VERT_PAGE_SIZE: u32 = 256; - constant Locals { - // Each matrix performs rotatation, translation, and scaling, relative to the sprite - // origin, for all sprite instances. The matrix will be in an array indexed by the - // sprite instance's orientation (0 through 7). - mat: [[f32; 4]; 4] = "mat", - wind_sway: [f32; 4] = "wind_sway", - offs: [f32; 4] = "offs", - } - - vertex/*constant*/ Instance { - // Terrain block position and orientation - pos_ori: u32 = "inst_pos_ori", - inst_mat0: [f32; 4] = "inst_mat0", - inst_mat1: [f32; 4] = "inst_mat1", - inst_mat2: [f32; 4] = "inst_mat2", - inst_mat3: [f32; 4] = "inst_mat3", - inst_light: [f32; 4] = "inst_light", - inst_wind_sway: f32 = "inst_wind_sway", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer = (), - ibuf: gfx::InstanceBuffer = (), - col_lights: gfx::TextureSampler<[f32; 4]> = "t_col_light", - - locals: gfx::ConstantBuffer = "u_locals", - // A sprite instance is a cross between a sprite and a terrain chunk. - terrain_locals: gfx::ConstantBuffer = "u_terrain_locals", - globals: gfx::ConstantBuffer = "u_globals", - lights: gfx::ConstantBuffer = "u_lights", - shadows: gfx::ConstantBuffer = "u_shadows", - - point_shadow_maps: gfx::TextureSampler = "t_point_shadow_maps", - directed_shadow_maps: gfx::TextureSampler = "t_directed_shadow_maps", - - alt: gfx::TextureSampler<[f32; 2]> = "t_alt", - horizon: gfx::TextureSampler<[f32; 4]> = "t_horizon", - - noise: gfx::TextureSampler = "t_noise", - - // Shadow stuff - light_shadows: gfx::ConstantBuffer = "u_light_shadows", - - tgt_color: gfx::BlendTarget = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA), - tgt_depth_stencil: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, - // tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Vertex { + pos_norm: u32, + // Because we try to restrict terrain sprite data to a 128×128 block + // we need an offset into the texture atlas. + atlas_pos: u32, + /* ____BBBBBBBBGGGGGGGGRRRRRRRR + * col: u32 = "v_col", + * .....NNN + * A = AO + * N = Normal + *norm: u32, */ } -impl fmt::Display for Vertex { +// TODO: fix? +/*impl fmt::Display for Vertex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Vertex") - .field("pos", &Vec3::::from(self.pos)) + .field("pos_norm", &Vec3::::from(self.pos)) .field( "atlas_pos", &Vec2::new(self.atlas_pos & 0xFFFF, (self.atlas_pos >> 16) & 0xFFFF), ) - .field("norm_ao", &self.norm_ao) .finish() } -} +}*/ impl Vertex { // NOTE: Limit to 16 (x) × 16 (y) × 32 (z). #[allow(clippy::collapsible_else_if)] - pub fn new( - atlas_pos: Vec2, - pos: Vec3, - norm: Vec3, /* , col: Rgb, ao: f32 */ - ) -> Self { + pub fn new(atlas_pos: Vec2, pos: Vec3, norm: Vec3) -> Self { + const VERT_EXTRA_NEG_Z: i32 = 128; // NOTE: change if number of bits changes below, also we might not need this if meshing always produces positives values for sprites (I have no idea) + let norm_bits = if norm.x != 0.0 { if norm.x < 0.0 { 0 } else { 1 } } else if norm.y != 0.0 { @@ -107,60 +58,287 @@ impl Vertex { // | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12 // | if meta { 1 } else { 0 } << 28 // | (norm_bits & 0x7) << 29, - pos: pos.into_array(), + pos_norm: ((pos.x as u32) & 0x00FF) // NOTE: temp hack, this doesn't need 8 bits + | ((pos.y as u32) & 0x00FF) << 8 + | (((pos.z as i32 + VERT_EXTRA_NEG_Z).max(0).min(1 << 12) as u32) & 0x0FFF) << 16 + | (norm_bits & 0x7) << 29, atlas_pos: ((atlas_pos.x as u32) & 0xFFFF) | ((atlas_pos.y as u32) & 0xFFFF) << 16, - norm_ao: norm_bits, } } } +impl Default for Vertex { + fn default() -> Self { Self::new(Vec2::zero(), Vec3::zero(), Vec3::zero()) } +} + +impl VertexTrait for Vertex { + const QUADS_INDEX: Option = Some(wgpu::IndexFormat::Uint16); + const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; +} + +pub struct SpriteVerts(Buffer); +//pub struct SpriteVerts(Texture); + +pub(in super::super) fn create_verts_buffer( + device: &wgpu::Device, + mesh: Mesh, +) -> SpriteVerts { + // TODO: type Buffer by wgpu::BufferUsage + SpriteVerts(Buffer::new( + &device, + wgpu::BufferUsage::STORAGE, + mesh.vertices(), + )) +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Instance { + inst_mat0: [f32; 4], + inst_mat1: [f32; 4], + inst_mat2: [f32; 4], + inst_mat3: [f32; 4], + pos_ori: u32, + inst_vert_page: u32, + inst_light: f32, + inst_glow: f32, + model_wind_sway: f32, + model_z_scale: f32, +} + impl Instance { pub fn new( mat: Mat4, wind_sway: f32, + z_scale: f32, pos: Vec3, ori_bits: u8, light: f32, glow: f32, + vert_page: u32, ) -> Self { const EXTRA_NEG_Z: i32 = 32768; let mat_arr = mat.into_col_arrays(); Self { - pos_ori: ((pos.x as u32) & 0x003F) - | ((pos.y as u32) & 0x003F) << 6 - | (((pos + EXTRA_NEG_Z).z.max(0).min(1 << 16) as u32) & 0xFFFF) << 12 - | (u32::from(ori_bits) & 0x7) << 29, inst_mat0: mat_arr[0], inst_mat1: mat_arr[1], inst_mat2: mat_arr[2], inst_mat3: mat_arr[3], - inst_light: [light, glow, 1.0, 1.0], - inst_wind_sway: wind_sway, + pos_ori: ((pos.x as u32) & 0x003F) + | ((pos.y as u32) & 0x003F) << 6 + | (((pos.z + EXTRA_NEG_Z).max(0).min(1 << 16) as u32) & 0xFFFF) << 12 + | (u32::from(ori_bits) & 0x7) << 29, + inst_vert_page: vert_page, + inst_light: light, + inst_glow: glow, + model_wind_sway: wind_sway, + model_z_scale: z_scale, + } + } + + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + const ATTRIBUTES: [wgpu::VertexAttribute; 10] = wgpu::vertex_attr_array![ + 0 => Float32x4, + 1 => Float32x4, + 2 => Float32x4, + 3 => Float32x4, + 4 => Uint32, + 5 => Uint32, + 6 => Float32, + 7 => Float32, + 8 => Float32, + 9 => Float32, + ]; + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Instance, + attributes: &ATTRIBUTES, } } } impl Default for Instance { - fn default() -> Self { Self::new(Mat4::identity(), 0.0, Vec3::zero(), 0, 1.0, 0.0) } + fn default() -> Self { Self::new(Mat4::identity(), 0.0, 0.0, Vec3::zero(), 0, 1.0, 0.0, 0) } } -impl Default for Locals { - fn default() -> Self { Self::new(Mat4::identity(), Vec3::one(), Vec3::zero(), 0.0) } +// TODO: ColLightsWrapper instead? +pub struct Locals; + +pub struct SpriteGlobalsBindGroup { + pub(in super::super) bind_group: wgpu::BindGroup, } -impl Locals { - pub fn new(mat: Mat4, scale: Vec3, offs: Vec3, wind_sway: f32) -> Self { +pub struct SpriteLayout { + pub globals: wgpu::BindGroupLayout, +} + +impl SpriteLayout { + pub fn new(device: &wgpu::Device) -> Self { + let mut entries = GlobalsLayouts::base_globals_layout(); + debug_assert_eq!(12, entries.len()); // To remember to adjust the bindings below + entries.extend_from_slice(&[ + // sprite_verts + wgpu::BindGroupLayoutEntry { + binding: 12, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: core::num::NonZeroU64::new( + core::mem::size_of::() as u64 + ), + }, + count: None, + }, + ]); + Self { - mat: mat.into_col_arrays(), - wind_sway: [scale.x, scale.y, scale.z, wind_sway], - offs: [offs.x, offs.y, offs.z, 0.0], + globals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &entries, + }), } } + + fn bind_globals_inner( + &self, + device: &wgpu::Device, + global_model: &GlobalModel, + lod_data: &lod_terrain::LodData, + noise: &Texture, + sprite_verts: &SpriteVerts, + ) -> wgpu::BindGroup { + let mut entries = GlobalsLayouts::bind_base_globals(global_model, lod_data, noise); + + entries.extend_from_slice(&[ + // sprite_verts + wgpu::BindGroupEntry { + binding: 12, + resource: sprite_verts.0.buf.as_entire_binding(), + }, + ]); + + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.globals, + entries: &entries, + }) + } + + pub fn bind_globals( + &self, + device: &wgpu::Device, + global_model: &GlobalModel, + lod_data: &lod_terrain::LodData, + noise: &Texture, + sprite_verts: &SpriteVerts, + ) -> SpriteGlobalsBindGroup { + let bind_group = + self.bind_globals_inner(device, global_model, lod_data, noise, sprite_verts); + + SpriteGlobalsBindGroup { bind_group } + } } -pub struct SpritePipeline; - -impl Pipeline for SpritePipeline { - type Vertex = Vertex; +pub struct SpritePipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl SpritePipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + layout: &SpriteLayout, + terrain_layout: &TerrainLayout, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "SpritePipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Sprite pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[ + &layout.globals, + &global_layout.shadow_textures, + &terrain_layout.locals, + // Note: mergable with globals + &global_layout.col_light, + ], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Sprite pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Instance::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::GreaterEqual, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba16Float, + // TODO: can we remove sprite transparency? + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } } diff --git a/voxygen/src/render/pipelines/terrain.rs b/voxygen/src/render/pipelines/terrain.rs index 0395f03f1f..8a379f589d 100644 --- a/voxygen/src/render/pipelines/terrain.rs +++ b/voxygen/src/render/pipelines/terrain.rs @@ -1,49 +1,13 @@ -use super::{ - super::{ColLightFmt, Pipeline, TgtColorFmt, TgtDepthStencilFmt}, - shadow, Globals, Light, Shadow, -}; -use gfx::{ - self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, - gfx_pipeline_inner, gfx_vertex_struct_meta, -}; +use super::super::{AaMode, Bound, Consts, GlobalsLayouts, Vertex as VertexTrait}; +use bytemuck::{Pod, Zeroable}; +use std::mem; use vek::*; -gfx_defines! { - vertex Vertex { - pos_norm: u32 = "v_pos_norm", - atlas_pos: u32 = "v_atlas_pos", - } - - constant Locals { - model_offs: [f32; 3] = "model_offs", - load_time: f32 = "load_time", - atlas_offs: [i32; 4] = "atlas_offs", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer = (), - col_lights: gfx::TextureSampler<[f32; 4]> = "t_col_light", - - locals: gfx::ConstantBuffer = "u_locals", - globals: gfx::ConstantBuffer = "u_globals", - lights: gfx::ConstantBuffer = "u_lights", - shadows: gfx::ConstantBuffer = "u_shadows", - - point_shadow_maps: gfx::TextureSampler = "t_point_shadow_maps", - directed_shadow_maps: gfx::TextureSampler = "t_directed_shadow_maps", - - alt: gfx::TextureSampler<[f32; 2]> = "t_alt", - horizon: gfx::TextureSampler<[f32; 4]> = "t_horizon", - - noise: gfx::TextureSampler = "t_noise", - - // Shadow stuff - light_shadows: gfx::ConstantBuffer = "u_light_shadows", - - tgt_color: gfx::RenderTarget = "tgt_color", - tgt_depth_stencil: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, - // tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Vertex { + pos_norm: u32, + atlas_pos: u32, } impl Vertex { @@ -104,8 +68,7 @@ impl Vertex { // 0 to 31 glow: u8, col: Rgb, - ) -> <::Surface as gfx::format::SurfaceTyped>::DataType - { + ) -> [u8; 4] { //[col.r, col.g, col.b, light] // It would be nice for this to be cleaner, but we want to squeeze 5 fields into // 4. We can do this because both `light` and `glow` go from 0 to 31, @@ -141,8 +104,7 @@ impl Vertex { glowy: bool, shiny: bool, col: Rgb, - ) -> <::Surface as gfx::format::SurfaceTyped>::DataType - { + ) -> [u8; 4] { let attr = 0 | ((glowy as u8) << 0) | ((shiny as u8) << 1); [ (light.min(31) << 3) | ((col.r >> 1) & 0b111), @@ -151,9 +113,48 @@ impl Vertex { col.g, // Green is lucky, it remains unscathed ] } + + /// Set the bone_idx for an existing figure vertex. + pub fn set_bone_idx(&mut self, bone_idx: u8) { + self.pos_norm = (self.pos_norm & !(0xF << 27)) | ((bone_idx as u32 & 0xF) << 27); + } + + pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + const ATTRIBUTES: [wgpu::VertexAttribute; 2] = + wgpu::vertex_attr_array![0 => Uint32,1 => Uint32]; + wgpu::VertexBufferLayout { + array_stride: Self::STRIDE, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &ATTRIBUTES, + } + } +} + +impl VertexTrait for Vertex { + // Note: I think it's u32 due to figures?? + // potentiall optimize by splitting + const QUADS_INDEX: Option = Some(wgpu::IndexFormat::Uint32); + const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +// TODO: new function and private fields?? +pub struct Locals { + model_offs: [f32; 3], + load_time: f32, + atlas_offs: [i32; 4], } impl Locals { + pub fn new(model_offs: Vec3, atlas_offs: Vec2, load_time: f32) -> Self { + Self { + model_offs: model_offs.into_array(), + load_time, + atlas_offs: Vec4::new(atlas_offs.x as i32, atlas_offs.y as i32, 0, 0).into_array(), + } + } + pub fn default() -> Self { Self { model_offs: [0.0; 3], @@ -163,8 +164,135 @@ impl Locals { } } -pub struct TerrainPipeline; +pub type BoundLocals = Bound>; -impl Pipeline for TerrainPipeline { - type Vertex = Vertex; +pub struct TerrainLayout { + pub locals: wgpu::BindGroupLayout, +} + +impl TerrainLayout { + pub fn new(device: &wgpu::Device) -> Self { + Self { + locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // locals + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }), + } + } + + pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts) -> BoundLocals { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.locals, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: locals.buf().as_entire_binding(), + }], + }); + + BoundLocals { + bind_group, + with: locals, + } + } +} + +pub struct TerrainPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl TerrainPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + layout: &TerrainLayout, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "TerrainPipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Terrain pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[ + &global_layout.globals, + &global_layout.shadow_textures, + &layout.locals, + &global_layout.col_light, + ], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Terrain pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Vertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::GreaterEqual, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba16Float, + blend: None, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } } diff --git a/voxygen/src/render/pipelines/ui.rs b/voxygen/src/render/pipelines/ui.rs index 8a39625c6e..26e7e21de1 100644 --- a/voxygen/src/render/pipelines/ui.rs +++ b/voxygen/src/render/pipelines/ui.rs @@ -1,41 +1,38 @@ -use super::super::{Globals, Pipeline, Quad, Tri, WinColorFmt, WinDepthFmt}; -use gfx::{ - self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, - gfx_pipeline_inner, gfx_vertex_struct_meta, -}; +use super::super::{Bound, Consts, GlobalsLayouts, Quad, Texture, Tri, Vertex as VertexTrait}; +use bytemuck::{Pod, Zeroable}; +use std::mem; use vek::*; -gfx_defines! { - vertex Vertex { - pos: [f32; 2] = "v_pos", - uv: [f32; 2] = "v_uv", - color: [f32; 4] = "v_color", - center: [f32; 2] = "v_center", - mode: u32 = "v_mode", - } +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Vertex { + pos: [f32; 2], + uv: [f32; 2], + color: [f32; 4], + center: [f32; 2], + mode: u32, +} - constant Locals { - pos: [f32; 4] = "w_pos", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer = (), - - locals: gfx::ConstantBuffer = "u_locals", - globals: gfx::ConstantBuffer = "u_globals", - tex: gfx::TextureSampler<[f32; 4]> = "u_tex", - - scissor: gfx::Scissor = (), - - tgt_color: gfx::BlendTarget = ("tgt_color", gfx::state::ColorMask::all(), gfx::preset::blend::ALPHA), - tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_TEST, +impl Vertex { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + const ATTRIBUTES: [wgpu::VertexAttribute; 5] = wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Float32x4, 3 => Float32x2, 4 => Uint32]; + wgpu::VertexBufferLayout { + array_stride: Self::STRIDE, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &ATTRIBUTES, + } } } -pub struct UiPipeline; +impl VertexTrait for Vertex { + const QUADS_INDEX: Option = None; + const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; +} -impl Pipeline for UiPipeline { - type Vertex = Vertex; +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Locals { + pos: [f32; 4], } impl From> for Locals { @@ -87,12 +84,177 @@ impl Mode { } } +pub type BoundLocals = Bound>; + +pub struct TextureBindGroup { + pub(in super::super) bind_group: wgpu::BindGroup, +} + +pub struct UiLayout { + pub locals: wgpu::BindGroupLayout, + pub texture: wgpu::BindGroupLayout, +} + +impl UiLayout { + pub fn new(device: &wgpu::Device) -> Self { + Self { + locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // locals + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }), + texture: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // texture + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + ], + }), + } + } + + pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts) -> BoundLocals { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.locals, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: locals.buf().as_entire_binding(), + }], + }); + + BoundLocals { + bind_group, + with: locals, + } + } + + pub fn bind_texture(&self, device: &wgpu::Device, texture: &Texture) -> TextureBindGroup { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.texture, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&texture.sampler), + }, + ], + }); + + TextureBindGroup { bind_group } + } +} + +pub struct UiPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl UiPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + sc_desc: &wgpu::SwapChainDescriptor, + global_layout: &GlobalsLayouts, + layout: &UiLayout, + ) -> Self { + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Ui pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&global_layout.globals, &layout.locals, &layout.texture], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("UI pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Vertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: sc_desc.format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } +} + pub fn create_quad( rect: Aabr, uv_rect: Aabr, color: Rgba, mode: Mode, -) -> Quad { +) -> Quad { create_quad_vert_gradient(rect, uv_rect, color, color, mode) } @@ -103,7 +265,7 @@ pub fn create_quad_vert_gradient( top_color: Rgba, bottom_color: Rgba, mode: Mode, -) -> Quad { +) -> Quad { let top_color = top_color.into_array(); let bottom_color = bottom_color.into_array(); @@ -152,7 +314,7 @@ pub fn create_tri( uv_tri: [[f32; 2]; 3], color: Rgba, mode: Mode, -) -> Tri { +) -> Tri { let center = [0.0, 0.0]; let mode_val = mode.value(); let v = |pos, uv| Vertex { diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index d83048baa7..2a81c4d2ee 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -1,241 +1,98 @@ +mod binding; +pub(super) mod drawer; +// Consts and bind groups for post-process and clouds +mod locals; +mod pipeline_creation; +mod screenshot; +mod shaders; +mod shadow_map; + +use locals::Locals; +use pipeline_creation::{ + IngameAndShadowPipelines, InterfacePipelines, PipelineCreation, Pipelines, ShadowPipelines, +}; +use shaders::Shaders; +use shadow_map::{ShadowMap, ShadowMapRenderer}; + use super::{ + buffer::Buffer, consts::Consts, - gfx_backend, instances::Instances, mesh::Mesh, model::{DynamicModel, Model}, pipelines::{ - clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite, terrain, - ui, GlobalModel, Globals, + blit, clouds, debug, figure, postprocess, shadow, sprite, terrain, ui, GlobalsBindGroup, + GlobalsLayouts, ShadowTexturesBindGroup, }, texture::Texture, - AaMode, CloudMode, FilterMethod, FluidMode, LightingMode, Pipeline, RenderError, RenderMode, - ShadowMapMode, ShadowMode, WrapMode, + AaMode, AddressMode, FilterMode, RenderError, RenderMode, ShadowMapMode, ShadowMode, Vertex, }; use common::assets::{self, AssetExt, AssetHandle}; use common_base::span; use core::convert::TryFrom; -use gfx::{ - self, - handle::Sampler, - state::Comparison, - traits::{Device, Factory, FactoryExt}, -}; -use glsl_include::Context as IncludeContext; -use tracing::{error, warn}; +use std::sync::Arc; +use tracing::{error, info, warn}; use vek::*; -/// Represents the format of the pre-processed color target. -// TODO: `(gfx::format::R11_G11_B10, gfx::format::Float)` would be better in -// theory, but it doesn't seem to work -pub type TgtColorFmt = gfx::format::Rgba16F; -/// Represents the format of the pre-processed depth and stencil target. -pub type TgtDepthStencilFmt = gfx::format::Depth; - -/// Represents the format of the window's color target. -pub type WinColorFmt = gfx::format::Srgba8; -/// Represents the format of the window's depth target. -pub type WinDepthFmt = gfx::format::Depth; - -/// Represents the format of the pre-processed shadow depth target. -pub type ShadowDepthStencilFmt = gfx::format::Depth; - -/// A handle to a pre-processed color target. -pub type TgtColorView = gfx::handle::RenderTargetView; -/// A handle to a pre-processed depth target. -pub type TgtDepthStencilView = - gfx::handle::DepthStencilView; - -/// A handle to a window color target. -pub type WinColorView = gfx::handle::RenderTargetView; -/// A handle to a window depth target. -pub type WinDepthView = gfx::handle::DepthStencilView; - -/// Represents the format of LOD shadows. -pub type LodTextureFmt = (gfx::format::R8_G8_B8_A8, gfx::format::Unorm); - -/// Represents the format of LOD altitudes. -pub type LodAltFmt = (gfx::format::R16_G16, gfx::format::Unorm); - -/// Represents the format of LOD map colors. -pub type LodColorFmt = (gfx::format::R8_G8_B8_A8, gfx::format::Srgb); - -/// Represents the format of greedy meshed color-light textures. -pub type ColLightFmt = (gfx::format::R8_G8_B8_A8, gfx::format::Unorm); - -/// A handle to a shadow depth target. -pub type ShadowDepthStencilView = - gfx::handle::DepthStencilView; -/// A handle to a shadow depth target as a resource. -pub type ShadowResourceView = gfx::handle::ShaderResourceView< - gfx_backend::Resources, - ::View, ->; - -/// A handle to a render color target as a resource. -pub type TgtColorRes = gfx::handle::ShaderResourceView< - gfx_backend::Resources, - ::View, ->; - -/// A handle to a render depth target as a resource. -pub type TgtDepthRes = gfx::handle::ShaderResourceView< - gfx_backend::Resources, - ::View, ->; - -/// A handle to a greedy meshed color-light texture as a resource. -pub type ColLightRes = gfx::handle::ShaderResourceView< - gfx_backend::Resources, - ::View, ->; +// TODO: yeet this somewhere else /// A type representing data that can be converted to an immutable texture map /// of ColLight data (used for texture atlases created during greedy meshing). -pub type ColLightInfo = ( - Vec<<::Surface as gfx::format::SurfaceTyped>::DataType>, - Vec2, -); +// TODO: revert to u16 +pub type ColLightInfo = (Vec<[u8; 4]>, Vec2); -/// Load from a GLSL file. -pub struct Glsl(String); +const QUAD_INDEX_BUFFER_U16_START_VERT_LEN: u16 = 3000; +const QUAD_INDEX_BUFFER_U32_START_VERT_LEN: u32 = 3000; -impl From for Glsl { - fn from(s: String) -> Glsl { Glsl(s) } +/// A type that stores all the layouts associated with this renderer. +struct Layouts { + global: GlobalsLayouts, + + clouds: clouds::CloudsLayout, + debug: debug::DebugLayout, + figure: figure::FigureLayout, + postprocess: postprocess::PostProcessLayout, + shadow: shadow::ShadowLayout, + sprite: sprite::SpriteLayout, + terrain: terrain::TerrainLayout, + ui: ui::UiLayout, + blit: blit::BlitLayout, } -impl assets::Asset for Glsl { - type Loader = assets::LoadFrom; +/// Render target views +struct Views { + // NOTE: unused for now, maybe... we will want it for something + _win_depth: wgpu::TextureView, - const EXTENSION: &'static str = "glsl"; + tgt_color: wgpu::TextureView, + tgt_depth: wgpu::TextureView, + // TODO: rename + tgt_color_pp: wgpu::TextureView, } -struct Shaders { - constants: AssetHandle, - globals: AssetHandle, - sky: AssetHandle, - light: AssetHandle, - srgb: AssetHandle, - random: AssetHandle, - lod: AssetHandle, - shadows: AssetHandle, - - anti_alias_none: AssetHandle, - anti_alias_fxaa: AssetHandle, - anti_alias_msaa_x4: AssetHandle, - anti_alias_msaa_x8: AssetHandle, - anti_alias_msaa_x16: AssetHandle, - cloud_none: AssetHandle, - cloud_regular: AssetHandle, - figure_vert: AssetHandle, - - terrain_point_shadow_vert: AssetHandle, - terrain_directed_shadow_vert: AssetHandle, - figure_directed_shadow_vert: AssetHandle, - directed_shadow_frag: AssetHandle, - - skybox_vert: AssetHandle, - skybox_frag: AssetHandle, - figure_frag: AssetHandle, - terrain_vert: AssetHandle, - terrain_frag: AssetHandle, - fluid_vert: AssetHandle, - fluid_frag_cheap: AssetHandle, - fluid_frag_shiny: AssetHandle, - sprite_vert: AssetHandle, - sprite_frag: AssetHandle, - particle_vert: AssetHandle, - particle_frag: AssetHandle, - ui_vert: AssetHandle, - ui_frag: AssetHandle, - lod_terrain_vert: AssetHandle, - lod_terrain_frag: AssetHandle, - clouds_vert: AssetHandle, - clouds_frag: AssetHandle, - postprocess_vert: AssetHandle, - postprocess_frag: AssetHandle, - player_shadow_frag: AssetHandle, - light_shadows_geom: AssetHandle, - light_shadows_frag: AssetHandle, +/// Shadow rendering textures, layouts, pipelines, and bind groups +struct Shadow { + map: ShadowMap, + bind: ShadowTexturesBindGroup, } -impl assets::Compound for Shaders { - // TODO: Taking the specifier argument as a base for shaders specifiers - // would allow to use several shaders groups easily - fn load( - _: &assets::AssetCache, - _: &str, - ) -> Result { - Ok(Shaders { - constants: AssetExt::load("voxygen.shaders.include.constants")?, - globals: AssetExt::load("voxygen.shaders.include.globals")?, - sky: AssetExt::load("voxygen.shaders.include.sky")?, - light: AssetExt::load("voxygen.shaders.include.light")?, - srgb: AssetExt::load("voxygen.shaders.include.srgb")?, - random: AssetExt::load("voxygen.shaders.include.random")?, - lod: AssetExt::load("voxygen.shaders.include.lod")?, - shadows: AssetExt::load("voxygen.shaders.include.shadows")?, - - anti_alias_none: AssetExt::load("voxygen.shaders.antialias.none")?, - anti_alias_fxaa: AssetExt::load("voxygen.shaders.antialias.fxaa")?, - anti_alias_msaa_x4: AssetExt::load("voxygen.shaders.antialias.msaa-x4")?, - anti_alias_msaa_x8: AssetExt::load("voxygen.shaders.antialias.msaa-x8")?, - anti_alias_msaa_x16: AssetExt::load("voxygen.shaders.antialias.msaa-x16")?, - cloud_none: AssetExt::load("voxygen.shaders.include.cloud.none")?, - cloud_regular: AssetExt::load("voxygen.shaders.include.cloud.regular")?, - figure_vert: AssetExt::load("voxygen.shaders.figure-vert")?, - - terrain_point_shadow_vert: AssetExt::load("voxygen.shaders.light-shadows-vert")?, - terrain_directed_shadow_vert: AssetExt::load( - "voxygen.shaders.light-shadows-directed-vert", - )?, - figure_directed_shadow_vert: AssetExt::load( - "voxygen.shaders.light-shadows-figure-vert", - )?, - directed_shadow_frag: AssetExt::load("voxygen.shaders.light-shadows-directed-frag")?, - - skybox_vert: AssetExt::load("voxygen.shaders.skybox-vert")?, - skybox_frag: AssetExt::load("voxygen.shaders.skybox-frag")?, - figure_frag: AssetExt::load("voxygen.shaders.figure-frag")?, - terrain_vert: AssetExt::load("voxygen.shaders.terrain-vert")?, - terrain_frag: AssetExt::load("voxygen.shaders.terrain-frag")?, - fluid_vert: AssetExt::load("voxygen.shaders.fluid-vert")?, - fluid_frag_cheap: AssetExt::load("voxygen.shaders.fluid-frag.cheap")?, - fluid_frag_shiny: AssetExt::load("voxygen.shaders.fluid-frag.shiny")?, - sprite_vert: AssetExt::load("voxygen.shaders.sprite-vert")?, - sprite_frag: AssetExt::load("voxygen.shaders.sprite-frag")?, - particle_vert: AssetExt::load("voxygen.shaders.particle-vert")?, - particle_frag: AssetExt::load("voxygen.shaders.particle-frag")?, - ui_vert: AssetExt::load("voxygen.shaders.ui-vert")?, - ui_frag: AssetExt::load("voxygen.shaders.ui-frag")?, - lod_terrain_vert: AssetExt::load("voxygen.shaders.lod-terrain-vert")?, - lod_terrain_frag: AssetExt::load("voxygen.shaders.lod-terrain-frag")?, - clouds_vert: AssetExt::load("voxygen.shaders.clouds-vert")?, - clouds_frag: AssetExt::load("voxygen.shaders.clouds-frag")?, - postprocess_vert: AssetExt::load("voxygen.shaders.postprocess-vert")?, - postprocess_frag: AssetExt::load("voxygen.shaders.postprocess-frag")?, - player_shadow_frag: AssetExt::load("voxygen.shaders.player-shadow-frag")?, - light_shadows_geom: AssetExt::load("voxygen.shaders.light-shadows-geom")?, - light_shadows_frag: AssetExt::load("voxygen.shaders.light-shadows-frag")?, - }) - } -} - -/// A type that holds shadow map data. Since shadow mapping may not be -/// supported on all platforms, we try to keep it separate. -pub struct ShadowMapRenderer { - // directed_encoder: gfx::Encoder, - // point_encoder: gfx::Encoder, - directed_depth_stencil_view: ShadowDepthStencilView, - directed_res: ShadowResourceView, - directed_sampler: Sampler, - - point_depth_stencil_view: ShadowDepthStencilView, - point_res: ShadowResourceView, - point_sampler: Sampler, - - point_pipeline: GfxPipeline>, - terrain_directed_pipeline: GfxPipeline>, - figure_directed_pipeline: GfxPipeline>, +/// Represent two states of the renderer: +/// 1. Only interface pipelines created +/// 2. All of the pipelines have been created +#[allow(clippy::large_enum_variant)] // They are both pretty large +enum State { + // NOTE: this is used as a transient placeholder for moving things out of State temporarily + Nothing, + Interface { + pipelines: InterfacePipelines, + shadow_views: Option<(Texture, Texture)>, + // In progress creation of the remaining pipelines in the background + creating: PipelineCreation, + }, + Complete { + pipelines: Pipelines, + shadow: Shadow, + recreating: Option>>, + }, } /// A type that encapsulates rendering state. `Renderer` is central to Voxygen's @@ -243,67 +100,156 @@ pub struct ShadowMapRenderer { /// GPU, along with pipeline state objects (PSOs) needed to renderer different /// kinds of models to the screen. pub struct Renderer { - device: gfx_backend::Device, - encoder: gfx::Encoder, - factory: gfx_backend::Factory, + device: Arc, + queue: wgpu::Queue, + surface: wgpu::Surface, + swap_chain: wgpu::SwapChain, + sc_desc: wgpu::SwapChainDescriptor, - win_color_view: WinColorView, - win_depth_view: WinDepthView, + sampler: wgpu::Sampler, + depth_sampler: wgpu::Sampler, - tgt_color_view: TgtColorView, - tgt_depth_stencil_view: TgtDepthStencilView, - tgt_color_view_pp: TgtColorView, + state: State, + // true if there is a pending need to recreate the pipelines (e.g. RenderMode change or shader + // hotloading) + recreation_pending: bool, - tgt_color_res: TgtColorRes, - tgt_depth_res: TgtDepthRes, - tgt_color_res_pp: TgtColorRes, + layouts: Arc, + // Note: we keep these here since their bind groups need to be updated if we resize the + // color/depth textures + locals: Locals, + views: Views, + noise_tex: Texture, - sampler: Sampler, - - shadow_map: Option, - - skybox_pipeline: GfxPipeline>, - figure_pipeline: GfxPipeline>, - terrain_pipeline: GfxPipeline>, - fluid_pipeline: GfxPipeline>, - sprite_pipeline: GfxPipeline>, - particle_pipeline: GfxPipeline>, - ui_pipeline: GfxPipeline>, - lod_terrain_pipeline: GfxPipeline>, - clouds_pipeline: GfxPipeline>, - postprocess_pipeline: GfxPipeline>, - #[allow(dead_code)] //TODO: remove ? - player_shadow_pipeline: GfxPipeline>, + quad_index_buffer_u16: Buffer, + quad_index_buffer_u32: Buffer, shaders: AssetHandle, - noise_tex: Texture<(gfx::format::R8, gfx::format::Unorm)>, - mode: RenderMode, + resolution: Vec2, + + // If this is Some then a screenshot will be taken and passed to the handler here + take_screenshot: Option, + + profiler: wgpu_profiler::GpuProfiler, + profile_times: Vec, + profiler_features_enabled: bool, } impl Renderer { /// Create a new `Renderer` from a variety of backend-specific components /// and the window targets. - pub fn new( - mut device: gfx_backend::Device, - mut factory: gfx_backend::Factory, - win_color_view: WinColorView, - win_depth_view: WinDepthView, - mode: RenderMode, - ) -> Result { + pub fn new(window: &winit::window::Window, mut mode: RenderMode) -> Result { // Enable seamless cubemaps globally, where available--they are essentially a // strict improvement on regular cube maps. // // Note that since we only have to enable this once globally, there is no point // in doing this on rerender. - Self::enable_seamless_cube_maps(&mut device); + // Self::enable_seamless_cube_maps(&mut device); - let dims = win_color_view.get_dimensions(); + // TODO: fix panic on wayland with opengl? + // TODO: fix backend defaulting to opengl on wayland. + let backend_bit = std::env::var("WGPU_BACKEND") + .ok() + .and_then(|backend| match backend.to_lowercase().as_str() { + "vulkan" => Some(wgpu::BackendBit::VULKAN), + "metal" => Some(wgpu::BackendBit::METAL), + "dx12" => Some(wgpu::BackendBit::DX12), + "primary" => Some(wgpu::BackendBit::PRIMARY), + "opengl" | "gl" => Some(wgpu::BackendBit::GL), + "dx11" => Some(wgpu::BackendBit::DX11), + "secondary" => Some(wgpu::BackendBit::SECONDARY), + "all" => Some(wgpu::BackendBit::all()), + _ => None, + }) + .unwrap_or( + (wgpu::BackendBit::PRIMARY | wgpu::BackendBit::SECONDARY) & !wgpu::BackendBit::GL, + ); - let shadow_views = Self::create_shadow_views( - &mut factory, - (dims.0, dims.1), + let instance = wgpu::Instance::new(backend_bit); + + let dims = window.inner_size(); + + // This is unsafe because the window handle must be valid, if you find a way to + // have an invalid winit::Window then you have bigger issues + #[allow(unsafe_code)] + let surface = unsafe { instance.create_surface(window) }; + + let adapter = futures_executor::block_on(instance.request_adapter( + &wgpu::RequestAdapterOptionsBase { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + }, + )) + .ok_or(RenderError::CouldNotFindAdapter)?; + + let limits = wgpu::Limits { + max_push_constant_size: 64, + ..Default::default() + }; + + let (device, queue) = futures_executor::block_on( + adapter.request_device( + &wgpu::DeviceDescriptor { + // TODO + label: None, + features: wgpu::Features::DEPTH_CLAMPING + | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER + | wgpu::Features::PUSH_CONSTANTS + | (adapter.features() & wgpu_profiler::GpuProfiler::REQUIRED_WGPU_FEATURES), + limits, + }, + std::env::var_os("WGPU_TRACE_DIR") + .as_ref() + .map(|v| std::path::Path::new(v)), + ), + )?; + + // Set error handler for wgpu errors + // This is better for use than their default because it includes the error in + // the panic message + device.on_uncaptured_error(|error| { + error!("{}", &error); + panic!( + "wgpu error (handling all wgpu errors as fatal): {:?}", + &error, + ) + }); + + let profiler_features_enabled = device + .features() + .contains(wgpu_profiler::GpuProfiler::REQUIRED_WGPU_FEATURES); + if !profiler_features_enabled { + info!( + "The features for GPU profiling (timestamp queries) are not available on this \ + adapter" + ); + } + + let info = adapter.get_info(); + info!( + ?info.name, + ?info.vendor, + ?info.backend, + ?info.device, + ?info.device_type, + "selected graphics device" + ); + + let sc_desc = wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width: dims.width, + height: dims.height, + present_mode: mode.present_mode.into(), + }; + + let swap_chain = device.create_swap_chain(&surface, &sc_desc); + + let shadow_views = ShadowMap::create_shadow_views( + &device, + (dims.width, dims.height), &ShadowMapMode::try_from(mode.shadow).unwrap_or_default(), ) .map_err(|err| { @@ -313,157 +259,187 @@ impl Renderer { let shaders = Shaders::load_expect(""); - let ( - skybox_pipeline, - figure_pipeline, - terrain_pipeline, - fluid_pipeline, - sprite_pipeline, - particle_pipeline, - ui_pipeline, - lod_terrain_pipeline, - clouds_pipeline, - postprocess_pipeline, - player_shadow_pipeline, - point_shadow_pipeline, - terrain_directed_shadow_pipeline, - figure_directed_shadow_pipeline, - ) = create_pipelines(&mut factory, &shaders.read(), &mode, shadow_views.is_some())?; + let layouts = { + let global = GlobalsLayouts::new(&device); - let ( - tgt_color_view, - tgt_depth_stencil_view, - tgt_color_view_pp, - tgt_color_res, - tgt_depth_res, - tgt_color_res_pp, - ) = Self::create_rt_views(&mut factory, (dims.0, dims.1), &mode)?; + let clouds = clouds::CloudsLayout::new(&device); + let debug = debug::DebugLayout::new(&device); + let figure = figure::FigureLayout::new(&device); + let postprocess = postprocess::PostProcessLayout::new(&device); + let shadow = shadow::ShadowLayout::new(&device); + let sprite = sprite::SpriteLayout::new(&device); + let terrain = terrain::TerrainLayout::new(&device); + let ui = ui::UiLayout::new(&device); + let blit = blit::BlitLayout::new(&device); - let shadow_map = if let ( - Some(point_pipeline), - Some(terrain_directed_pipeline), - Some(figure_directed_pipeline), - Some(shadow_views), - ) = ( - point_shadow_pipeline, - terrain_directed_shadow_pipeline, - figure_directed_shadow_pipeline, - shadow_views, - ) { - let ( - point_depth_stencil_view, - point_res, - point_sampler, - directed_depth_stencil_view, - directed_res, - directed_sampler, - ) = shadow_views; - Some(ShadowMapRenderer { - directed_depth_stencil_view, - directed_res, - directed_sampler, + Layouts { + global, - // point_encoder: factory.create_command_buffer().into(), - // directed_encoder: factory.create_command_buffer().into(), - point_depth_stencil_view, - point_res, - point_sampler, - - point_pipeline, - terrain_directed_pipeline, - figure_directed_pipeline, - }) - } else { - None + clouds, + debug, + figure, + postprocess, + shadow, + sprite, + terrain, + ui, + blit, + } }; - let sampler = factory.create_sampler(gfx::texture::SamplerInfo::new( - gfx::texture::FilterMethod::Bilinear, - gfx::texture::WrapMode::Clamp, - )); + // Arcify the device and layouts + let device = Arc::new(device); + let layouts = Arc::new(layouts); + + let (interface_pipelines, creating) = pipeline_creation::initial_create_pipelines( + // TODO: combine Arcs? + Arc::clone(&device), + Arc::clone(&layouts), + shaders.read().clone(), + mode.clone(), + sc_desc.clone(), // Note: cheap clone + shadow_views.is_some(), + )?; + + let state = State::Interface { + pipelines: interface_pipelines, + shadow_views, + creating, + }; + + let views = Self::create_rt_views(&device, (dims.width, dims.height), &mode)?; + + let create_sampler = |filter| { + device.create_sampler(&wgpu::SamplerDescriptor { + label: None, + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: filter, + min_filter: filter, + mipmap_filter: wgpu::FilterMode::Nearest, + compare: None, + ..Default::default() + }) + }; + + let sampler = create_sampler(wgpu::FilterMode::Linear); + let depth_sampler = create_sampler(wgpu::FilterMode::Nearest); let noise_tex = Texture::new( - &mut factory, + &device, + &queue, &assets::Image::load_expect("voxygen.texture.noise").read().0, - Some(gfx::texture::FilterMethod::Trilinear), - Some(gfx::texture::WrapMode::Tile), - None, + Some(wgpu::FilterMode::Linear), + Some(wgpu::AddressMode::Repeat), )?; + let clouds_locals = + Self::create_consts_inner(&device, &queue, &[clouds::Locals::default()]); + let postprocess_locals = + Self::create_consts_inner(&device, &queue, &[postprocess::Locals::default()]); + + let locals = Locals::new( + &device, + &layouts, + clouds_locals, + postprocess_locals, + &views.tgt_color, + &views.tgt_depth, + &views.tgt_color_pp, + &sampler, + &depth_sampler, + ); + + let quad_index_buffer_u16 = + create_quad_index_buffer_u16(&device, QUAD_INDEX_BUFFER_U16_START_VERT_LEN as usize); + let quad_index_buffer_u32 = + create_quad_index_buffer_u32(&device, QUAD_INDEX_BUFFER_U32_START_VERT_LEN as usize); + let mut profiler = wgpu_profiler::GpuProfiler::new(4, queue.get_timestamp_period()); + mode.profiler_enabled &= profiler_features_enabled; + profiler.enable_timer = mode.profiler_enabled; + profiler.enable_debug_marker = mode.profiler_enabled; + Ok(Self { device, - encoder: factory.create_command_buffer().into(), - factory, + queue, + surface, + swap_chain, + sc_desc, - win_color_view, - win_depth_view, + state, + recreation_pending: false, - tgt_color_view, - tgt_depth_stencil_view, - tgt_color_view_pp, - - tgt_color_res, - tgt_depth_res, - tgt_color_res_pp, + layouts, + locals, + views, sampler, + depth_sampler, + noise_tex, - shadow_map, - - skybox_pipeline, - figure_pipeline, - terrain_pipeline, - fluid_pipeline, - sprite_pipeline, - particle_pipeline, - ui_pipeline, - lod_terrain_pipeline, - clouds_pipeline, - postprocess_pipeline, - player_shadow_pipeline, + quad_index_buffer_u16, + quad_index_buffer_u32, shaders, - noise_tex, - mode, + resolution: Vec2::new(dims.width, dims.height), + + take_screenshot: None, + + profiler, + profile_times: Vec::new(), + profiler_features_enabled, }) } - /// Get references to the internal render target views that get rendered to - /// before post-processing. - #[allow(dead_code)] - pub fn tgt_views(&self) -> (&TgtColorView, &TgtDepthStencilView) { - (&self.tgt_color_view, &self.tgt_depth_stencil_view) + /// Check the status of the intial pipeline creation + /// Returns `None` if complete + /// Returns `Some((total, complete))` if in progress + pub fn pipeline_creation_status(&self) -> Option<(usize, usize)> { + if let State::Interface { creating, .. } = &self.state { + Some(creating.status()) + } else { + None + } } - /// Get references to the internal render target views that get displayed - /// directly by the window. - #[allow(dead_code)] - pub fn win_views(&self) -> (&WinColorView, &WinDepthView) { - (&self.win_color_view, &self.win_depth_view) - } - - /// Get mutable references to the internal render target views that get - /// rendered to before post-processing. - #[allow(dead_code)] - pub fn tgt_views_mut(&mut self) -> (&mut TgtColorView, &mut TgtDepthStencilView) { - (&mut self.tgt_color_view, &mut self.tgt_depth_stencil_view) - } - - /// Get mutable references to the internal render target views that get - /// displayed directly by the window. - #[allow(dead_code)] - pub fn win_views_mut(&mut self) -> (&mut WinColorView, &mut WinDepthView) { - (&mut self.win_color_view, &mut self.win_depth_view) + /// Check the status the pipeline recreation + /// Returns `None` if pipelines are currently not being recreated + /// Returns `Some((total, complete))` if in progress + pub fn pipeline_recreation_status(&self) -> Option<(usize, usize)> { + if let State::Complete { recreating, .. } = &self.state { + recreating.as_ref().map(|r| r.status()) + } else { + None + } } /// Change the render mode. pub fn set_render_mode(&mut self, mode: RenderMode) -> Result<(), RenderError> { + // TODO: are there actually any issues with the current mode not matching the + // pipelines (since we could previously have inconsistencies from + // pipelines failing to build due to shader editing)? + // TODO: FIXME: defer mode changing until pipelines are rebuilt to prevent + // incompatibilities as pipelines are now rebuilt in a deferred mannder in the + // background TODO: consider separating changes that don't require + // rebuilding pipelines self.mode = mode; + self.sc_desc.present_mode = self.mode.present_mode.into(); + + // Only enable profiling if the wgpu features are enabled + self.mode.profiler_enabled &= self.profiler_features_enabled; + // Enable/disable profiler + if !self.mode.profiler_enabled { + // Clear the times if disabled + core::mem::take(&mut self.profile_times); + } + self.profiler.enable_timer = self.mode.profiler_enabled; + self.profiler.enable_debug_marker = self.mode.profiler_enabled; // Recreate render target - self.on_resize()?; + self.on_resize(self.resolution)?; // Recreate pipelines with the new AA mode self.recreate_pipelines(); @@ -474,45 +450,97 @@ impl Renderer { /// Get the render mode. pub fn render_mode(&self) -> &RenderMode { &self.mode } + /// Get the current profiling times + /// Nested timings immediately follow their parent + /// Returns Vec<(how nested this timing is, label, length in seconds)> + pub fn timings(&self) -> Vec<(u8, &str, f64)> { + use wgpu_profiler::GpuTimerScopeResult; + fn recursive_collect<'a>( + vec: &mut Vec<(u8, &'a str, f64)>, + result: &'a GpuTimerScopeResult, + nest_level: u8, + ) { + vec.push(( + nest_level, + &result.label, + result.time.end - result.time.start, + )); + result + .nested_scopes + .iter() + .for_each(|child| recursive_collect(vec, child, nest_level + 1)); + } + let mut vec = Vec::new(); + self.profile_times + .iter() + .for_each(|child| recursive_collect(&mut vec, child, 0)); + vec + } + /// Resize internal render targets to match window render target dimensions. - pub fn on_resize(&mut self) -> Result<(), RenderError> { - let dims = self.win_color_view.get_dimensions(); - + pub fn on_resize(&mut self, dims: Vec2) -> Result<(), RenderError> { // Avoid panics when creating texture with w,h of 0,0. - if dims.0 != 0 && dims.1 != 0 { - let ( - tgt_color_view, - tgt_depth_stencil_view, - tgt_color_view_pp, - tgt_color_res, - tgt_depth_res, - tgt_color_res_pp, - ) = Self::create_rt_views(&mut self.factory, (dims.0, dims.1), &self.mode)?; - self.tgt_color_res = tgt_color_res; - self.tgt_depth_res = tgt_depth_res; - self.tgt_color_res_pp = tgt_color_res_pp; - self.tgt_color_view = tgt_color_view; - self.tgt_depth_stencil_view = tgt_depth_stencil_view; - self.tgt_color_view_pp = tgt_color_view_pp; - if let (Some(shadow_map), ShadowMode::Map(mode)) = - (self.shadow_map.as_mut(), self.mode.shadow) - { - match Self::create_shadow_views(&mut self.factory, (dims.0, dims.1), &mode) { - Ok(( - point_depth_stencil_view, - point_res, - point_sampler, - directed_depth_stencil_view, - directed_res, - directed_sampler, - )) => { - shadow_map.point_depth_stencil_view = point_depth_stencil_view; - shadow_map.point_res = point_res; - shadow_map.point_sampler = point_sampler; + if dims.x != 0 && dims.y != 0 { + // Resize swap chain + self.resolution = dims; + self.sc_desc.width = dims.x; + self.sc_desc.height = dims.y; + self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); - shadow_map.directed_depth_stencil_view = directed_depth_stencil_view; - shadow_map.directed_res = directed_res; - shadow_map.directed_sampler = directed_sampler; + // Resize other render targets + self.views = Self::create_rt_views(&self.device, (dims.x, dims.y), &self.mode)?; + // Rebind views to clouds/postprocess bind groups + self.locals.rebind( + &self.device, + &self.layouts, + &self.views.tgt_color, + &self.views.tgt_depth, + &self.views.tgt_color_pp, + &self.sampler, + &self.depth_sampler, + ); + + // Get mutable reference to shadow views out of the current state + let shadow_views = match &mut self.state { + State::Interface { shadow_views, .. } => { + shadow_views.as_mut().map(|s| (&mut s.0, &mut s.1)) + }, + State::Complete { + shadow: + Shadow { + map: ShadowMap::Enabled(shadow_map), + .. + }, + .. + } => Some((&mut shadow_map.point_depth, &mut shadow_map.directed_depth)), + State::Complete { .. } => None, + State::Nothing => None, // Should never hit this + }; + + if let (Some((point_depth, directed_depth)), ShadowMode::Map(mode)) = + (shadow_views, self.mode.shadow) + { + match ShadowMap::create_shadow_views(&self.device, (dims.x, dims.y), &mode) { + Ok((new_point_depth, new_directed_depth)) => { + *point_depth = new_point_depth; + *directed_depth = new_directed_depth; + // Recreate the shadow bind group if needed + if let State::Complete { + shadow: + Shadow { + bind, + map: ShadowMap::Enabled(shadow_map), + .. + }, + .. + } = &mut self.state + { + *bind = self.layouts.global.bind_shadow_textures( + &self.device, + &shadow_map.point_depth, + &shadow_map.directed_depth, + ); + } }, Err(err) => { warn!("Could not create shadow map views: {:?}", err); @@ -524,270 +552,136 @@ impl Renderer { Ok(()) } + /// Create render target views fn create_rt_views( - factory: &mut gfx_device_gl::Factory, - size: (u16, u16), + device: &wgpu::Device, + size: (u32, u32), mode: &RenderMode, - ) -> Result< - ( - TgtColorView, - TgtDepthStencilView, - TgtColorView, - TgtColorRes, - TgtDepthRes, - TgtColorRes, - ), - RenderError, - > { - let upscaled = Vec2::from(size) - .map(|e: u16| (e as f32 * mode.upscale_mode.factor) as u16) + ) -> Result { + let upscaled = Vec2::::from(size) + .map(|e| (e as f32 * mode.upscale_mode.factor) as u32) .into_tuple(); - let kind = match mode.aa { - AaMode::None | AaMode::Fxaa => { - gfx::texture::Kind::D2(upscaled.0, upscaled.1, gfx::texture::AaMode::Single) - }, - // TODO: Ensure sampling in the shader is exactly between the 4 texels - AaMode::MsaaX4 => { - gfx::texture::Kind::D2(upscaled.0, upscaled.1, gfx::texture::AaMode::Multi(4)) - }, - AaMode::MsaaX8 => { - gfx::texture::Kind::D2(upscaled.0, upscaled.1, gfx::texture::AaMode::Multi(8)) - }, - AaMode::MsaaX16 => { - gfx::texture::Kind::D2(upscaled.0, upscaled.1, gfx::texture::AaMode::Multi(16)) - }, + let (width, height, sample_count) = match mode.aa { + AaMode::None | AaMode::Fxaa => (upscaled.0, upscaled.1, 1), + AaMode::MsaaX4 => (upscaled.0, upscaled.1, 4), + AaMode::MsaaX8 => (upscaled.0, upscaled.1, 8), + AaMode::MsaaX16 => (upscaled.0, upscaled.1, 16), }; let levels = 1; - let color_cty = <::Channel as gfx::format::ChannelTyped - >::get_channel_type(); - let mut color_tex = || { - factory.create_texture( - kind, - levels, - gfx::memory::Bind::SHADER_RESOURCE | gfx::memory::Bind::RENDER_TARGET, - gfx::memory::Usage::Data, - Some(color_cty), - ) + let color_view = || { + let tex = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: levels, + sample_count, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba16Float, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, + }); + + tex.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Rgba16Float), + dimension: Some(wgpu::TextureViewDimension::D2), + // TODO: why is this not Color? + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }) }; - let tgt_color_tex = color_tex()?; - let tgt_color_tex_pp = color_tex()?; - let mut color_res = |tex| { - factory.view_texture_as_shader_resource::( - tex, - (0, levels - 1), - gfx::format::Swizzle::new(), - ) - }; - let tgt_color_res = color_res(&tgt_color_tex)?; - let tgt_color_res_pp = color_res(&tgt_color_tex_pp)?; - let tgt_color_view = factory.view_texture_as_render_target(&tgt_color_tex, 0, None)?; - let tgt_color_view_pp = - factory.view_texture_as_render_target(&tgt_color_tex_pp, 0, None)?; - let depth_stencil_cty = <::Channel as gfx::format::ChannelTyped>::get_channel_type(); - let tgt_depth_stencil_tex = factory.create_texture( - kind, - levels, - gfx::memory::Bind::SHADER_RESOURCE | gfx::memory::Bind::DEPTH_STENCIL, - gfx::memory::Usage::Data, - Some(depth_stencil_cty), - )?; - let tgt_depth_res = factory.view_texture_as_shader_resource::( - &tgt_depth_stencil_tex, - (0, levels - 1), - gfx::format::Swizzle::new(), - )?; - let tgt_depth_stencil_view = - factory.view_texture_as_depth_stencil_trivial(&tgt_depth_stencil_tex)?; + let tgt_color_view = color_view(); + let tgt_color_pp_view = color_view(); - Ok(( - tgt_color_view, - tgt_depth_stencil_view, - tgt_color_view_pp, - tgt_color_res, - tgt_depth_res, - tgt_color_res_pp, - )) - } - - /// Create textures and views for shadow maps. - // This is a one-use type and the two halves are not guaranteed to remain identical, so we - // disable the type complexity lint. - #[allow(clippy::type_complexity)] - fn create_shadow_views( - factory: &mut gfx_device_gl::Factory, - size: (u16, u16), - mode: &ShadowMapMode, - ) -> Result< - ( - ShadowDepthStencilView, - ShadowResourceView, - Sampler, - ShadowDepthStencilView, - ShadowResourceView, - Sampler, - ), - RenderError, - > { - // (Attempt to) apply resolution factor to shadow map resolution. - let resolution_factor = mode.resolution.clamped(0.25, 4.0); - - let max_texture_size = Self::max_texture_size_raw(factory); - // Limit to max texture size, rather than erroring. - let size = Vec2::new(size.0, size.1).map(|e| { - let size = f32::from(e) * resolution_factor; - // NOTE: We know 0 <= e since we clamped the resolution factor to be between - // 0.25 and 4.0. - if size <= f32::from(max_texture_size) { - size as u16 - } else { - max_texture_size - } + let tgt_depth_tex = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: levels, + sample_count, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth32Float, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, + }); + let tgt_depth_view = tgt_depth_tex.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Depth32Float), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::DepthOnly, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, }); - let levels = 1; - // Limit to max texture size rather than erroring. - let two_size = size.map(|e| { - u16::checked_next_power_of_two(e) - .filter(|&e| e <= max_texture_size) - .unwrap_or(max_texture_size) + let win_depth_tex = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: size.0, + height: size.1, + depth_or_array_layers: 1, + }, + mip_level_count: levels, + sample_count, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth32Float, + usage: wgpu::TextureUsage::RENDER_ATTACHMENT, + }); + // TODO: Consider no depth buffer for the final draw to the window? + let win_depth_view = win_depth_tex.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Depth32Float), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::DepthOnly, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, }); - let min_size = size.reduce_min(); - let max_size = size.reduce_max(); - let _min_two_size = two_size.reduce_min(); - let _max_two_size = two_size.reduce_max(); - // For rotated shadow maps, the maximum size of a pixel along any axis is the - // size of a diagonal along that axis. - let diag_size = size.map(f64::from).magnitude(); - let diag_cross_size = f64::from(min_size) / f64::from(max_size) * diag_size; - let (diag_size, _diag_cross_size) = - if 0.0 < diag_size && diag_size <= f64::from(max_texture_size) { - // NOTE: diag_cross_size must be non-negative, since it is the ratio of a - // non-negative and a positive number (if max_size were zero, - // diag_size would be 0 too). And it must be <= diag_size, - // since min_size <= max_size. Therefore, if diag_size fits in a - // u16, so does diag_cross_size. - (diag_size as u16, diag_cross_size as u16) - } else { - // Limit to max texture resolution rather than error. - (max_texture_size as u16, max_texture_size as u16) - }; - let diag_two_size = u16::checked_next_power_of_two(diag_size) - .filter(|&e| e <= max_texture_size) - // Limit to max texture resolution rather than error. - .unwrap_or(max_texture_size); - let depth_stencil_cty = <::Channel as gfx::format::ChannelTyped>::get_channel_type(); - let point_shadow_tex = factory - .create_texture( - gfx::texture::Kind::Cube(diag_two_size / 4), - levels as gfx::texture::Level, - gfx::memory::Bind::SHADER_RESOURCE | gfx::memory::Bind::DEPTH_STENCIL, - gfx::memory::Usage::Data, - Some(depth_stencil_cty), - ) - .map_err(|err| RenderError::CombinedError(gfx::CombinedError::Texture(err)))?; - - let point_tgt_shadow_view = factory - .view_texture_as_depth_stencil::( - &point_shadow_tex, - 0, - None, - gfx::texture::DepthStencilFlags::empty(), - )?; - - let point_tgt_shadow_res = factory - .view_texture_as_shader_resource::( - &point_shadow_tex, - (0, levels - 1), - gfx::format::Swizzle::new(), - )?; - - let directed_shadow_tex = factory - .create_texture( - gfx::texture::Kind::D2(diag_two_size, diag_two_size, gfx::texture::AaMode::Single), - levels as gfx::texture::Level, - gfx::memory::Bind::SHADER_RESOURCE | gfx::memory::Bind::DEPTH_STENCIL, - gfx::memory::Usage::Data, - Some(depth_stencil_cty), - ) - .map_err(|err| RenderError::CombinedError(gfx::CombinedError::Texture(err)))?; - let directed_tgt_shadow_view = factory - .view_texture_as_depth_stencil::( - &directed_shadow_tex, - 0, - None, - gfx::texture::DepthStencilFlags::empty(), - )?; - let directed_tgt_shadow_res = factory - .view_texture_as_shader_resource::( - &directed_shadow_tex, - (0, levels - 1), - gfx::format::Swizzle::new(), - )?; - - let mut sampler_info = gfx::texture::SamplerInfo::new( - gfx::texture::FilterMethod::Bilinear, - // Lights should always be assumed to flood areas we can't see. - gfx::texture::WrapMode::Border, - ); - sampler_info.comparison = Some(Comparison::LessEqual); - sampler_info.border = [1.0; 4].into(); - let point_shadow_tex_sampler = factory.create_sampler(sampler_info); - let directed_shadow_tex_sampler = factory.create_sampler(sampler_info); - - Ok(( - point_tgt_shadow_view, - point_tgt_shadow_res, - point_shadow_tex_sampler, - directed_tgt_shadow_view, - directed_tgt_shadow_res, - directed_shadow_tex_sampler, - )) + Ok(Views { + tgt_color: tgt_color_view, + tgt_depth: tgt_depth_view, + tgt_color_pp: tgt_color_pp_view, + _win_depth: win_depth_view, + }) } /// Get the resolution of the render target. - /// Note: the change after a resize can be delayed so - /// don't rely on this value being constant between resize events - pub fn get_resolution(&self) -> Vec2 { - Vec2::new( - self.win_color_view.get_dimensions().0, - self.win_color_view.get_dimensions().1, - ) - } + pub fn resolution(&self) -> Vec2 { self.resolution } /// Get the resolution of the shadow render target. - pub fn get_shadow_resolution(&self) -> (Vec2, Vec2) { - if let Some(shadow_map) = &self.shadow_map { - let point_dims = shadow_map.point_depth_stencil_view.get_dimensions(); - let directed_dims = shadow_map.directed_depth_stencil_view.get_dimensions(); - ( - Vec2::new(point_dims.0, point_dims.1), - Vec2::new(directed_dims.0, directed_dims.1), - ) - } else { - (Vec2::new(1, 1), Vec2::new(1, 1)) - } - } - - /// Queue the clearing of the shadow targets ready for a new frame to be - /// rendered. - pub fn clear_shadows(&mut self) { - span!(_guard, "clear_shadows", "Renderer::clear_shadows"); - if !self.mode.shadow.is_map() { - return; - } - if let Some(shadow_map) = self.shadow_map.as_mut() { - // let point_encoder = &mut shadow_map.point_encoder; - let point_encoder = &mut self.encoder; - point_encoder.clear_depth(&shadow_map.point_depth_stencil_view, 1.0); - // let directed_encoder = &mut shadow_map.directed_encoder; - let directed_encoder = &mut self.encoder; - directed_encoder.clear_depth(&shadow_map.directed_depth_stencil_view, 1.0); + pub fn get_shadow_resolution(&self) -> (Vec2, Vec2) { + match &self.state { + State::Interface { shadow_views, .. } => shadow_views.as_ref().map(|s| (&s.0, &s.1)), + State::Complete { + shadow: + Shadow { + map: ShadowMap::Enabled(shadow_map), + .. + }, + .. + } => Some((&shadow_map.point_depth, &shadow_map.directed_depth)), + State::Complete { .. } | State::Nothing => None, } + .map(|(point, directed)| (point.get_dimensions().xy(), directed.get_dimensions().xy())) + .unwrap_or_else(|| (Vec2::new(1, 1), Vec2::new(1, 1))) } + // TODO: Seamless is potentially the default with wgpu but we need further + // investigation into whether this is actually turned on for the OpenGL + // backend + // /// NOTE: Supported by Vulkan (by default), DirectX 10+ (it seems--it's hard /// to find proof of this, but Direct3D 10 apparently does it by /// default, and 11 definitely does, so I assume it's natively supported @@ -795,1440 +689,535 @@ impl Renderer { /// there may be some GPUs that don't quite support it correctly, the /// impact is relatively small, so there is no reason not to enable it where /// available. - #[allow(unsafe_code)] - fn enable_seamless_cube_maps(device: &mut gfx_backend::Device) { - unsafe { - // NOTE: Currently just fail silently rather than complain if the computer is on - // a version lower than 3.2, where seamless cubemaps were introduced. - if !device.get_info().is_version_supported(3, 2) { - return; + //fn enable_seamless_cube_maps() { + //todo!() + // unsafe { + // // NOTE: Currently just fail silently rather than complain if the + // computer is on // a version lower than 3.2, where + // seamless cubemaps were introduced. if !device.get_info(). + // is_version_supported(3, 2) { return; + // } + + // // NOTE: Safe because GL_TEXTURE_CUBE_MAP_SEAMLESS is supported + // by OpenGL 3.2+ // (see https://www.khronos.org/opengl/wiki/Cubemap_Texture#Seamless_cubemap); + // // enabling seamless cube maps should always be safe regardless + // of the state of // the OpenGL context, so no further + // checks are needed. device.with_gl(|gl| { + // gl.Enable(gfx_gl::TEXTURE_CUBE_MAP_SEAMLESS); + // }); + // } + //} + + /// Start recording the frame + /// When the returned `Drawer` is dropped the recorded draw calls will be + /// submitted to the queue + /// If there is an intermittent issue with the swap chain then Ok(None) will + /// be returned + pub fn start_recording_frame<'a>( + &'a mut self, + globals: &'a GlobalsBindGroup, + ) -> Result>, RenderError> { + span!( + _guard, + "start_recording_frame", + "Renderer::start_recording_frame" + ); + + // Try to get the latest profiling results + if self.mode.profiler_enabled { + // Note: this lags a few frames behind + if let Some(profile_times) = self.profiler.process_finished_frame() { + self.profile_times = profile_times; } - - // NOTE: Safe because GL_TEXTURE_CUBE_MAP_SEAMLESS is supported by OpenGL 3.2+ - // (see https://www.khronos.org/opengl/wiki/Cubemap_Texture#Seamless_cubemap); - // enabling seamless cube maps should always be safe regardless of the state of - // the OpenGL context, so no further checks are needed. - device.with_gl(|gl| { - gl.Enable(gfx_gl::TEXTURE_CUBE_MAP_SEAMLESS); - }); } - } - /// NOTE: Supported by all but a handful of mobile GPUs - /// (see https://github.com/gpuweb/gpuweb/issues/480) - /// so wgpu should support it too. - #[allow(unsafe_code)] - fn set_depth_clamp(device: &mut gfx_backend::Device, depth_clamp: bool) { - unsafe { - // NOTE: Currently just fail silently rather than complain if the computer is on - // a version lower than 3.3, though we probably will complain - // elsewhere regardless, since shadow mapping is an optional feature - // and having depth clamping disabled won't cause undefined - // behavior, just incorrect shadowing from objects behind the viewer. - if !device.get_info().is_version_supported(3, 3) { - return; + // Handle polling background pipeline creation/recreation + // Temporarily set to nothing and then replace in the statement below + let state = core::mem::replace(&mut self.state, State::Nothing); + // If still creating initial pipelines, check if complete + self.state = if let State::Interface { + pipelines: interface, + shadow_views, + creating, + } = state + { + match creating.try_complete() { + Ok(pipelines) => { + let IngameAndShadowPipelines { ingame, shadow } = pipelines; + + let pipelines = Pipelines::consolidate(interface, ingame); + + let shadow_map = ShadowMap::new( + &self.device, + &self.queue, + shadow.point, + shadow.directed, + shadow.figure, + shadow_views, + ); + + let shadow_bind = { + let (point, directed) = shadow_map.textures(); + self.layouts + .global + .bind_shadow_textures(&self.device, point, directed) + }; + + let shadow = Shadow { + map: shadow_map, + bind: shadow_bind, + }; + + State::Complete { + pipelines, + shadow, + recreating: None, + } + }, + // Not complete + Err(creating) => State::Interface { + pipelines: interface, + shadow_views, + creating, + }, } - - // NOTE: Safe because glDepthClamp is (I believe) supported by - // OpenGL 3.3, so we shouldn't have to check for other OpenGL versions which - // may use different extensions. Also, enabling depth clamping should - // essentially always be safe regardless of the state of the OpenGL - // context, so no further checks are needed. - device.with_gl(|gl| { - if depth_clamp { - gl.Enable(gfx_gl::DEPTH_CLAMP); - } else { - gl.Disable(gfx_gl::DEPTH_CLAMP); - } - }); - } - } - - /// Queue the clearing of the depth target ready for a new frame to be - /// rendered. - pub fn clear(&mut self) { - span!(_guard, "clear", "Renderer::clear"); - self.encoder.clear_depth(&self.tgt_depth_stencil_view, 1.0); - // self.encoder.clear_stencil(&self.tgt_depth_stencil_view, 0); - self.encoder.clear_depth(&self.win_depth_view, 1.0); - } - - /// Set up shadow rendering. - pub fn start_shadows(&mut self) { - if !self.mode.shadow.is_map() { - return; - } - if let Some(_shadow_map) = self.shadow_map.as_mut() { - self.encoder.flush(&mut self.device); - Self::set_depth_clamp(&mut self.device, true); - } - } - - /// Perform all queued draw calls for global.shadows. - pub fn flush_shadows(&mut self) { - if !self.mode.shadow.is_map() { - return; - } - if let Some(_shadow_map) = self.shadow_map.as_mut() { - let point_encoder = &mut self.encoder; - // let point_encoder = &mut shadow_map.point_encoder; - point_encoder.flush(&mut self.device); - // let directed_encoder = &mut shadow_map.directed_encoder; - // directed_encoder.flush(&mut self.device); - // Reset depth clamping. - Self::set_depth_clamp(&mut self.device, false); - } - } - - /// Perform all queued draw calls for this frame and clean up discarded - /// items. - pub fn flush(&mut self) { - span!(_guard, "flush", "Renderer::flush"); - self.encoder.flush(&mut self.device); - self.device.cleanup(); + // If recreating the pipelines, check if that is complete + } else if let State::Complete { + pipelines, + mut shadow, + recreating: Some(recreating), + } = state + { + match recreating.try_complete() { + Ok(Ok((pipelines, shadow_pipelines))) => { + if let ( + Some(point_pipeline), + Some(terrain_directed_pipeline), + Some(figure_directed_pipeline), + ShadowMap::Enabled(shadow_map), + ) = ( + shadow_pipelines.point, + shadow_pipelines.directed, + shadow_pipelines.figure, + &mut shadow.map, + ) { + shadow_map.point_pipeline = point_pipeline; + shadow_map.terrain_directed_pipeline = terrain_directed_pipeline; + shadow_map.figure_directed_pipeline = figure_directed_pipeline; + } + State::Complete { + pipelines, + shadow, + recreating: None, + } + }, + Ok(Err(e)) => { + error!(?e, "Could not recreate shaders from assets due to an error"); + State::Complete { + pipelines, + shadow, + recreating: None, + } + }, + // Not complete + Err(recreating) => State::Complete { + pipelines, + shadow, + recreating: Some(recreating), + }, + } + } else { + state + }; // If the shaders files were changed attempt to recreate the shaders if self.shaders.reloaded() { self.recreate_pipelines(); } + + // Or if we have a recreation pending + if self.recreation_pending + && matches!(&self.state, State::Complete { recreating, .. } if recreating.is_none()) + { + self.recreation_pending = false; + self.recreate_pipelines(); + } + + let tex = match self.swap_chain.get_current_frame() { + Ok(frame) => frame.output, + // If lost recreate the swap chain + Err(err @ wgpu::SwapChainError::Lost) => { + warn!("{}. Recreating swap chain. A frame will be missed", err); + return self.on_resize(self.resolution).map(|()| None); + }, + Err(wgpu::SwapChainError::Timeout) => { + // This will probably be resolved on the next frame + // NOTE: we don't log this because it happens very frequently with + // PresentMode::Fifo and unlimited FPS on certain machines + return Ok(None); + }, + Err(err @ wgpu::SwapChainError::Outdated) => { + warn!("{}. This will probably be resolved on the next frame", err); + return Ok(None); + }, + Err(err @ wgpu::SwapChainError::OutOfMemory) => return Err(err.into()), + }; + let encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("A render encoder"), + }); + + Ok(Some(drawer::Drawer::new(encoder, self, tex, globals))) } /// Recreate the pipelines fn recreate_pipelines(&mut self) { - match create_pipelines( - &mut self.factory, - &self.shaders.read(), - &self.mode, - self.shadow_map.is_some(), - ) { - Ok(( - skybox_pipeline, - figure_pipeline, - terrain_pipeline, - fluid_pipeline, - sprite_pipeline, - particle_pipeline, - ui_pipeline, - lod_terrain_pipeline, - clouds_pipeline, - postprocess_pipeline, - player_shadow_pipeline, - point_shadow_pipeline, - terrain_directed_shadow_pipeline, - figure_directed_shadow_pipeline, - )) => { - self.skybox_pipeline = skybox_pipeline; - self.figure_pipeline = figure_pipeline; - self.terrain_pipeline = terrain_pipeline; - self.fluid_pipeline = fluid_pipeline; - self.sprite_pipeline = sprite_pipeline; - self.particle_pipeline = particle_pipeline; - self.ui_pipeline = ui_pipeline; - self.lod_terrain_pipeline = lod_terrain_pipeline; - self.clouds_pipeline = clouds_pipeline; - self.postprocess_pipeline = postprocess_pipeline; - self.player_shadow_pipeline = player_shadow_pipeline; - if let ( - Some(point_pipeline), - Some(terrain_directed_pipeline), - Some(figure_directed_pipeline), - Some(shadow_map), - ) = ( - point_shadow_pipeline, - terrain_directed_shadow_pipeline, - figure_directed_shadow_pipeline, - self.shadow_map.as_mut(), - ) { - shadow_map.point_pipeline = point_pipeline; - shadow_map.terrain_directed_pipeline = terrain_directed_pipeline; - shadow_map.figure_directed_pipeline = figure_directed_pipeline; - } + match &mut self.state { + State::Complete { recreating, .. } if recreating.is_some() => { + // Defer recreation so that we are not building multiple sets of pipelines in + // the background at once + self.recreation_pending = true; }, - Err(e) => error!(?e, "Could not recreate shaders from assets due to an error",), + State::Complete { + recreating, shadow, .. + } => { + *recreating = Some(pipeline_creation::recreate_pipelines( + Arc::clone(&self.device), + Arc::clone(&self.layouts), + self.shaders.read().clone(), + self.mode.clone(), + self.sc_desc.clone(), // Note: cheap clone + shadow.map.is_enabled(), + )); + }, + State::Interface { .. } => { + // Defer recreation so that we are not building multiple sets of pipelines in + // the background at once + self.recreation_pending = true; + }, + State::Nothing => {}, } } /// Create a new set of constants with the provided values. - pub fn create_consts( - &mut self, + pub fn create_consts(&mut self, vals: &[T]) -> Consts { + Self::create_consts_inner(&self.device, &self.queue, vals) + } + + pub fn create_consts_inner( + device: &wgpu::Device, + queue: &wgpu::Queue, vals: &[T], - ) -> Result, RenderError> { - let mut consts = Consts::new(&mut self.factory, vals.len()); - consts.update(&mut self.encoder, vals, 0)?; - Ok(consts) + ) -> Consts { + let mut consts = Consts::new(device, vals.len()); + consts.update(queue, vals, 0); + consts } /// Update a set of constants with the provided values. - pub fn update_consts( - &mut self, - consts: &mut Consts, - vals: &[T], - ) -> Result<(), RenderError> { - consts.update(&mut self.encoder, vals, 0) + pub fn update_consts(&self, consts: &mut Consts, vals: &[T]) { + consts.update(&self.queue, vals, 0) + } + + pub fn update_clouds_locals(&mut self, new_val: clouds::Locals) { + self.locals.clouds.update(&self.queue, &[new_val], 0) + } + + pub fn update_postprocess_locals(&mut self, new_val: postprocess::Locals) { + self.locals.postprocess.update(&self.queue, &[new_val], 0) } /// Create a new set of instances with the provided values. - pub fn create_instances( + pub fn create_instances( &mut self, vals: &[T], ) -> Result, RenderError> { - let mut instances = Instances::new(&mut self.factory, vals.len())?; - instances.update(&mut self.encoder, vals)?; + let mut instances = Instances::new(&self.device, vals.len()); + instances.update(&self.queue, vals, 0); Ok(instances) } + /// Ensure that the quad index buffer is large enough for a quad vertex + /// buffer with this many vertices + pub(super) fn ensure_sufficient_index_length( + &mut self, + // Length of the vert buffer with 4 verts per quad + vert_length: usize, + ) { + let quad_index_length = vert_length / 4 * 6; + + match V::QUADS_INDEX { + Some(wgpu::IndexFormat::Uint16) => { + // Make sure the global quad index buffer is large enough + if self.quad_index_buffer_u16.len() < quad_index_length { + // Make sure we aren't over the max + if vert_length > u16::MAX as usize { + panic!( + "Vertex type: {} needs to use a larger index type, length: {}", + core::any::type_name::(), + vert_length + ); + } + self.quad_index_buffer_u16 = + create_quad_index_buffer_u16(&self.device, vert_length); + } + }, + Some(wgpu::IndexFormat::Uint32) => { + // Make sure the global quad index buffer is large enough + if self.quad_index_buffer_u32.len() < quad_index_length { + // Make sure we aren't over the max + if vert_length > u32::MAX as usize { + panic!( + "More than u32::MAX({}) verts({}) for type({}) using an index buffer!", + u32::MAX, + vert_length, + core::any::type_name::() + ); + } + self.quad_index_buffer_u32 = + create_quad_index_buffer_u32(&self.device, vert_length); + } + }, + None => {}, + } + } + + pub fn create_sprite_verts(&mut self, mesh: Mesh) -> sprite::SpriteVerts { + self.ensure_sufficient_index_length::(sprite::VERT_PAGE_SIZE as usize); + sprite::create_verts_buffer(&self.device, mesh) + } + /// Create a new model from the provided mesh. - pub fn create_model(&mut self, mesh: &Mesh

) -> Result, RenderError> { - Ok(Model::new(&mut self.factory, mesh)) + /// If the provided mesh is empty this returns None + pub fn create_model(&mut self, mesh: &Mesh) -> Option> { + self.ensure_sufficient_index_length::(mesh.vertices().len()); + Model::new(&self.device, mesh) } /// Create a new dynamic model with the specified size. - pub fn create_dynamic_model( - &mut self, - size: usize, - ) -> Result, RenderError> { - DynamicModel::new(&mut self.factory, size) + pub fn create_dynamic_model(&mut self, size: usize) -> DynamicModel { + DynamicModel::new(&self.device, size) } /// Update a dynamic model with a mesh and a offset. - pub fn update_model( - &mut self, - model: &DynamicModel

, - mesh: &Mesh

, - offset: usize, - ) -> Result<(), RenderError> { - model.update(&mut self.encoder, mesh, offset) + pub fn update_model(&self, model: &DynamicModel, mesh: &Mesh, offset: usize) { + model.update(&self.queue, mesh, offset) } /// Return the maximum supported texture size. - pub fn max_texture_size(&self) -> u16 { Self::max_texture_size_raw(&self.factory) } + pub fn max_texture_size(&self) -> u32 { Self::max_texture_size_raw(&self.device) } /// Return the maximum supported texture size from the factory. - fn max_texture_size_raw(factory: &gfx_backend::Factory) -> u16 { - /// NOTE: OpenGL requirement. - const MAX_TEXTURE_SIZE_MIN: u16 = 1024; - #[cfg(target_os = "macos")] - /// NOTE: Because Macs lie about their max supported texture size. - const MAX_TEXTURE_SIZE_MAX: u16 = 8192; - #[cfg(not(target_os = "macos"))] - /// NOTE: Apparently Macs aren't the only machines that lie. - /// - /// TODO: Find a way to let graphics cards that don't lie do better. - const MAX_TEXTURE_SIZE_MAX: u16 = 8192; - // NOTE: Many APIs for textures require coordinates to fit in u16, which is why - // we perform this conversion. - u16::try_from(factory.get_capabilities().max_texture_size) - .unwrap_or(MAX_TEXTURE_SIZE_MIN) - .min(MAX_TEXTURE_SIZE_MAX) + fn max_texture_size_raw(_device: &wgpu::Device) -> u32 { + // This value is temporary as there are plans to include a way to get this in + // wgpu this is just a sane standard for now + 8192 } /// Create a new immutable texture from the provided image. - pub fn create_texture_immutable_raw( + /// # Panics + /// If the provided data doesn't completely fill the texture this function + /// will panic. + pub fn create_texture_with_data_raw( &mut self, - kind: gfx::texture::Kind, - mipmap: gfx::texture::Mipmap, - data: &[&[::DataType]], - sampler_info: gfx::texture::SamplerInfo, - ) -> Result, RenderError> - where - F::Surface: gfx::format::TextureSurface, - F::Channel: gfx::format::TextureChannel, - ::DataType: Copy, - { - Texture::new_immutable_raw(&mut self.factory, kind, mipmap, data, sampler_info) + texture_info: &wgpu::TextureDescriptor, + view_info: &wgpu::TextureViewDescriptor, + sampler_info: &wgpu::SamplerDescriptor, + data: &[u8], + ) -> Texture { + let tex = Texture::new_raw(&self.device, &texture_info, &view_info, &sampler_info); + + let size = texture_info.size; + let block_size = texture_info.format.describe().block_size; + assert_eq!( + size.width as usize + * size.height as usize + * size.depth_or_array_layers as usize + * block_size as usize, + data.len(), + "Provided data length {} does not fill the provided texture size {:?}", + data.len(), + size, + ); + + tex.update( + &self.queue, + [0; 2], + [texture_info.size.width, texture_info.size.height], + data, + ); + + tex } /// Create a new raw texture. - pub fn create_texture_raw( + pub fn create_texture_raw( &mut self, - kind: gfx::texture::Kind, - max_levels: u8, - bind: gfx::memory::Bind, - usage: gfx::memory::Usage, - levels: (u8, u8), - swizzle: gfx::format::Swizzle, - sampler_info: gfx::texture::SamplerInfo, - ) -> Result, RenderError> - where - F::Surface: gfx::format::TextureSurface, - F::Channel: gfx::format::TextureChannel, - ::DataType: Copy, - { - Texture::new_raw( - &mut self.device, - &mut self.factory, - kind, - max_levels, - bind, - usage, - levels, - swizzle, - sampler_info, - ) + texture_info: &wgpu::TextureDescriptor, + view_info: &wgpu::TextureViewDescriptor, + sampler_info: &wgpu::SamplerDescriptor, + ) -> Texture { + let texture = Texture::new_raw(&self.device, texture_info, view_info, sampler_info); + texture.clear(&self.queue); // Needs to be fully initialized for partial writes to work on Dx12 AMD + texture } /// Create a new texture from the provided image. - pub fn create_texture( + /// + /// Currently only supports Rgba8Srgb + pub fn create_texture( &mut self, image: &image::DynamicImage, - filter_method: Option, - wrap_mode: Option, - border: Option, - ) -> Result, RenderError> - where - F::Surface: gfx::format::TextureSurface, - F::Channel: gfx::format::TextureChannel, - ::DataType: Copy, - { - Texture::new(&mut self.factory, image, filter_method, wrap_mode, border) + filter_method: Option, + address_mode: Option, + ) -> Result { + Texture::new( + &self.device, + &self.queue, + image, + filter_method, + address_mode, + ) } - /// Create a new dynamic texture (gfx::memory::Usage::Dynamic) with the + /// Create a new dynamic texture with the /// specified dimensions. - pub fn create_dynamic_texture(&mut self, dims: Vec2) -> Result { - Texture::new_dynamic(&mut self.factory, dims.x, dims.y) + /// + /// Currently only supports Rgba8Srgb + pub fn create_dynamic_texture(&mut self, dims: Vec2) -> Texture { + Texture::new_dynamic(&self.device, &self.queue, dims.x, dims.y) } /// Update a texture with the provided offset, size, and data. - pub fn update_texture( + /// + /// Currently only supports Rgba8Srgb + pub fn update_texture( &mut self, - texture: &Texture, - offset: [u16; 2], - size: [u16; 2], - data: &[<::Surface as gfx::format::SurfaceTyped>::DataType], - ) -> Result<(), RenderError> - where - ::Surface: gfx::format::TextureSurface, - ::Channel: gfx::format::TextureChannel, - <::Surface as gfx::format::SurfaceTyped>::DataType: Copy, - { - texture.update(&mut self.encoder, offset, size, data) + texture: &Texture, /* */ + offset: [u32; 2], + size: [u32; 2], + // TODO: be generic over pixel type + data: &[[u8; 4]], + ) { + texture.update(&self.queue, offset, size, bytemuck::cast_slice(data)) } - /// Creates a download buffer, downloads the win_color_view, and converts to - /// a image::DynamicImage. - #[allow(clippy::map_clone)] // TODO: Pending review in #587 - pub fn create_screenshot(&mut self) -> Result { - let (width, height) = self.get_resolution().into_tuple(); - use gfx::{ - format::{Formatted, SurfaceTyped}, - memory::Typed, - }; - type WinSurfaceData = <::Surface as SurfaceTyped>::DataType; - let download = self - .factory - .create_download_buffer::(width as usize * height as usize)?; - self.encoder.copy_texture_to_buffer_raw( - self.win_color_view.raw().get_texture(), - None, - gfx::texture::RawImageInfo { - xoffset: 0, - yoffset: 0, - zoffset: 0, - width, - height, - depth: 0, - format: WinColorFmt::get_format(), - mipmap: 0, - }, - download.raw(), - 0, - )?; - self.flush(); - - // Assumes that the format is Rgba8. - let raw_data = self - .factory - .read_mapping(&download)? - .chunks_exact(width as usize) - .rev() - .flatten() - .flatten() - .map(|&e| e) - .collect::>(); - Ok(image::DynamicImage::ImageRgba8( - // Should not fail if the dimensions are correct. - image::ImageBuffer::from_raw(width as u32, height as u32, raw_data).unwrap(), - )) - } - - /// Queue the rendering of the provided skybox model in the upcoming frame. - pub fn render_skybox( + /// Queue to obtain a screenshot on the next frame render + pub fn create_screenshot( &mut self, - model: &Model, - global: &GlobalModel, - locals: &Consts, - lod: &lod_terrain::LodData, + screenshot_handler: impl FnOnce(image::DynamicImage) + Send + 'static, ) { - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &self.skybox_pipeline.pso, - &skybox::pipe::Data { - vbuf: model.vbuf.clone(), - locals: locals.buf.clone(), - globals: global.globals.buf.clone(), - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - tgt_color: self.tgt_color_view.clone(), - tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), - }, - ); - } - - /// Queue the rendering of the provided figure model in the upcoming frame. - pub fn render_figure( - &mut self, - model: &figure::FigureModel, - col_lights: &Texture, - global: &GlobalModel, - locals: &Consts, - bones: &Consts, - lod: &lod_terrain::LodData, - ) { - let (point_shadow_maps, directed_shadow_maps) = - if let Some(shadow_map) = &mut self.shadow_map { - ( - ( - shadow_map.point_res.clone(), - shadow_map.point_sampler.clone(), - ), - ( - shadow_map.directed_res.clone(), - shadow_map.directed_sampler.clone(), - ), - ) - } else { - ( - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - ) - }; - let model = &model.opaque; - - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &self.figure_pipeline.pso, - &figure::pipe::Data { - vbuf: model.vbuf.clone(), - col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), - locals: locals.buf.clone(), - globals: global.globals.buf.clone(), - bones: bones.buf.clone(), - lights: global.lights.buf.clone(), - shadows: global.shadows.buf.clone(), - light_shadows: global.shadow_mats.buf.clone(), - point_shadow_maps, - directed_shadow_maps, - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - tgt_color: self.tgt_color_view.clone(), - tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), - }, - ); - } - - /// Queue the rendering of the player silhouette in the upcoming frame. - pub fn render_player_shadow( - &mut self, - _model: &figure::FigureModel, - _col_lights: &Texture, - _global: &GlobalModel, - _bones: &Consts, - _lod: &lod_terrain::LodData, - _locals: &Consts, - ) { - // FIXME: Consider reenabling at some point. - /* let (point_shadow_maps, directed_shadow_maps) = - if let Some(shadow_map) = &mut self.shadow_map { - ( - ( - shadow_map.point_res.clone(), - shadow_map.point_sampler.clone(), - ), - ( - shadow_map.directed_res.clone(), - shadow_map.directed_sampler.clone(), - ), - ) - } else { - ( - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - ) - }; - let model = &model.opaque; - - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &self.player_shadow_pipeline.pso, - &figure::pipe::Data { - vbuf: model.vbuf.clone(), - col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), - locals: locals.buf.clone(), - globals: global.globals.buf.clone(), - bones: bones.buf.clone(), - lights: global.lights.buf.clone(), - shadows: global.shadows.buf.clone(), - light_shadows: global.shadow_mats.buf.clone(), - point_shadow_maps, - directed_shadow_maps, - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - tgt_color: self.tgt_color_view.clone(), - tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (0, 0) */), - }, - ); */ - } - - /// Queue the rendering of the player model in the upcoming frame. - pub fn render_player( - &mut self, - model: &figure::FigureModel, - col_lights: &Texture, - global: &GlobalModel, - locals: &Consts, - bones: &Consts, - lod: &lod_terrain::LodData, - ) { - let (point_shadow_maps, directed_shadow_maps) = - if let Some(shadow_map) = &mut self.shadow_map { - ( - ( - shadow_map.point_res.clone(), - shadow_map.point_sampler.clone(), - ), - ( - shadow_map.directed_res.clone(), - shadow_map.directed_sampler.clone(), - ), - ) - } else { - ( - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - ) - }; - let model = &model.opaque; - - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &self.figure_pipeline.pso, - &figure::pipe::Data { - vbuf: model.vbuf.clone(), - col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), - locals: locals.buf.clone(), - globals: global.globals.buf.clone(), - bones: bones.buf.clone(), - lights: global.lights.buf.clone(), - shadows: global.shadows.buf.clone(), - light_shadows: global.shadow_mats.buf.clone(), - point_shadow_maps, - directed_shadow_maps, - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - tgt_color: self.tgt_color_view.clone(), - tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), - }, - ); - } - - /// Queue the rendering of the provided terrain chunk model in the upcoming - /// frame. - pub fn render_terrain_chunk( - &mut self, - model: &Model, - col_lights: &Texture, - global: &GlobalModel, - locals: &Consts, - lod: &lod_terrain::LodData, - ) { - let (point_shadow_maps, directed_shadow_maps) = - if let Some(shadow_map) = &mut self.shadow_map { - ( - ( - shadow_map.point_res.clone(), - shadow_map.point_sampler.clone(), - ), - ( - shadow_map.directed_res.clone(), - shadow_map.directed_sampler.clone(), - ), - ) - } else { - ( - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - ) - }; - - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &self.terrain_pipeline.pso, - &terrain::pipe::Data { - vbuf: model.vbuf.clone(), - // TODO: Consider splitting out texture atlas data into a separate vertex buffer, - // since we don't need it for things like global.shadows. - col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), - locals: locals.buf.clone(), - globals: global.globals.buf.clone(), - lights: global.lights.buf.clone(), - shadows: global.shadows.buf.clone(), - light_shadows: global.shadow_mats.buf.clone(), - point_shadow_maps, - directed_shadow_maps, - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - tgt_color: self.tgt_color_view.clone(), - tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), - }, - ); - } - - /// Queue the rendering of a shadow map from a point light in the upcoming - /// frame. - pub fn render_shadow_point( - &mut self, - model: &Model, - global: &GlobalModel, - terrain_locals: &Consts, - locals: &Consts, - ) { - if !self.mode.shadow.is_map() { - return; - } - // NOTE: Don't render shadows if the shader is not supported. - let shadow_map = if let Some(shadow_map) = &mut self.shadow_map { - shadow_map - } else { - return; - }; - - // let point_encoder = &mut shadow_map.point_encoder; - let point_encoder = &mut self.encoder; - point_encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &shadow_map.point_pipeline.pso, - &shadow::pipe::Data { - // Terrain vertex stuff - vbuf: model.vbuf.clone(), - locals: terrain_locals.buf.clone(), - globals: global.globals.buf.clone(), - - // Shadow stuff - light_shadows: locals.buf.clone(), - tgt_depth_stencil: shadow_map.point_depth_stencil_view.clone(), - }, - ); - } - - /// Queue the rendering of terrain shadow map from all directional lights in - /// the upcoming frame. - pub fn render_terrain_shadow_directed( - &mut self, - model: &Model, - global: &GlobalModel, - terrain_locals: &Consts, - locals: &Consts, - ) { - if !self.mode.shadow.is_map() { - return; - } - // NOTE: Don't render shadows if the shader is not supported. - let shadow_map = if let Some(shadow_map) = &mut self.shadow_map { - shadow_map - } else { - return; - }; - - // let directed_encoder = &mut shadow_map.directed_encoder; - let directed_encoder = &mut self.encoder; - directed_encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &shadow_map.terrain_directed_pipeline.pso, - &shadow::pipe::Data { - // Terrain vertex stuff - vbuf: model.vbuf.clone(), - locals: terrain_locals.buf.clone(), - globals: global.globals.buf.clone(), - - // Shadow stuff - light_shadows: locals.buf.clone(), - tgt_depth_stencil: shadow_map.directed_depth_stencil_view.clone(), - }, - ); - } - - /// Queue the rendering of figure shadow map from all directional lights in - /// the upcoming frame. - pub fn render_figure_shadow_directed( - &mut self, - model: &figure::FigureModel, - global: &GlobalModel, - figure_locals: &Consts, - bones: &Consts, - locals: &Consts, - ) { - if !self.mode.shadow.is_map() { - return; - } - // NOTE: Don't render shadows if the shader is not supported. - let shadow_map = if let Some(shadow_map) = &mut self.shadow_map { - shadow_map - } else { - return; - }; - let model = &model.opaque; - - // let directed_encoder = &mut shadow_map.directed_encoder; - let directed_encoder = &mut self.encoder; - directed_encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &shadow_map.figure_directed_pipeline.pso, - &shadow::figure_pipe::Data { - // Terrain vertex stuff - vbuf: model.vbuf.clone(), - locals: figure_locals.buf.clone(), - bones: bones.buf.clone(), - globals: global.globals.buf.clone(), - - // Shadow stuff - light_shadows: locals.buf.clone(), - tgt_depth_stencil: shadow_map.directed_depth_stencil_view.clone(), - }, - ); - } - - /// Queue the rendering of the provided terrain chunk model in the upcoming - /// frame. - pub fn render_fluid_chunk( - &mut self, - model: &Model, - global: &GlobalModel, - locals: &Consts, - lod: &lod_terrain::LodData, - waves: &Texture, - ) { - let (point_shadow_maps, directed_shadow_maps) = - if let Some(shadow_map) = &mut self.shadow_map { - ( - ( - shadow_map.point_res.clone(), - shadow_map.point_sampler.clone(), - ), - ( - shadow_map.directed_res.clone(), - shadow_map.directed_sampler.clone(), - ), - ) - } else { - ( - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - ) - }; - - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &self.fluid_pipeline.pso, - &fluid::pipe::Data { - vbuf: model.vbuf.clone(), - locals: locals.buf.clone(), - globals: global.globals.buf.clone(), - lights: global.lights.buf.clone(), - shadows: global.shadows.buf.clone(), - light_shadows: global.shadow_mats.buf.clone(), - point_shadow_maps, - directed_shadow_maps, - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - waves: (waves.srv.clone(), waves.sampler.clone()), - tgt_color: self.tgt_color_view.clone(), - tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), - }, - ); - } - - /// Queue the rendering of the provided terrain chunk model in the upcoming - /// frame. - pub fn render_sprites( - &mut self, - model: &Model, - col_lights: &Texture, - global: &GlobalModel, - terrain_locals: &Consts, - locals: &Consts, - instances: &Instances, - lod: &lod_terrain::LodData, - ) { - let (point_shadow_maps, directed_shadow_maps) = - if let Some(shadow_map) = &mut self.shadow_map { - ( - ( - shadow_map.point_res.clone(), - shadow_map.point_sampler.clone(), - ), - ( - shadow_map.directed_res.clone(), - shadow_map.directed_sampler.clone(), - ), - ) - } else { - ( - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - ) - }; - - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: Some((instances.count() as u32, 0)), - buffer: gfx::IndexBuffer::Auto, - }, - &self.sprite_pipeline.pso, - &sprite::pipe::Data { - vbuf: model.vbuf.clone(), - ibuf: instances.ibuf.clone(), - col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), - terrain_locals: terrain_locals.buf.clone(), - // NOTE: It would be nice if this wasn't needed and we could use a constant buffer - // offset into the sprite data. Hopefully, when we switch to wgpu we can do this, - // as it offers the exact API we want (the equivalent can be done in OpenGL using - // glBindBufferOffset). - locals: locals.buf.clone(), - globals: global.globals.buf.clone(), - lights: global.lights.buf.clone(), - shadows: global.shadows.buf.clone(), - light_shadows: global.shadow_mats.buf.clone(), - point_shadow_maps, - directed_shadow_maps, - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - tgt_color: self.tgt_color_view.clone(), - tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), - }, - ); - } - - /// Queue the rendering of the provided LoD terrain model in the upcoming - /// frame. - pub fn render_lod_terrain( - &mut self, - model: &Model, - global: &GlobalModel, - locals: &Consts, - lod: &lod_terrain::LodData, - ) { - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &self.lod_terrain_pipeline.pso, - &lod_terrain::pipe::Data { - vbuf: model.vbuf.clone(), - locals: locals.buf.clone(), - globals: global.globals.buf.clone(), - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - map: (lod.map.srv.clone(), lod.map.sampler.clone()), - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - tgt_color: self.tgt_color_view.clone(), - tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), - }, - ); - } - - /// Queue the rendering of the provided particle in the upcoming frame. - pub fn render_particles( - &mut self, - model: &Model, - global: &GlobalModel, - instances: &Instances, - lod: &lod_terrain::LodData, - ) { - let (point_shadow_maps, directed_shadow_maps) = - if let Some(shadow_map) = &mut self.shadow_map { - ( - ( - shadow_map.point_res.clone(), - shadow_map.point_sampler.clone(), - ), - ( - shadow_map.directed_res.clone(), - shadow_map.directed_sampler.clone(), - ), - ) - } else { - ( - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - ) - }; - - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: Some((instances.count() as u32, 0)), - buffer: gfx::IndexBuffer::Auto, - }, - &self.particle_pipeline.pso, - &particle::pipe::Data { - vbuf: model.vbuf.clone(), - ibuf: instances.ibuf.clone(), - globals: global.globals.buf.clone(), - lights: global.lights.buf.clone(), - shadows: global.shadows.buf.clone(), - light_shadows: global.shadow_mats.buf.clone(), - point_shadow_maps, - directed_shadow_maps, - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - tgt_color: self.tgt_color_view.clone(), - tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), - }, - ); - } - - /// Queue the rendering of the provided UI element in the upcoming frame. - pub fn render_ui_element>( - &mut self, - model: Model, - tex: &Texture, - scissor: Aabr, - globals: &Consts, - locals: &Consts, - ) where - F::Surface: gfx::format::TextureSurface, - F::Channel: gfx::format::TextureChannel, - ::DataType: Copy, - { - let Aabr { min, max } = scissor; - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range.start, - end: model.vertex_range.end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &self.ui_pipeline.pso, - &ui::pipe::Data { - vbuf: model.vbuf, - scissor: gfx::Rect { - x: min.x, - y: min.y, - w: max.x - min.x, - h: max.y - min.y, - }, - tex: (tex.srv.clone(), tex.sampler.clone()), - locals: locals.buf.clone(), - globals: globals.buf.clone(), - tgt_color: self.win_color_view.clone(), - tgt_depth: self.win_depth_view.clone(), - }, - ); - } - - pub fn render_clouds( - &mut self, - model: &Model, - globals: &Consts, - locals: &Consts, - lod: &lod_terrain::LodData, - ) { - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &self.clouds_pipeline.pso, - &clouds::pipe::Data { - vbuf: model.vbuf.clone(), - locals: locals.buf.clone(), - globals: globals.buf.clone(), - map: (lod.map.srv.clone(), lod.map.sampler.clone()), - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - color_sampler: (self.tgt_color_res.clone(), self.sampler.clone()), - depth_sampler: (self.tgt_depth_res.clone(), self.sampler.clone()), - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - tgt_color: self.tgt_color_view_pp.clone(), - }, - ) - } - - pub fn render_post_process( - &mut self, - model: &Model, - globals: &Consts, - locals: &Consts, - lod: &lod_terrain::LodData, - ) { - self.encoder.draw( - &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, - base_vertex: 0, - instances: None, - buffer: gfx::IndexBuffer::Auto, - }, - &self.postprocess_pipeline.pso, - &postprocess::pipe::Data { - vbuf: model.vbuf.clone(), - locals: locals.buf.clone(), - globals: globals.buf.clone(), - map: (lod.map.srv.clone(), lod.map.sampler.clone()), - alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), - horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), - color_sampler: (self.tgt_color_res_pp.clone(), self.sampler.clone()), - depth_sampler: (self.tgt_depth_res.clone(), self.sampler.clone()), - noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), - tgt_color: self.win_color_view.clone(), - }, - ) - } -} - -struct GfxPipeline { - pso: gfx::pso::PipelineState, -} - -/// Creates all the pipelines used to render. -#[allow(clippy::type_complexity)] // TODO: Pending review in #587 -fn create_pipelines( - factory: &mut gfx_backend::Factory, - shaders: &Shaders, - mode: &RenderMode, - has_shadow_views: bool, -) -> Result< - ( - GfxPipeline>, - GfxPipeline>, - GfxPipeline>, - GfxPipeline>, - GfxPipeline>, - GfxPipeline>, - GfxPipeline>, - GfxPipeline>, - GfxPipeline>, - GfxPipeline>, - GfxPipeline>, - Option>>, - Option>>, - Option>>, - ), - RenderError, -> { - // We dynamically add extra configuration settings to the constants file. - let constants = format!( - r#" -{} - -#define VOXYGEN_COMPUTATION_PREFERENCE {} -#define FLUID_MODE {} -#define CLOUD_MODE {} -#define LIGHTING_ALGORITHM {} -#define SHADOW_MODE {} - -"#, - shaders.constants.read().0, - // TODO: Configurable vertex/fragment shader preference. - "VOXYGEN_COMPUTATION_PREFERENCE_FRAGMENT", - match mode.fluid { - FluidMode::Cheap => "FLUID_MODE_CHEAP", - FluidMode::Shiny => "FLUID_MODE_SHINY", - }, - match mode.cloud { - CloudMode::None => "CLOUD_MODE_NONE", - CloudMode::Minimal => "CLOUD_MODE_MINIMAL", - CloudMode::Low => "CLOUD_MODE_LOW", - CloudMode::Medium => "CLOUD_MODE_MEDIUM", - CloudMode::High => "CLOUD_MODE_HIGH", - CloudMode::Ultra => "CLOUD_MODE_ULTRA", - }, - match mode.lighting { - LightingMode::Ashikhmin => "LIGHTING_ALGORITHM_ASHIKHMIN", - LightingMode::BlinnPhong => "LIGHTING_ALGORITHM_BLINN_PHONG", - LightingMode::Lambertian => "LIGHTING_ALGORITHM_LAMBERTIAN", - }, - match mode.shadow { - ShadowMode::None => "SHADOW_MODE_NONE", - ShadowMode::Map(_) if has_shadow_views => "SHADOW_MODE_MAP", - ShadowMode::Cheap | ShadowMode::Map(_) => "SHADOW_MODE_CHEAP", - }, - ); - - let anti_alias = &match mode.aa { - AaMode::None => shaders.anti_alias_none, - AaMode::Fxaa => shaders.anti_alias_fxaa, - AaMode::MsaaX4 => shaders.anti_alias_msaa_x4, - AaMode::MsaaX8 => shaders.anti_alias_msaa_x8, - AaMode::MsaaX16 => shaders.anti_alias_msaa_x16, - }; - - let cloud = &match mode.cloud { - CloudMode::None => shaders.cloud_none, - _ => shaders.cloud_regular, - }; - - let mut include_ctx = IncludeContext::new(); - include_ctx.include("constants.glsl", &constants); - include_ctx.include("globals.glsl", &shaders.globals.read().0); - include_ctx.include("shadows.glsl", &shaders.shadows.read().0); - include_ctx.include("sky.glsl", &shaders.sky.read().0); - include_ctx.include("light.glsl", &shaders.light.read().0); - include_ctx.include("srgb.glsl", &shaders.srgb.read().0); - include_ctx.include("random.glsl", &shaders.random.read().0); - include_ctx.include("lod.glsl", &shaders.lod.read().0); - include_ctx.include("anti-aliasing.glsl", &anti_alias.read().0); - include_ctx.include("cloud.glsl", &cloud.read().0); - - // Construct a pipeline for rendering skyboxes - let skybox_pipeline = create_pipeline( - factory, - skybox::pipe::new(), - &shaders.skybox_vert.read().0, - &shaders.skybox_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - )?; - - // Construct a pipeline for rendering figures - let figure_pipeline = create_pipeline( - factory, - figure::pipe::new(), - &shaders.figure_vert.read().0, - &shaders.figure_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - )?; - - // Construct a pipeline for rendering terrain - let terrain_pipeline = create_pipeline( - factory, - terrain::pipe::new(), - &shaders.terrain_vert.read().0, - &shaders.terrain_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - )?; - - // Construct a pipeline for rendering fluids - let fluid_pipeline = create_pipeline( - factory, - fluid::pipe::new(), - &shaders.fluid_vert.read().0, - &match mode.fluid { - FluidMode::Cheap => shaders.fluid_frag_cheap, - FluidMode::Shiny => shaders.fluid_frag_shiny, - } - .read() - .0, - &include_ctx, - gfx::state::CullFace::Nothing, - )?; - - // Construct a pipeline for rendering sprites - let sprite_pipeline = create_pipeline( - factory, - sprite::pipe::new(), - &shaders.sprite_vert.read().0, - &shaders.sprite_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - )?; - - // Construct a pipeline for rendering particles - let particle_pipeline = create_pipeline( - factory, - particle::pipe::new(), - &shaders.particle_vert.read().0, - &shaders.particle_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - )?; - - // Construct a pipeline for rendering UI elements - let ui_pipeline = create_pipeline( - factory, - ui::pipe::new(), - &shaders.ui_vert.read().0, - &shaders.ui_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - )?; - - // Construct a pipeline for rendering terrain - let lod_terrain_pipeline = create_pipeline( - factory, - lod_terrain::pipe::new(), - &shaders.lod_terrain_vert.read().0, - &shaders.lod_terrain_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - )?; - - // Construct a pipeline for rendering our clouds (a kind of post-processing) - let clouds_pipeline = create_pipeline( - factory, - clouds::pipe::new(), - &shaders.clouds_vert.read().0, - &shaders.clouds_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - )?; - - // Construct a pipeline for rendering our post-processing - let postprocess_pipeline = create_pipeline( - factory, - postprocess::pipe::new(), - &shaders.postprocess_vert.read().0, - &shaders.postprocess_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - )?; - - // Construct a pipeline for rendering the player silhouette - let player_shadow_pipeline = create_pipeline( - factory, - figure::pipe::Init { - tgt_depth_stencil: (gfx::preset::depth::PASS_TEST/*, - Stencil::new( - Comparison::Equal, - 0xff, - (StencilOp::Keep, StencilOp::Keep, StencilOp::Keep), - ),*/), - ..figure::pipe::new() - }, - &shaders.figure_vert.read().0, - &shaders.player_shadow_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - )?; - - // Construct a pipeline for rendering point light terrain shadow maps. - let point_shadow_pipeline = match create_shadow_pipeline( - factory, - shadow::pipe::new(), - &shaders.terrain_point_shadow_vert.read().0, - Some(&shaders.light_shadows_geom.read().0), - &shaders.light_shadows_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - None, // Some(gfx::state::Offset(2, 0)) - ) { - Ok(pipe) => Some(pipe), - Err(err) => { - warn!("Could not load point shadow map pipeline: {:?}", err); - None - }, - }; - - // Construct a pipeline for rendering directional light terrain shadow maps. - let terrain_directed_shadow_pipeline = match create_shadow_pipeline( - factory, - shadow::pipe::new(), - &shaders.terrain_directed_shadow_vert.read().0, - None, - &shaders.directed_shadow_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - None, // Some(gfx::state::Offset(2, 1)) - ) { - Ok(pipe) => Some(pipe), - Err(err) => { - warn!( - "Could not load directed terrain shadow map pipeline: {:?}", - err + // Queue screenshot + self.take_screenshot = Some(Box::new(screenshot_handler)); + // Take profiler snapshot + if self.mode.profiler_enabled { + let file_name = format!( + "frame-trace_{}.json", + std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .map(|d| d.as_millis()) + .unwrap_or(0) ); - None - }, - }; - // Construct a pipeline for rendering directional light figure shadow maps. - let figure_directed_shadow_pipeline = match create_shadow_pipeline( - factory, - shadow::figure_pipe::new(), - &shaders.figure_directed_shadow_vert.read().0, - None, - &shaders.directed_shadow_frag.read().0, - &include_ctx, - gfx::state::CullFace::Back, - None, // Some(gfx::state::Offset(2, 1)) - ) { - Ok(pipe) => Some(pipe), - Err(err) => { - warn!( - "Could not load directed figure shadow map pipeline: {:?}", - err - ); - None - }, - }; + if let Err(err) = wgpu_profiler::chrometrace::write_chrometrace( + std::path::Path::new(&file_name), + &self.profile_times, + ) { + error!(?err, "Failed to save GPU timing snapshot"); + } else { + info!("Saved GPU timing snapshot as: {}", file_name); + } + } + } - Ok(( - skybox_pipeline, - figure_pipeline, - terrain_pipeline, - fluid_pipeline, - sprite_pipeline, - particle_pipeline, - ui_pipeline, - lod_terrain_pipeline, - clouds_pipeline, - postprocess_pipeline, - player_shadow_pipeline, - point_shadow_pipeline, - terrain_directed_shadow_pipeline, - figure_directed_shadow_pipeline, - )) + // Consider reenabling at some time + // + // /// Queue the rendering of the player silhouette in the upcoming frame. + // pub fn render_player_shadow( + // &mut self, + // _model: &figure::FigureModel, + // _col_lights: &Texture, + // _global: &GlobalModel, + // _bones: &Consts, + // _lod: &lod_terrain::LodData, + // _locals: &Consts, + // ) { + // // FIXME: Consider reenabling at some point. + // /* let (point_shadow_maps, directed_shadow_maps) = + // if let Some(shadow_map) = &mut self.shadow_map { + // ( + // ( + // shadow_map.point_res.clone(), + // shadow_map.point_sampler.clone(), + // ), + // ( + // shadow_map.directed_res.clone(), + // shadow_map.directed_sampler.clone(), + // ), + // ) + // } else { + // ( + // (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), + // (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), + // ) + // }; + // let model = &model.opaque; + + // self.encoder.draw( + // &gfx::Slice { + // start: model.vertex_range().start, + // end: model.vertex_range().end, + // base_vertex: 0, + // instances: None, + // buffer: gfx::IndexBuffer::Auto, + // }, + // &self.player_shadow_pipeline.pso, + // &figure::pipe::Data { + // vbuf: model.vbuf.clone(), + // col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), + // locals: locals.buf.clone(), + // globals: global.globals.buf.clone(), + // bones: bones.buf.clone(), + // lights: global.lights.buf.clone(), + // shadows: global.shadows.buf.clone(), + // light_shadows: global.shadow_mats.buf.clone(), + // point_shadow_maps, + // directed_shadow_maps, + // noise: (self.noise_tex.srv.clone(), + // self.noise_tex.sampler.clone()), alt: (lod.alt.srv.clone(), + // lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), + // lod.horizon.sampler.clone()), tgt_color: + // self.tgt_color_view.clone(), tgt_depth: + // (self.tgt_depth_view.clone()/* , (0, 0) */), }, + // ); */ + // } } -/// Create a new pipeline from the provided vertex shader and fragment shader. -fn create_pipeline( - factory: &mut gfx_backend::Factory, - pipe: P, - vs: &str, - fs: &str, - ctx: &IncludeContext, - cull_face: gfx::state::CullFace, -) -> Result, RenderError> { - let vs = ctx.expand(vs)?; - let fs = ctx.expand(fs)?; +fn create_quad_index_buffer_u16(device: &wgpu::Device, vert_length: usize) -> Buffer { + assert!(vert_length <= u16::MAX as usize); + let indices = [0, 1, 2, 2, 1, 3] + .iter() + .cycle() + .copied() + .take(vert_length / 4 * 6) + .enumerate() + .map(|(i, b)| (i / 6 * 4 + b) as u16) + .collect::>(); - let program = factory.link_program(vs.as_bytes(), fs.as_bytes())?; - - let result = Ok(GfxPipeline { - pso: factory.create_pipeline_from_program( - &program, - gfx::Primitive::TriangleList, - gfx::state::Rasterizer { - front_face: gfx::state::FrontFace::CounterClockwise, - cull_face, - method: gfx::state::RasterMethod::Fill, - offset: None, - samples: Some(gfx::state::MultiSample), - }, - pipe, - )?, - }); - - result + Buffer::new(device, wgpu::BufferUsage::INDEX, &indices) } -/// Create a new shadow map pipeline. -fn create_shadow_pipeline( - factory: &mut gfx_backend::Factory, - pipe: P, - vs: &str, - gs: Option<&str>, - fs: &str, - ctx: &IncludeContext, - cull_face: gfx::state::CullFace, - offset: Option, -) -> Result, RenderError> { - let vs = ctx.expand(vs)?; - let gs = gs.map(|gs| ctx.expand(gs)).transpose()?; - let fs = ctx.expand(fs)?; +fn create_quad_index_buffer_u32(device: &wgpu::Device, vert_length: usize) -> Buffer { + assert!(vert_length <= u32::MAX as usize); + let indices = [0, 1, 2, 2, 1, 3] + .iter() + .cycle() + .copied() + .take(vert_length / 4 * 6) + .enumerate() + .map(|(i, b)| (i / 6 * 4 + b) as u32) + .collect::>(); - let shader_set = if let Some(gs) = gs { - factory.create_shader_set_geometry(vs.as_bytes(), gs.as_bytes(), fs.as_bytes())? - } else { - factory.create_shader_set(vs.as_bytes(), fs.as_bytes())? - }; - - Ok(GfxPipeline { - pso: factory.create_pipeline_state( - &shader_set, - gfx::Primitive::TriangleList, - gfx::state::Rasterizer { - front_face: gfx::state::FrontFace::CounterClockwise, - // Second-depth shadow mapping: should help reduce z-fighting provided all objects - // are "watertight" (every triangle edge is shared with at most one other - // triangle); this *should* be true for Veloren. - cull_face: match cull_face { - gfx::state::CullFace::Front => gfx::state::CullFace::Back, - gfx::state::CullFace::Back => gfx::state::CullFace::Front, - gfx::state::CullFace::Nothing => gfx::state::CullFace::Nothing, - }, - method: gfx::state::RasterMethod::Fill, - offset, - samples: None, - }, - pipe, - )?, - }) + Buffer::new(device, wgpu::BufferUsage::INDEX, &indices) } diff --git a/voxygen/src/render/renderer/binding.rs b/voxygen/src/render/renderer/binding.rs new file mode 100644 index 0000000000..f42808c0a8 --- /dev/null +++ b/voxygen/src/render/renderer/binding.rs @@ -0,0 +1,88 @@ +use super::{ + super::{ + pipelines::{ + debug, figure, lod_terrain, shadow, sprite, terrain, ui, ColLights, GlobalModel, + GlobalsBindGroup, + }, + texture::Texture, + }, + Renderer, +}; + +impl Renderer { + pub fn bind_globals( + &self, + global_model: &GlobalModel, + lod_data: &lod_terrain::LodData, + ) -> GlobalsBindGroup { + self.layouts + .global + .bind(&self.device, global_model, lod_data, &self.noise_tex) + } + + pub fn bind_sprite_globals( + &self, + global_model: &GlobalModel, + lod_data: &lod_terrain::LodData, + sprite_verts: &sprite::SpriteVerts, + ) -> sprite::SpriteGlobalsBindGroup { + self.layouts.sprite.bind_globals( + &self.device, + global_model, + lod_data, + &self.noise_tex, + sprite_verts, + ) + } + + pub fn create_debug_bound_locals(&mut self, vals: &[debug::Locals]) -> debug::BoundLocals { + let locals = self.create_consts(vals); + self.layouts.debug.bind_locals(&self.device, locals) + } + + pub fn create_ui_bound_locals(&mut self, vals: &[ui::Locals]) -> ui::BoundLocals { + let locals = self.create_consts(vals); + self.layouts.ui.bind_locals(&self.device, locals) + } + + pub fn ui_bind_texture(&self, texture: &Texture) -> ui::TextureBindGroup { + self.layouts.ui.bind_texture(&self.device, texture) + } + + pub fn create_figure_bound_locals( + &mut self, + locals: &[figure::Locals], + bone_data: &[figure::BoneData], + ) -> figure::BoundLocals { + let locals = self.create_consts(locals); + let bone_data = self.create_consts(bone_data); + self.layouts + .figure + .bind_locals(&self.device, locals, bone_data) + } + + pub fn create_terrain_bound_locals( + &mut self, + locals: &[terrain::Locals], + ) -> terrain::BoundLocals { + let locals = self.create_consts(locals); + self.layouts.terrain.bind_locals(&self.device, locals) + } + + pub fn create_shadow_bound_locals(&mut self, locals: &[shadow::Locals]) -> shadow::BoundLocals { + let locals = self.create_consts(locals); + self.layouts.shadow.bind_locals(&self.device, locals) + } + + pub fn figure_bind_col_light(&self, col_light: Texture) -> ColLights { + self.layouts.global.bind_col_light(&self.device, col_light) + } + + pub fn terrain_bind_col_light(&self, col_light: Texture) -> ColLights { + self.layouts.global.bind_col_light(&self.device, col_light) + } + + pub fn sprite_bind_col_light(&self, col_light: Texture) -> ColLights { + self.layouts.global.bind_col_light(&self.device, col_light) + } +} diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs new file mode 100644 index 0000000000..7e25c349d2 --- /dev/null +++ b/voxygen/src/render/renderer/drawer.rs @@ -0,0 +1,869 @@ +use super::{ + super::{ + buffer::Buffer, + instances::Instances, + model::{DynamicModel, Model, SubModel}, + pipelines::{ + blit, clouds, debug, figure, fluid, lod_terrain, particle, shadow, skybox, sprite, + terrain, ui, ColLights, GlobalsBindGroup, + }, + }, + Renderer, ShadowMap, ShadowMapRenderer, +}; +use core::{num::NonZeroU32, ops::Range}; +use std::sync::Arc; +use vek::Aabr; +use wgpu_profiler::scope::{ManualOwningScope, OwningScope, Scope}; + +// Currently available pipelines +enum Pipelines<'frame> { + Interface(&'frame super::InterfacePipelines), + All(&'frame super::Pipelines), + // Should never be in this state for now but we need this to accound for super::State::Nothing + None, +} + +impl<'frame> Pipelines<'frame> { + fn ui(&self) -> Option<&ui::UiPipeline> { + match self { + Pipelines::Interface(pipelines) => Some(&pipelines.ui), + Pipelines::All(pipelines) => Some(&pipelines.ui), + Pipelines::None => None, + } + } + + fn blit(&self) -> Option<&blit::BlitPipeline> { + match self { + Pipelines::Interface(pipelines) => Some(&pipelines.blit), + Pipelines::All(pipelines) => Some(&pipelines.blit), + Pipelines::None => None, + } + } + + fn all(&self) -> Option<&super::Pipelines> { + match self { + Pipelines::All(pipelines) => Some(pipelines), + Pipelines::Interface(_) | Pipelines::None => None, + } + } +} + +// Borrow the fields we need from the renderer so that the GpuProfiler can be +// disjointly borrowed mutably +struct RendererBorrow<'frame> { + queue: &'frame wgpu::Queue, + device: &'frame wgpu::Device, + shadow: Option<&'frame super::Shadow>, + pipelines: Pipelines<'frame>, + locals: &'frame super::locals::Locals, + views: &'frame super::Views, + mode: &'frame super::super::RenderMode, + quad_index_buffer_u16: &'frame Buffer, + quad_index_buffer_u32: &'frame Buffer, +} + +pub struct Drawer<'frame> { + encoder: Option>, + borrow: RendererBorrow<'frame>, + swap_tex: wgpu::SwapChainTexture, + globals: &'frame GlobalsBindGroup, + // Texture and other info for taking a screenshot + // Writes to this instead in the third pass if it is present + taking_screenshot: Option, +} + +impl<'frame> Drawer<'frame> { + pub fn new( + encoder: wgpu::CommandEncoder, + renderer: &'frame mut Renderer, + swap_tex: wgpu::SwapChainTexture, + globals: &'frame GlobalsBindGroup, + ) -> Self { + let taking_screenshot = renderer.take_screenshot.take().map(|screenshot_fn| { + super::screenshot::TakeScreenshot::new( + &renderer.device, + &renderer.layouts.blit, + &renderer.sampler, + &renderer.sc_desc, + screenshot_fn, + ) + }); + + let (pipelines, shadow) = match &renderer.state { + super::State::Interface { pipelines, .. } => (Pipelines::Interface(pipelines), None), + super::State::Complete { + pipelines, shadow, .. + } => (Pipelines::All(pipelines), Some(shadow)), + super::State::Nothing => (Pipelines::None, None), + }; + + let borrow = RendererBorrow { + queue: &renderer.queue, + device: &renderer.device, + shadow, + pipelines, + locals: &renderer.locals, + views: &renderer.views, + mode: &renderer.mode, + quad_index_buffer_u16: &renderer.quad_index_buffer_u16, + quad_index_buffer_u32: &renderer.quad_index_buffer_u32, + }; + + let encoder = + ManualOwningScope::start("frame", &mut renderer.profiler, encoder, borrow.device); + + Self { + encoder: Some(encoder), + borrow, + swap_tex, + globals, + taking_screenshot, + } + } + + /// Get the render mode. + pub fn render_mode(&self) -> &super::super::RenderMode { self.borrow.mode } + + /// Returns None if the shadow renderer is not enabled at some level or the + /// pipelines are not available yet + pub fn shadow_pass(&mut self) -> Option { + if !self.borrow.mode.shadow.is_map() { + return None; + } + + if let ShadowMap::Enabled(ref shadow_renderer) = self.borrow.shadow?.map { + let encoder = self.encoder.as_mut().unwrap(); + let device = self.borrow.device; + let mut render_pass = + encoder.scoped_render_pass("shadow_pass", device, &wgpu::RenderPassDescriptor { + label: Some("shadow pass"), + color_attachments: &[], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &shadow_renderer.directed_depth.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), + }); + + render_pass.set_bind_group(0, &self.globals.bind_group, &[]); + + Some(ShadowPassDrawer { + render_pass, + borrow: &self.borrow, + shadow_renderer, + }) + } else { + None + } + } + + /// Returns None if all the pipelines are not available + pub fn first_pass(&mut self) -> Option { + let pipelines = self.borrow.pipelines.all()?; + // Note: this becomes Some once pipeline creation is complete even if shadows + // are not enabled + let shadow = self.borrow.shadow?; + + let encoder = self.encoder.as_mut().unwrap(); + let device = self.borrow.device; + let mut render_pass = + encoder.scoped_render_pass("first_pass", device, &wgpu::RenderPassDescriptor { + label: Some("first pass"), + color_attachments: &[wgpu::RenderPassColorAttachment { + view: &self.borrow.views.tgt_color, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: true, + }, + }], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.borrow.views.tgt_depth, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(0.0), + store: true, + }), + stencil_ops: None, + }), + }); + + render_pass.set_bind_group(0, &self.globals.bind_group, &[]); + render_pass.set_bind_group(1, &shadow.bind.bind_group, &[]); + + Some(FirstPassDrawer { + render_pass, + borrow: &self.borrow, + pipelines, + globals: self.globals, + }) + } + + /// Returns None if the clouds pipeline is not available + pub fn second_pass(&mut self) -> Option { + let pipeline = &self.borrow.pipelines.all()?.clouds; + + let encoder = self.encoder.as_mut().unwrap(); + let device = self.borrow.device; + let mut render_pass = + encoder.scoped_render_pass("second_pass", device, &wgpu::RenderPassDescriptor { + label: Some("second pass (clouds)"), + color_attachments: &[wgpu::RenderPassColorAttachment { + view: &self.borrow.views.tgt_color_pp, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + + render_pass.set_bind_group(0, &self.globals.bind_group, &[]); + + Some(SecondPassDrawer { + render_pass, + borrow: &self.borrow, + pipeline, + }) + } + + pub fn third_pass(&mut self) -> ThirdPassDrawer { + let encoder = self.encoder.as_mut().unwrap(); + let device = self.borrow.device; + let mut render_pass = + encoder.scoped_render_pass("third_pass", device, &wgpu::RenderPassDescriptor { + label: Some("third pass (postprocess + ui)"), + color_attachments: &[wgpu::RenderPassColorAttachment { + // If a screenshot was requested render to that as an intermediate texture + // instead + view: self + .taking_screenshot + .as_ref() + .map_or(&self.swap_tex.view, |s| s.texture_view()), + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + + render_pass.set_bind_group(0, &self.globals.bind_group, &[]); + + ThirdPassDrawer { + render_pass, + borrow: &self.borrow, + } + } + + /// Does nothing if the shadow pipelines are not available or shadow map + /// rendering is disabled + pub fn draw_point_shadows<'data: 'frame>( + &mut self, + matrices: &[shadow::PointLightMatrix; 126], + chunks: impl Clone + + Iterator, &'data terrain::BoundLocals)>, + ) { + if !self.borrow.mode.shadow.is_map() { + return; + } + + if let Some(ShadowMap::Enabled(ref shadow_renderer)) = self.borrow.shadow.map(|s| &s.map) { + let device = self.borrow.device; + let mut encoder = self + .encoder + .as_mut() + .unwrap() + .scope("point shadows", device); + const STRIDE: usize = std::mem::size_of::(); + let data = bytemuck::cast_slice(matrices); + + for face in 0..6 { + // TODO: view creation cost? + let view = + shadow_renderer + .point_depth + .tex + .create_view(&wgpu::TextureViewDescriptor { + label: Some("Point shadow cubemap face"), + format: None, + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::DepthOnly, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: face, + array_layer_count: NonZeroU32::new(1), + }); + + let label = format!("point shadow face-{} pass", face); + let mut render_pass = + encoder.scoped_render_pass(&label, device, &wgpu::RenderPassDescriptor { + label: Some(&label), + color_attachments: &[], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), + }); + + render_pass.set_pipeline(&shadow_renderer.point_pipeline.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + render_pass.set_bind_group(0, &self.globals.bind_group, &[]); + + (0../*20*/1).for_each(|point_light| { + render_pass.set_push_constants( + wgpu::ShaderStage::all(), + 0, + &data[(6 * (point_light + 1) * STRIDE + face as usize * STRIDE) + ..(6 * (point_light + 1) * STRIDE + (face + 1) as usize * STRIDE)], + ); + chunks.clone().for_each(|(model, locals)| { + render_pass.set_bind_group(1, &locals.bind_group, &[]); + render_pass.set_vertex_buffer(0, model.buf().slice(..)); + render_pass.draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..1); + }); + }); + } + } + } + + /// Clear all the shadow textures, useful if directed shadows (shadow_pass) + /// and point light shadows (draw_point_shadows) are unused and thus the + /// textures will otherwise not be cleared after either their + /// initialization or their last use + /// NOTE: could simply use the above passes except `draw_point_shadows` + /// requires an array of matrices that could be a pain to construct + /// simply for clearing + /// + /// Does nothing if the shadow pipelines are not available (although they + /// aren't used here they are needed for the ShadowMap to exist) + pub fn clear_shadows(&mut self) { + if let Some(ShadowMap::Enabled(ref shadow_renderer)) = self.borrow.shadow.map(|s| &s.map) { + let device = self.borrow.device; + let encoder = self.encoder.as_mut().unwrap(); + let _ = encoder.scoped_render_pass( + "clear_directed_shadow", + device, + &wgpu::RenderPassDescriptor { + label: Some("clear directed shadow pass"), + color_attachments: &[], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &shadow_renderer.directed_depth.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), + }, + ); + + for face in 0..6 { + // TODO: view creation cost? + let view = + shadow_renderer + .point_depth + .tex + .create_view(&wgpu::TextureViewDescriptor { + label: Some("Point shadow cubemap face"), + format: None, + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::DepthOnly, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: face, + array_layer_count: NonZeroU32::new(1), + }); + + let label = format!("clear point shadow face-{} pass", face); + let _ = encoder.scoped_render_pass(&label, device, &wgpu::RenderPassDescriptor { + label: Some(&label), + color_attachments: &[], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), + }); + } + } + } +} + +impl<'frame> Drop for Drawer<'frame> { + fn drop(&mut self) { + let mut encoder = self.encoder.take().unwrap(); + + // If taking a screenshot and the blit pipeline is available + // NOTE: blit pipeline should always be available for now so we don't report an + // error if it isn't + if let Some((screenshot, blit)) = self + .taking_screenshot + .take() + .zip(self.borrow.pipelines.blit()) + { + // Image needs to be copied from the screenshot texture to the swapchain texture + let mut render_pass = encoder.scoped_render_pass( + "screenshot blit", + self.borrow.device, + &wgpu::RenderPassDescriptor { + label: Some("Blit screenshot pass"), + color_attachments: &[wgpu::RenderPassColorAttachment { + view: &self.swap_tex.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: true, + }, + }], + depth_stencil_attachment: None, + }, + ); + render_pass.set_pipeline(&blit.pipeline); + render_pass.set_bind_group(0, &screenshot.bind_group(), &[]); + render_pass.draw(0..3, 0..1); + drop(render_pass); + // Issues a command to copy from the texture to a buffer and then sends the + // buffer off to another thread to be mapped and processed + screenshot.download_and_handle(&mut encoder); + } + + let (mut encoder, profiler) = encoder.end_scope(); + profiler.resolve_queries(&mut encoder); + + // It is recommended to only do one submit per frame + self.borrow.queue.submit(std::iter::once(encoder.finish())); + + profiler + .end_frame() + .expect("Gpu profiler error! Maybe there was an unclosed scope?"); + } +} + +// Shadow pass +pub struct ShadowPassDrawer<'pass> { + render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>, + borrow: &'pass RendererBorrow<'pass>, + shadow_renderer: &'pass ShadowMapRenderer, +} + +impl<'pass> ShadowPassDrawer<'pass> { + pub fn draw_figure_shadows(&mut self) -> FigureShadowDrawer<'_, 'pass> { + let mut render_pass = self + .render_pass + .scope("direcred_figure_shadows", self.borrow.device); + + render_pass.set_pipeline(&self.shadow_renderer.figure_directed_pipeline.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + + FigureShadowDrawer { render_pass } + } + + pub fn draw_terrain_shadows(&mut self) -> TerrainShadowDrawer<'_, 'pass> { + let mut render_pass = self + .render_pass + .scope("direcred_terrain_shadows", self.borrow.device); + + render_pass.set_pipeline(&self.shadow_renderer.terrain_directed_pipeline.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + + TerrainShadowDrawer { render_pass } + } +} + +pub struct FigureShadowDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, +} + +impl<'pass_ref, 'pass: 'pass_ref> FigureShadowDrawer<'pass_ref, 'pass> { + pub fn draw<'data: 'pass>( + &mut self, + model: SubModel<'data, terrain::Vertex>, + locals: &'data figure::BoundLocals, + ) { + self.render_pass.set_bind_group(1, &locals.bind_group, &[]); + self.render_pass.set_vertex_buffer(0, model.buf()); + self.render_pass + .draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..1); + } +} + +pub struct TerrainShadowDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, +} + +impl<'pass_ref, 'pass: 'pass_ref> TerrainShadowDrawer<'pass_ref, 'pass> { + pub fn draw<'data: 'pass>( + &mut self, + model: &'data Model, + locals: &'data terrain::BoundLocals, + ) { + self.render_pass.set_bind_group(1, &locals.bind_group, &[]); + self.render_pass.set_vertex_buffer(0, model.buf().slice(..)); + self.render_pass + .draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..1); + } +} + +// First pass +pub struct FirstPassDrawer<'pass> { + pub(super) render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>, + borrow: &'pass RendererBorrow<'pass>, + pipelines: &'pass super::Pipelines, + globals: &'pass GlobalsBindGroup, +} + +impl<'pass> FirstPassDrawer<'pass> { + pub fn draw_skybox<'data: 'pass>(&mut self, model: &'data Model) { + let mut render_pass = self.render_pass.scope("skybox", self.borrow.device); + + render_pass.set_pipeline(&self.pipelines.skybox.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + render_pass.set_vertex_buffer(0, model.buf().slice(..)); + render_pass.draw(0..model.len() as u32, 0..1); + } + + pub fn draw_debug(&mut self) -> DebugDrawer<'_, 'pass> { + let mut render_pass = self.render_pass.scope("debug", self.borrow.device); + + render_pass.set_pipeline(&self.pipelines.debug.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + + DebugDrawer { render_pass } + } + + pub fn draw_lod_terrain<'data: 'pass>(&mut self, model: &'data Model) { + let mut render_pass = self.render_pass.scope("lod_terrain", self.borrow.device); + + render_pass.set_pipeline(&self.pipelines.lod_terrain.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + render_pass.set_vertex_buffer(0, model.buf().slice(..)); + render_pass.draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..1); + } + + pub fn draw_figures(&mut self) -> FigureDrawer<'_, 'pass> { + let mut render_pass = self.render_pass.scope("figures", self.borrow.device); + + render_pass.set_pipeline(&self.pipelines.figure.pipeline); + // Note: figures use the same vertex type as the terrain + set_quad_index_buffer::(&mut render_pass, &self.borrow); + + FigureDrawer { render_pass } + } + + pub fn draw_terrain(&mut self) -> TerrainDrawer<'_, 'pass> { + let mut render_pass = self.render_pass.scope("terrain", self.borrow.device); + + render_pass.set_pipeline(&self.pipelines.terrain.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + + TerrainDrawer { + render_pass, + col_lights: None, + } + } + + pub fn draw_particles(&mut self) -> ParticleDrawer<'_, 'pass> { + let mut render_pass = self.render_pass.scope("particles", self.borrow.device); + + render_pass.set_pipeline(&self.pipelines.particle.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + + ParticleDrawer { render_pass } + } + + pub fn draw_sprites<'data: 'pass>( + &mut self, + globals: &'data sprite::SpriteGlobalsBindGroup, + col_lights: &'data ColLights, + ) -> SpriteDrawer<'_, 'pass> { + let mut render_pass = self.render_pass.scope("sprites", self.borrow.device); + + render_pass.set_pipeline(&self.pipelines.sprite.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + render_pass.set_bind_group(0, &globals.bind_group, &[]); + render_pass.set_bind_group(3, &col_lights.bind_group, &[]); + + SpriteDrawer { + render_pass, + globals: self.globals, + } + } + + pub fn draw_fluid(&mut self) -> FluidDrawer<'_, 'pass> { + let mut render_pass = self.render_pass.scope("fluid", self.borrow.device); + + render_pass.set_pipeline(&self.pipelines.fluid.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + + FluidDrawer { render_pass } + } +} + +pub struct DebugDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, +} + +impl<'pass_ref, 'pass: 'pass_ref> DebugDrawer<'pass_ref, 'pass> { + pub fn draw<'data: 'pass>( + &mut self, + model: &'data Model, + locals: &'data debug::BoundLocals, + ) { + self.render_pass.set_bind_group(1, &locals.bind_group, &[]); + self.render_pass.set_vertex_buffer(0, model.buf().slice(..)); + self.render_pass.draw(0..model.len() as u32, 0..1); + } +} + +pub struct FigureDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, +} + +impl<'pass_ref, 'pass: 'pass_ref> FigureDrawer<'pass_ref, 'pass> { + pub fn draw<'data: 'pass>( + &mut self, + model: SubModel<'data, terrain::Vertex>, + locals: &'data figure::BoundLocals, + // TODO: don't rebind this every time once they are shared between figures + col_lights: &'data ColLights, + ) { + self.render_pass.set_bind_group(2, &locals.bind_group, &[]); + self.render_pass + .set_bind_group(3, &col_lights.bind_group, &[]); + self.render_pass.set_vertex_buffer(0, model.buf()); + self.render_pass + .draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..1); + } +} + +pub struct TerrainDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, + col_lights: Option<&'pass_ref Arc>>, +} + +impl<'pass_ref, 'pass: 'pass_ref> TerrainDrawer<'pass_ref, 'pass> { + pub fn draw<'data: 'pass>( + &mut self, + model: &'data Model, + col_lights: &'data Arc>, + locals: &'data terrain::BoundLocals, + ) { + if self.col_lights + // Check if we are still using the same atlas texture as the previous drawn + // chunk + .filter(|current_col_lights| Arc::ptr_eq(current_col_lights, col_lights)) + .is_none() + { + self.render_pass + .set_bind_group(3, &col_lights.bind_group, &[]); // TODO: put this in slot 2 + self.col_lights = Some(col_lights); + }; + + self.render_pass.set_bind_group(2, &locals.bind_group, &[]); // TODO: put this in slot 3 + self.render_pass.set_vertex_buffer(0, model.buf().slice(..)); + self.render_pass + .draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..1); + } +} + +pub struct ParticleDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, +} + +impl<'pass_ref, 'pass: 'pass_ref> ParticleDrawer<'pass_ref, 'pass> { + // Note: if we ever need to draw less than the whole model, these APIs can be + // changed + pub fn draw<'data: 'pass>( + &mut self, + model: &'data Model, + instances: &'data Instances, + ) { + self.render_pass.set_vertex_buffer(0, model.buf().slice(..)); + self.render_pass + .set_vertex_buffer(1, instances.buf().slice(..)); + self.render_pass + // TODO: since we cast to u32 maybe this should returned by the len/count functions? + .draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..instances.count() as u32); + } +} + +pub struct SpriteDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, + globals: &'pass GlobalsBindGroup, +} + +impl<'pass_ref, 'pass: 'pass_ref> SpriteDrawer<'pass_ref, 'pass> { + pub fn draw<'data: 'pass>( + &mut self, + terrain_locals: &'data terrain::BoundLocals, + instances: &'data Instances, + ) { + self.render_pass + .set_bind_group(2, &terrain_locals.bind_group, &[]); + + self.render_pass + .set_vertex_buffer(0, instances.buf().slice(..)); + self.render_pass.draw_indexed( + 0..sprite::VERT_PAGE_SIZE / 4 * 6, + 0, + 0..instances.count() as u32, + ); + } +} + +impl<'pass_ref, 'pass: 'pass_ref> Drop for SpriteDrawer<'pass_ref, 'pass> { + fn drop(&mut self) { + // Reset to regular globals + self.render_pass + .set_bind_group(0, &self.globals.bind_group, &[]); + } +} + +pub struct FluidDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, +} + +impl<'pass_ref, 'pass: 'pass_ref> FluidDrawer<'pass_ref, 'pass> { + pub fn draw<'data: 'pass>( + &mut self, + model: &'data Model, + locals: &'data terrain::BoundLocals, + ) { + self.render_pass.set_vertex_buffer(0, model.buf().slice(..)); + self.render_pass.set_bind_group(2, &locals.bind_group, &[]); + self.render_pass + .draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..1); + } +} + +// Second pass: clouds +pub struct SecondPassDrawer<'pass> { + render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>, + borrow: &'pass RendererBorrow<'pass>, + pipeline: &'pass clouds::CloudsPipeline, +} + +impl<'pass> SecondPassDrawer<'pass> { + pub fn draw_clouds(&mut self) { + self.render_pass.set_pipeline(&self.pipeline.pipeline); + self.render_pass + .set_bind_group(1, &self.borrow.locals.clouds_bind.bind_group, &[]); + self.render_pass.draw(0..3, 0..1); + } +} + +/// Third pass: postprocess + ui +pub struct ThirdPassDrawer<'pass> { + render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>, + borrow: &'pass RendererBorrow<'pass>, +} + +impl<'pass> ThirdPassDrawer<'pass> { + /// Does nothing if the postprocess pipeline is not available + pub fn draw_postprocess(&mut self) { + let postprocess = match self.borrow.pipelines.all() { + Some(p) => &p.postprocess, + None => return, + }; + + let mut render_pass = self.render_pass.scope("postprocess", self.borrow.device); + render_pass.set_pipeline(&postprocess.pipeline); + render_pass.set_bind_group(1, &self.borrow.locals.postprocess_bind.bind_group, &[]); + render_pass.draw(0..3, 0..1); + } + + /// Returns None if the UI pipeline is not available (note: this should + /// never be the case for now) + pub fn draw_ui(&mut self) -> Option> { + let ui = self.borrow.pipelines.ui()?; + + let mut render_pass = self.render_pass.scope("ui", self.borrow.device); + render_pass.set_pipeline(&ui.pipeline); + set_quad_index_buffer::(&mut render_pass, &self.borrow); + + Some(UiDrawer { render_pass }) + } +} + +pub struct UiDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, +} + +pub struct PreparedUiDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: &'pass_ref mut wgpu::RenderPass<'pass>, +} + +impl<'pass_ref, 'pass: 'pass_ref> UiDrawer<'pass_ref, 'pass> { + /// Set vertex buffer, initial scissor, and locals + /// These can be changed later but this ensures that they don't have to be + /// set with every draw call + pub fn prepare<'data: 'pass>( + &mut self, + locals: &'data ui::BoundLocals, + buf: &'data DynamicModel, + scissor: Aabr, + ) -> PreparedUiDrawer<'_, 'pass> { + // Note: not actually prepared yet + // we do this to avoid having to write extra code for the set functions + let mut prepared = PreparedUiDrawer { + render_pass: &mut *self.render_pass, + }; + // Prepare + prepared.set_locals(locals); + prepared.set_model(buf); + prepared.set_scissor(scissor); + + prepared + } +} + +impl<'pass_ref, 'pass: 'pass_ref> PreparedUiDrawer<'pass_ref, 'pass> { + pub fn set_locals<'data: 'pass>(&mut self, locals: &'data ui::BoundLocals) { + self.render_pass.set_bind_group(1, &locals.bind_group, &[]); + } + + pub fn set_model<'data: 'pass>(&mut self, model: &'data DynamicModel) { + self.render_pass.set_vertex_buffer(0, model.buf().slice(..)) + } + + pub fn set_scissor(&mut self, scissor: Aabr) { + let Aabr { min, max } = scissor; + self.render_pass.set_scissor_rect( + min.x as u32, + min.y as u32, + (max.x - min.x) as u32, + (max.y - min.y) as u32, + ); + } + + pub fn draw<'data: 'pass>(&mut self, texture: &'data ui::TextureBindGroup, verts: Range) { + self.render_pass.set_bind_group(2, &texture.bind_group, &[]); + self.render_pass.draw(verts, 0..1); + } +} + +fn set_quad_index_buffer<'a, V: super::super::Vertex>( + pass: &mut wgpu::RenderPass<'a>, + borrow: &RendererBorrow<'a>, +) { + if let Some(format) = V::QUADS_INDEX { + let slice = match format { + wgpu::IndexFormat::Uint16 => borrow.quad_index_buffer_u16.buf.slice(..), + wgpu::IndexFormat::Uint32 => borrow.quad_index_buffer_u32.buf.slice(..), + }; + + pass.set_index_buffer(slice, format); + } +} diff --git a/voxygen/src/render/renderer/locals.rs b/voxygen/src/render/renderer/locals.rs new file mode 100644 index 0000000000..b7c94c5396 --- /dev/null +++ b/voxygen/src/render/renderer/locals.rs @@ -0,0 +1,75 @@ +use super::{ + super::{ + consts::Consts, + pipelines::{clouds, postprocess}, + }, + Layouts, +}; + +pub struct Locals { + pub clouds: Consts, + pub clouds_bind: clouds::BindGroup, + + pub postprocess: Consts, + pub postprocess_bind: postprocess::BindGroup, +} + +impl Locals { + pub(super) fn new( + device: &wgpu::Device, + layouts: &Layouts, + clouds_locals: Consts, + postprocess_locals: Consts, + tgt_color_view: &wgpu::TextureView, + tgt_depth_view: &wgpu::TextureView, + tgt_color_pp_view: &wgpu::TextureView, + sampler: &wgpu::Sampler, + depth_sampler: &wgpu::Sampler, + ) -> Self { + let clouds_bind = layouts.clouds.bind( + device, + tgt_color_view, + tgt_depth_view, + sampler, + depth_sampler, + &clouds_locals, + ); + let postprocess_bind = + layouts + .postprocess + .bind(device, tgt_color_pp_view, sampler, &postprocess_locals); + + Self { + clouds: clouds_locals, + clouds_bind, + postprocess: postprocess_locals, + postprocess_bind, + } + } + + pub(super) fn rebind( + &mut self, + device: &wgpu::Device, + layouts: &Layouts, + // Call when these are recreated and need to be rebound + // e.g. resizing + tgt_color_view: &wgpu::TextureView, + tgt_depth_view: &wgpu::TextureView, + tgt_color_pp_view: &wgpu::TextureView, + sampler: &wgpu::Sampler, + depth_sampler: &wgpu::Sampler, + ) { + self.clouds_bind = layouts.clouds.bind( + device, + tgt_color_view, + tgt_depth_view, + sampler, + depth_sampler, + &self.clouds, + ); + self.postprocess_bind = + layouts + .postprocess + .bind(device, tgt_color_pp_view, sampler, &self.postprocess); + } +} diff --git a/voxygen/src/render/renderer/pipeline_creation.rs b/voxygen/src/render/renderer/pipeline_creation.rs new file mode 100644 index 0000000000..6ad5e483a9 --- /dev/null +++ b/voxygen/src/render/renderer/pipeline_creation.rs @@ -0,0 +1,914 @@ +use super::{ + super::{ + pipelines::{ + blit, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, + sprite, terrain, ui, + }, + AaMode, CloudMode, FluidMode, LightingMode, RenderError, RenderMode, ShadowMode, + }, + shaders::Shaders, + Layouts, +}; +use common_base::prof_span; +use std::sync::Arc; + +/// All the pipelines +pub struct Pipelines { + pub debug: debug::DebugPipeline, + pub figure: figure::FigurePipeline, + pub fluid: fluid::FluidPipeline, + pub lod_terrain: lod_terrain::LodTerrainPipeline, + pub particle: particle::ParticlePipeline, + pub clouds: clouds::CloudsPipeline, + pub postprocess: postprocess::PostProcessPipeline, + // Consider reenabling at some time + // player_shadow: figure::FigurePipeline, + pub skybox: skybox::SkyboxPipeline, + pub sprite: sprite::SpritePipeline, + pub terrain: terrain::TerrainPipeline, + pub ui: ui::UiPipeline, + pub blit: blit::BlitPipeline, +} + +/// Pipelines that are needed to render 3D stuff in-game +/// Use to decouple interface pipeline creation when initializing the renderer +pub struct IngamePipelines { + debug: debug::DebugPipeline, + figure: figure::FigurePipeline, + fluid: fluid::FluidPipeline, + lod_terrain: lod_terrain::LodTerrainPipeline, + particle: particle::ParticlePipeline, + clouds: clouds::CloudsPipeline, + postprocess: postprocess::PostProcessPipeline, + // Consider reenabling at some time + // player_shadow: figure::FigurePipeline, + skybox: skybox::SkyboxPipeline, + sprite: sprite::SpritePipeline, + terrain: terrain::TerrainPipeline, +} + +pub struct ShadowPipelines { + pub point: Option, + pub directed: Option, + pub figure: Option, +} + +pub struct IngameAndShadowPipelines { + pub ingame: IngamePipelines, + pub shadow: ShadowPipelines, +} + +/// Pipelines neccesary to display the UI and take screenshots +/// Use to decouple interface pipeline creation when initializing the renderer +pub struct InterfacePipelines { + pub ui: ui::UiPipeline, + pub blit: blit::BlitPipeline, +} + +impl Pipelines { + pub fn consolidate(interface: InterfacePipelines, ingame: IngamePipelines) -> Self { + Self { + debug: ingame.debug, + figure: ingame.figure, + fluid: ingame.fluid, + lod_terrain: ingame.lod_terrain, + particle: ingame.particle, + clouds: ingame.clouds, + postprocess: ingame.postprocess, + //player_shadow: ingame.player_shadow, + skybox: ingame.skybox, + sprite: ingame.sprite, + terrain: ingame.terrain, + ui: interface.ui, + blit: interface.blit, + } + } +} + +/// Processed shaders ready for use in pipeline creation +struct ShaderModules { + skybox_vert: wgpu::ShaderModule, + skybox_frag: wgpu::ShaderModule, + debug_vert: wgpu::ShaderModule, + debug_frag: wgpu::ShaderModule, + figure_vert: wgpu::ShaderModule, + figure_frag: wgpu::ShaderModule, + terrain_vert: wgpu::ShaderModule, + terrain_frag: wgpu::ShaderModule, + fluid_vert: wgpu::ShaderModule, + fluid_frag: wgpu::ShaderModule, + sprite_vert: wgpu::ShaderModule, + sprite_frag: wgpu::ShaderModule, + particle_vert: wgpu::ShaderModule, + particle_frag: wgpu::ShaderModule, + ui_vert: wgpu::ShaderModule, + ui_frag: wgpu::ShaderModule, + lod_terrain_vert: wgpu::ShaderModule, + lod_terrain_frag: wgpu::ShaderModule, + clouds_vert: wgpu::ShaderModule, + clouds_frag: wgpu::ShaderModule, + postprocess_vert: wgpu::ShaderModule, + postprocess_frag: wgpu::ShaderModule, + blit_vert: wgpu::ShaderModule, + blit_frag: wgpu::ShaderModule, + point_light_shadows_vert: wgpu::ShaderModule, + light_shadows_directed_vert: wgpu::ShaderModule, + light_shadows_figure_vert: wgpu::ShaderModule, +} + +impl ShaderModules { + pub fn new( + device: &wgpu::Device, + shaders: &Shaders, + mode: &RenderMode, + has_shadow_views: bool, + ) -> Result { + prof_span!(_guard, "ShaderModules::new"); + use shaderc::{CompileOptions, Compiler, OptimizationLevel, ResolvedInclude, ShaderKind}; + + let constants = shaders.get("include.constants").unwrap(); + let globals = shaders.get("include.globals").unwrap(); + let sky = shaders.get("include.sky").unwrap(); + let light = shaders.get("include.light").unwrap(); + let srgb = shaders.get("include.srgb").unwrap(); + let random = shaders.get("include.random").unwrap(); + let lod = shaders.get("include.lod").unwrap(); + let shadows = shaders.get("include.shadows").unwrap(); + + // We dynamically add extra configuration settings to the constants file. + let constants = format!( + r#" +{} + +#define VOXYGEN_COMPUTATION_PREFERENCE {} +#define FLUID_MODE {} +#define CLOUD_MODE {} +#define LIGHTING_ALGORITHM {} +#define SHADOW_MODE {} + +"#, + &constants.0, + // TODO: Configurable vertex/fragment shader preference. + "VOXYGEN_COMPUTATION_PREFERENCE_FRAGMENT", + match mode.fluid { + FluidMode::Cheap => "FLUID_MODE_CHEAP", + FluidMode::Shiny => "FLUID_MODE_SHINY", + }, + match mode.cloud { + CloudMode::None => "CLOUD_MODE_NONE", + CloudMode::Minimal => "CLOUD_MODE_MINIMAL", + CloudMode::Low => "CLOUD_MODE_LOW", + CloudMode::Medium => "CLOUD_MODE_MEDIUM", + CloudMode::High => "CLOUD_MODE_HIGH", + CloudMode::Ultra => "CLOUD_MODE_ULTRA", + }, + match mode.lighting { + LightingMode::Ashikhmin => "LIGHTING_ALGORITHM_ASHIKHMIN", + LightingMode::BlinnPhong => "LIGHTING_ALGORITHM_BLINN_PHONG", + LightingMode::Lambertian => "LIGHTING_ALGORITHM_LAMBERTIAN", + }, + match mode.shadow { + ShadowMode::None => "SHADOW_MODE_NONE", + ShadowMode::Map(_) if has_shadow_views => "SHADOW_MODE_MAP", + ShadowMode::Cheap | ShadowMode::Map(_) => "SHADOW_MODE_CHEAP", + }, + ); + + let anti_alias = shaders + .get(match mode.aa { + AaMode::None => "antialias.none", + AaMode::Fxaa => "antialias.fxaa", + AaMode::MsaaX4 => "antialias.msaa-x4", + AaMode::MsaaX8 => "antialias.msaa-x8", + AaMode::MsaaX16 => "antialias.msaa-x16", + }) + .unwrap(); + + let cloud = shaders + .get(match mode.cloud { + CloudMode::None => "include.cloud.none", + _ => "include.cloud.regular", + }) + .unwrap(); + + let mut compiler = Compiler::new().ok_or(RenderError::ErrorInitializingCompiler)?; + let mut options = CompileOptions::new().ok_or(RenderError::ErrorInitializingCompiler)?; + options.set_optimization_level(OptimizationLevel::Performance); + options.set_forced_version_profile(430, shaderc::GlslProfile::Core); + options.set_include_callback(move |name, _, shader_name, _| { + Ok(ResolvedInclude { + resolved_name: name.to_string(), + content: match name { + "constants.glsl" => constants.clone(), + "globals.glsl" => globals.0.to_owned(), + "shadows.glsl" => shadows.0.to_owned(), + "sky.glsl" => sky.0.to_owned(), + "light.glsl" => light.0.to_owned(), + "srgb.glsl" => srgb.0.to_owned(), + "random.glsl" => random.0.to_owned(), + "lod.glsl" => lod.0.to_owned(), + "anti-aliasing.glsl" => anti_alias.0.to_owned(), + "cloud.glsl" => cloud.0.to_owned(), + other => { + return Err(format!( + "Include {} in {} is not defined", + other, shader_name + )); + }, + }, + }) + }); + + let mut create_shader = |name, kind| { + let glsl = &shaders + .get(name) + .unwrap_or_else(|| panic!("Can't retrieve shader: {}", name)) + .0; + let file_name = format!("{}.glsl", name); + create_shader_module(device, &mut compiler, glsl, kind, &file_name, &options) + }; + + let selected_fluid_shader = ["fluid-frag.", match mode.fluid { + FluidMode::Cheap => "cheap", + FluidMode::Shiny => "shiny", + }] + .concat(); + + Ok(Self { + skybox_vert: create_shader("skybox-vert", ShaderKind::Vertex)?, + skybox_frag: create_shader("skybox-frag", ShaderKind::Fragment)?, + debug_vert: create_shader("debug-vert", ShaderKind::Vertex)?, + debug_frag: create_shader("debug-frag", ShaderKind::Fragment)?, + figure_vert: create_shader("figure-vert", ShaderKind::Vertex)?, + figure_frag: create_shader("figure-frag", ShaderKind::Fragment)?, + terrain_vert: create_shader("terrain-vert", ShaderKind::Vertex)?, + terrain_frag: create_shader("terrain-frag", ShaderKind::Fragment)?, + fluid_vert: create_shader("fluid-vert", ShaderKind::Vertex)?, + fluid_frag: create_shader(&selected_fluid_shader, ShaderKind::Fragment)?, + sprite_vert: create_shader("sprite-vert", ShaderKind::Vertex)?, + sprite_frag: create_shader("sprite-frag", ShaderKind::Fragment)?, + particle_vert: create_shader("particle-vert", ShaderKind::Vertex)?, + particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?, + ui_vert: create_shader("ui-vert", ShaderKind::Vertex)?, + ui_frag: create_shader("ui-frag", ShaderKind::Fragment)?, + lod_terrain_vert: create_shader("lod-terrain-vert", ShaderKind::Vertex)?, + lod_terrain_frag: create_shader("lod-terrain-frag", ShaderKind::Fragment)?, + clouds_vert: create_shader("clouds-vert", ShaderKind::Vertex)?, + clouds_frag: create_shader("clouds-frag", ShaderKind::Fragment)?, + postprocess_vert: create_shader("postprocess-vert", ShaderKind::Vertex)?, + postprocess_frag: create_shader("postprocess-frag", ShaderKind::Fragment)?, + blit_vert: create_shader("blit-vert", ShaderKind::Vertex)?, + blit_frag: create_shader("blit-frag", ShaderKind::Fragment)?, + point_light_shadows_vert: create_shader( + "point-light-shadows-vert", + ShaderKind::Vertex, + )?, + light_shadows_directed_vert: create_shader( + "light-shadows-directed-vert", + ShaderKind::Vertex, + )?, + light_shadows_figure_vert: create_shader( + "light-shadows-figure-vert", + ShaderKind::Vertex, + )?, + }) + } +} + +fn create_shader_module( + device: &wgpu::Device, + compiler: &mut shaderc::Compiler, + source: &str, + kind: shaderc::ShaderKind, + file_name: &str, + options: &shaderc::CompileOptions, +) -> Result { + prof_span!(_guard, "create_shader_modules"); + use std::borrow::Cow; + + let spv = compiler + .compile_into_spirv(source, kind, file_name, "main", Some(options)) + .map_err(|e| (file_name, e))?; + + let label = [file_name, "\n\n", source].concat(); + Ok(device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some(&label), + source: wgpu::ShaderSource::SpirV(Cow::Borrowed(spv.as_binary())), + flags: wgpu::ShaderFlags::empty(), + // TODO: renable // flags: wgpu::ShaderFlags::VALIDATION, + })) +} + +/// Things needed to create a pipeline +#[derive(Clone, Copy)] +struct PipelineNeeds<'a> { + device: &'a wgpu::Device, + layouts: &'a Layouts, + shaders: &'a ShaderModules, + mode: &'a RenderMode, + sc_desc: &'a wgpu::SwapChainDescriptor, +} + +/// Creates InterfacePipelines in parallel +fn create_interface_pipelines( + needs: PipelineNeeds, + pool: &rayon::ThreadPool, + tasks: [Task; 2], +) -> InterfacePipelines { + prof_span!(_guard, "create_interface_pipelines"); + + let [ui_task, blit_task] = tasks; + // Construct a pipeline for rendering UI elements + let create_ui = || { + ui_task.run( + || { + ui::UiPipeline::new( + needs.device, + &needs.shaders.ui_vert, + &needs.shaders.ui_frag, + needs.sc_desc, + &needs.layouts.global, + &needs.layouts.ui, + ) + }, + "ui pipeline creation", + ) + }; + + // Construct a pipeline for blitting, used during screenshotting + let create_blit = || { + blit_task.run( + || { + blit::BlitPipeline::new( + needs.device, + &needs.shaders.blit_vert, + &needs.shaders.blit_frag, + needs.sc_desc, + &needs.layouts.blit, + ) + }, + "blit pipeline creation", + ) + }; + + let (ui, blit) = pool.join(create_ui, create_blit); + + InterfacePipelines { ui, blit } +} + +/// Create IngamePipelines and shadow pipelines in parallel +fn create_ingame_and_shadow_pipelines( + needs: PipelineNeeds, + pool: &rayon::ThreadPool, + tasks: [Task; 13], +) -> IngameAndShadowPipelines { + prof_span!(_guard, "create_ingame_and_shadow_pipelines"); + + let PipelineNeeds { + device, + layouts, + shaders, + mode, + sc_desc, + } = needs; + + let [ + debug_task, + skybox_task, + figure_task, + terrain_task, + fluid_task, + sprite_task, + particle_task, + lod_terrain_task, + clouds_task, + postprocess_task, + // TODO: if these are ever actually optionally done, counting them + // as tasks to do beforehand seems kind of iffy since they will just + // be skipped + point_shadow_task, + terrain_directed_shadow_task, + figure_directed_shadow_task, + ] = tasks; + + // TODO: pass in format of target color buffer + + // Pipeline for rendering debug shapes + let create_debug = || { + debug_task.run( + || { + debug::DebugPipeline::new( + device, + &shaders.debug_vert, + &shaders.debug_frag, + &layouts.global, + &layouts.debug, + mode.aa, + ) + }, + "debug pipeline creation", + ) + }; + // Pipeline for rendering skyboxes + let create_skybox = || { + skybox_task.run( + || { + skybox::SkyboxPipeline::new( + device, + &shaders.skybox_vert, + &shaders.skybox_frag, + &layouts.global, + mode.aa, + ) + }, + "skybox pipeline creation", + ) + }; + // Pipeline for rendering figures + let create_figure = || { + figure_task.run( + || { + figure::FigurePipeline::new( + device, + &shaders.figure_vert, + &shaders.figure_frag, + &layouts.global, + &layouts.figure, + mode.aa, + ) + }, + "figure pipeline creation", + ) + }; + // Pipeline for rendering terrain + let create_terrain = || { + terrain_task.run( + || { + terrain::TerrainPipeline::new( + device, + &shaders.terrain_vert, + &shaders.terrain_frag, + &layouts.global, + &layouts.terrain, + mode.aa, + ) + }, + "terrain pipeline creation", + ) + }; + // Pipeline for rendering fluids + let create_fluid = || { + fluid_task.run( + || { + fluid::FluidPipeline::new( + device, + &shaders.fluid_vert, + &shaders.fluid_frag, + &layouts.global, + &layouts.terrain, + mode.aa, + ) + }, + "fluid pipeline creation", + ) + }; + // Pipeline for rendering sprites + let create_sprite = || { + sprite_task.run( + || { + sprite::SpritePipeline::new( + device, + &shaders.sprite_vert, + &shaders.sprite_frag, + &layouts.global, + &layouts.sprite, + &layouts.terrain, + mode.aa, + ) + }, + "sprite pipeline creation", + ) + }; + // Pipeline for rendering particles + let create_particle = || { + particle_task.run( + || { + particle::ParticlePipeline::new( + device, + &shaders.particle_vert, + &shaders.particle_frag, + &layouts.global, + mode.aa, + ) + }, + "particle pipeline creation", + ) + }; + // Pipeline for rendering terrain + let create_lod_terrain = || { + lod_terrain_task.run( + || { + lod_terrain::LodTerrainPipeline::new( + device, + &shaders.lod_terrain_vert, + &shaders.lod_terrain_frag, + &layouts.global, + mode.aa, + ) + }, + "lod terrain pipeline creation", + ) + }; + // Pipeline for rendering our clouds (a kind of post-processing) + let create_clouds = || { + clouds_task.run( + || { + clouds::CloudsPipeline::new( + device, + &shaders.clouds_vert, + &shaders.clouds_frag, + &layouts.global, + &layouts.clouds, + mode.aa, + ) + }, + "clouds pipeline creation", + ) + }; + // Pipeline for rendering our post-processing + let create_postprocess = || { + postprocess_task.run( + || { + postprocess::PostProcessPipeline::new( + device, + &shaders.postprocess_vert, + &shaders.postprocess_frag, + sc_desc, + &layouts.global, + &layouts.postprocess, + ) + }, + "postprocess pipeline creation", + ) + }; + + // + // // Pipeline for rendering the player silhouette + // let player_shadow_pipeline = create_pipeline( + // factory, + // figure::pipe::Init { + // tgt_depth: (gfx::preset::depth::PASS_TEST/*, + // Stencil::new( + // Comparison::Equal, + // 0xff, + // (StencilOp::Keep, StencilOp::Keep, StencilOp::Keep), + // ),*/), + // ..figure::pipe::new() + // }, + // &figure_vert, + // &Glsl::load_watched( + // "voxygen.shaders.player-shadow-frag", + // shader_reload_indicator, + // ) + // .unwrap(), + // &include_ctx, + // gfx::state::CullFace::Back, + // )?; + + // Pipeline for rendering point light terrain shadow maps. + let create_point_shadow = || { + point_shadow_task.run( + || { + shadow::PointShadowPipeline::new( + device, + &shaders.point_light_shadows_vert, + &layouts.global, + &layouts.terrain, + mode.aa, + ) + }, + "point shadow pipeline creation", + ) + }; + // Pipeline for rendering directional light terrain shadow maps. + let create_terrain_directed_shadow = || { + terrain_directed_shadow_task.run( + || { + shadow::ShadowPipeline::new( + device, + &shaders.light_shadows_directed_vert, + &layouts.global, + &layouts.terrain, + mode.aa, + ) + }, + "terrain directed shadow pipeline creation", + ) + }; + // Pipeline for rendering directional light figure shadow maps. + let create_figure_directed_shadow = || { + figure_directed_shadow_task.run( + || { + shadow::ShadowFigurePipeline::new( + device, + &shaders.light_shadows_figure_vert, + &layouts.global, + &layouts.figure, + mode.aa, + ) + }, + "figure directed shadow pipeline creation", + ) + }; + + let j1 = || pool.join(create_debug, || pool.join(create_skybox, create_figure)); + let j2 = || pool.join(create_terrain, create_fluid); + let j3 = || pool.join(create_sprite, create_particle); + let j4 = || pool.join(create_lod_terrain, create_clouds); + let j5 = || pool.join(create_postprocess, create_point_shadow); + let j6 = || { + pool.join( + create_terrain_directed_shadow, + create_figure_directed_shadow, + ) + }; + + // Ignore this + let ( + ( + ((debug, (skybox, figure)), (terrain, fluid)), + ((sprite, particle), (lod_terrain, clouds)), + ), + ((postprocess, point_shadow), (terrain_directed_shadow, figure_directed_shadow)), + ) = pool.join( + || pool.join(|| pool.join(j1, j2), || pool.join(j3, j4)), + || pool.join(j5, j6), + ); + + IngameAndShadowPipelines { + ingame: IngamePipelines { + debug, + figure, + fluid, + lod_terrain, + particle, + clouds, + postprocess, + skybox, + sprite, + terrain, + // player_shadow_pipeline, + }, + // TODO: skip creating these if the shadow map setting is not enabled + shadow: ShadowPipelines { + point: Some(point_shadow), + directed: Some(terrain_directed_shadow), + figure: Some(figure_directed_shadow), + }, + } +} + +/// Creates all the pipelines used to render. +/// Use this for the initial creation. +/// It blocks the main thread to create the interface pipelines while moving the +/// creation of other pipelines into the background +/// NOTE: this tries to use all the CPU cores to complete as soon as possible +pub(super) fn initial_create_pipelines( + device: Arc, + layouts: Arc, + shaders: Shaders, + mode: RenderMode, + sc_desc: wgpu::SwapChainDescriptor, + has_shadow_views: bool, +) -> Result< + ( + InterfacePipelines, + PipelineCreation, + ), + RenderError, +> { + prof_span!(_guard, "initial_create_pipelines"); + + // Process shaders into modules + let shader_modules = ShaderModules::new(&device, &shaders, &mode, has_shadow_views)?; + + // Create threadpool for parallel portion + let pool = rayon::ThreadPoolBuilder::new() + .thread_name(|n| format!("pipeline-creation-{}", n)) + .build() + .unwrap(); + + let needs = PipelineNeeds { + device: &device, + layouts: &layouts, + shaders: &shader_modules, + mode: &mode, + sc_desc: &sc_desc, + }; + + // Create interface pipelines while blocking the main thread + // Note: we use a throwaway Progress tracker here since we don't need to track + // the progress + let interface_pipelines = + create_interface_pipelines(needs, &pool, Progress::new().create_tasks()); + + let pool = Arc::new(pool); + let send_pool = Arc::clone(&pool); + // Track pipeline creation progress + let progress = Arc::new(Progress::new()); + let (pipeline_send, pipeline_recv) = crossbeam_channel::bounded(0); + let pipeline_creation = PipelineCreation { + progress: Arc::clone(&progress), + recv: pipeline_recv, + }; + // Start background compilation + pool.spawn(move || { + let pool = &*send_pool; + + let needs = PipelineNeeds { + device: &device, + layouts: &layouts, + shaders: &shader_modules, + mode: &mode, + sc_desc: &sc_desc, + }; + + let pipelines = create_ingame_and_shadow_pipelines(needs, &pool, progress.create_tasks()); + + pipeline_send.send(pipelines).expect("Channel disconnected"); + }); + + Ok((interface_pipelines, pipeline_creation)) +} + +/// Creates all the pipelines used to render. +/// Use this to recreate all the pipelines in the background. +/// TODO: report progress +/// NOTE: this tries to use all the CPU cores to complete as soon as possible +pub(super) fn recreate_pipelines( + device: Arc, + layouts: Arc, + shaders: Shaders, + mode: RenderMode, + sc_desc: wgpu::SwapChainDescriptor, + has_shadow_views: bool, +) -> PipelineCreation> { + prof_span!(_guard, "recreate_pipelines"); + + // Create threadpool for parallel portion + let pool = rayon::ThreadPoolBuilder::new() + .thread_name(|n| format!("pipeline-recreation-{}", n)) + .build() + .unwrap(); + let pool = Arc::new(pool); + let send_pool = Arc::clone(&pool); + // Track pipeline creation progress + let progress = Arc::new(Progress::new()); + let (result_send, result_recv) = crossbeam_channel::bounded(0); + let pipeline_creation = PipelineCreation { + progress: Arc::clone(&progress), + recv: result_recv, + }; + // Start background compilation + pool.spawn(move || { + let pool = &*send_pool; + + // Create tasks upfront so the total counter will be accurate + let shader_task = progress.create_task(); + let interface_tasks = progress.create_tasks(); + let ingame_and_shadow_tasks = progress.create_tasks(); + + // Process shaders into modules + let guard = shader_task.start("process shaders"); + let shader_modules = match ShaderModules::new(&device, &shaders, &mode, has_shadow_views) { + Ok(modules) => modules, + Err(err) => { + result_send.send(Err(err)).expect("Channel disconnected"); + return; + }, + }; + drop(guard); + + let needs = PipelineNeeds { + device: &device, + layouts: &layouts, + shaders: &shader_modules, + mode: &mode, + sc_desc: &sc_desc, + }; + + // Create interface pipelines + let interface = create_interface_pipelines(needs, &pool, interface_tasks); + + // Create the rest of the pipelines + let IngameAndShadowPipelines { ingame, shadow } = + create_ingame_and_shadow_pipelines(needs, &pool, ingame_and_shadow_tasks); + + // Send them + result_send + .send(Ok((Pipelines::consolidate(interface, ingame), shadow))) + .expect("Channel disconnected"); + }); + + pipeline_creation +} + +use core::sync::atomic::{AtomicUsize, Ordering}; + +/// Represents future task that has not been started +/// Dropping this will mark the task as complete though +struct Task<'a> { + progress: &'a Progress, +} + +/// Represents in-progress task, drop when complete +// NOTE: fields are unused because they are only used for their Drop impls +struct StartedTask<'a> { + _span: common_base::ProfSpan, + _task: Task<'a>, +} + +#[derive(Default)] +struct Progress { + total: AtomicUsize, + complete: AtomicUsize, + // Note: could easily add a "started counter" if that would be useful +} + +impl Progress { + pub fn new() -> Self { Self::default() } + + /// Creates a task incrementing the total number of tasks + /// NOTE: all tasks should be created as upfront as possible so that the + /// total reflects the amount of tasks that will need to be completed + pub fn create_task(&self) -> Task { + self.total.fetch_add(1, Ordering::Relaxed); + Task { progress: &self } + } + + /// Helper method for creating tasks to do in bulk + pub fn create_tasks(&self) -> [Task; N] { [(); N].map(|()| self.create_task()) } +} + +impl<'a> Task<'a> { + /// Start a task. + /// The name is used for profiling. + fn start(self, _name: &str) -> StartedTask<'a> { + // _name only used when tracy feature is activated + StartedTask { + _span: { + prof_span!(guard, _name); + guard + }, + _task: self, + } + } + + /// Convenience function to run the provided closure as the task + /// Completing the task when this function returns + fn run(self, task: impl FnOnce() -> T, name: &str) -> T { + let _guard = self.start(name); + task() + } +} + +impl Drop for Task<'_> { + fn drop(&mut self) { self.progress.complete.fetch_add(1, Ordering::Relaxed); } +} + +pub struct PipelineCreation { + progress: Arc, + recv: crossbeam_channel::Receiver, +} + +impl PipelineCreation { + /// Returns the number of pipelines being built and completed + /// (total, complete) + /// NOTE: there is no guarantee that `total >= complete` due to relaxed + /// atomics but this property should hold most of the time + pub fn status(&self) -> (usize, usize) { + let progress = &*self.progress; + ( + progress.total.load(Ordering::Relaxed), + progress.complete.load(Ordering::Relaxed), + ) + } + + /// Checks if the pipelines were completed and returns the result if they + /// were + pub fn try_complete(self) -> Result { + use crossbeam_channel::TryRecvError; + match self.recv.try_recv() { + // Yay! + Ok(t) => Ok(t), + // Normal error, we have not gotten anything yet + Err(TryRecvError::Empty) => Err(self), + // How rude! + Err(TryRecvError::Disconnected) => { + panic!( + "Background thread panicked or dropped the sender without sending anything!" + ); + }, + } + } +} diff --git a/voxygen/src/render/renderer/screenshot.rs b/voxygen/src/render/renderer/screenshot.rs new file mode 100644 index 0000000000..484b83ef4d --- /dev/null +++ b/voxygen/src/render/renderer/screenshot.rs @@ -0,0 +1,185 @@ +use super::super::pipelines::blit; +use tracing::error; + +pub type ScreenshotFn = Box; + +pub struct TakeScreenshot { + bind_group: blit::BindGroup, + view: wgpu::TextureView, + texture: wgpu::Texture, + buffer: wgpu::Buffer, + screenshot_fn: ScreenshotFn, + // Dimensions used for copying from the screenshot texture to a buffer + width: u32, + height: u32, + bytes_per_pixel: u8, +} + +impl TakeScreenshot { + pub fn new( + device: &wgpu::Device, + blit_layout: &blit::BlitLayout, + sampler: &wgpu::Sampler, + // Used to determine the resolution and texture format + sc_desc: &wgpu::SwapChainDescriptor, + // Function that is given the image after downloading it from the GPU + // This is executed in a background thread + screenshot_fn: ScreenshotFn, + ) -> Self { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("screenshot tex"), + size: wgpu::Extent3d { + width: sc_desc.width, + height: sc_desc.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: sc_desc.format, + usage: wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED + | wgpu::TextureUsage::RENDER_ATTACHMENT, + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label: Some("screenshot tex view"), + format: Some(sc_desc.format), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }); + + let bind_group = blit_layout.bind(device, &view, sampler); + + let bytes_per_pixel = sc_desc.format.describe().block_size; + let padded_bytes_per_row = padded_bytes_per_row(sc_desc.width, bytes_per_pixel); + + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("screenshot download buffer"), + size: (padded_bytes_per_row * sc_desc.height) as u64, + usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::MAP_READ, + mapped_at_creation: false, + }); + + Self { + bind_group, + texture, + view, + buffer, + screenshot_fn, + width: sc_desc.width, + height: sc_desc.height, + bytes_per_pixel, + } + } + + /// Get the texture view for the screenshot + /// This can then be used as a render attachment + pub fn texture_view(&self) -> &wgpu::TextureView { &self.view } + + /// Get the bind group used for blitting the screenshot to the current + /// swapchain image + pub fn bind_group(&self) -> &wgpu::BindGroup { &self.bind_group.bind_group } + + /// NOTE: spawns thread + /// Call this after rendering to the screenshot texture + pub fn download_and_handle(self, encoder: &mut wgpu::CommandEncoder) { + // Calculate padded bytes per row + let padded_bytes_per_row = padded_bytes_per_row(self.width, self.bytes_per_pixel); + // Copy image to a buffer + encoder.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture: &self.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + wgpu::ImageCopyBuffer { + buffer: &self.buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: core::num::NonZeroU32::new(padded_bytes_per_row), + rows_per_image: None, + }, + }, + wgpu::Extent3d { + width: self.width, + height: self.height, + depth_or_array_layers: 1, + }, + ); + // Send buffer to another thread for async mapping, downloading, and passing to + // the given handler function (which probably saves it to the disk) + std::thread::Builder::new() + .name("screenshot".into()) + .spawn(move || { + self.download_and_handle_internal(); + }) + .expect("Failed to spawn screenshot thread"); + } + + fn download_and_handle_internal(self) { + // Calculate padded bytes per row + let padded_bytes_per_row = padded_bytes_per_row(self.width, self.bytes_per_pixel); + let singlethread_rt = match tokio::runtime::Builder::new_current_thread().build() { + Ok(rt) => rt, + Err(err) => { + error!(?err, "Could not create tokio runtime"); + return; + }, + }; + + // Map buffer + let buffer_slice = self.buffer.slice(..); + let buffer_map_future = buffer_slice.map_async(wgpu::MapMode::Read); + + // Wait on buffer mapping + let pixel_bytes = match singlethread_rt.block_on(buffer_map_future) { + // Buffer is mapped and we can read it + Ok(()) => { + // Copy to a Vec + let padded_buffer = buffer_slice.get_mapped_range(); + let mut pixel_bytes = Vec::new(); + padded_buffer + .chunks(padded_bytes_per_row as usize) + .map(|padded_chunk| { + &padded_chunk[..self.width as usize * self.bytes_per_pixel as usize] + }) + .for_each(|row| pixel_bytes.extend_from_slice(row)); + pixel_bytes + }, + // Error + Err(err) => { + error!( + ?err, + "Failed to map buffer for downloading a screenshot from the GPU" + ); + return; + }, + }; + + // Construct image + // TODO: support other formats + let image = image::ImageBuffer::, Vec>::from_vec( + self.width, + self.height, + pixel_bytes, + ) + .expect("Failed to create ImageBuffer! Buffer was not large enough. This should not occur"); + let image = image::DynamicImage::ImageBgra8(image); + + // Call supplied handler + (self.screenshot_fn)(image); + } +} + +// Graphics API requires a specific alignment for buffer copies +fn padded_bytes_per_row(width: u32, bytes_per_pixel: u8) -> u32 { + let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; + let unpadded_bytes_per_row = width * bytes_per_pixel as u32; + let padding = (align - unpadded_bytes_per_row % align) % align; + unpadded_bytes_per_row + padding +} diff --git a/voxygen/src/render/renderer/shaders.rs b/voxygen/src/render/renderer/shaders.rs new file mode 100644 index 0000000000..eb3a148a89 --- /dev/null +++ b/voxygen/src/render/renderer/shaders.rs @@ -0,0 +1,96 @@ +use common::assets::{self, AssetExt, AssetHandle}; +use hashbrown::HashMap; + +/// Load from a GLSL file. +pub struct Glsl(pub String); + +impl From for Glsl { + fn from(s: String) -> Glsl { Glsl(s) } +} + +impl assets::Asset for Glsl { + type Loader = assets::LoadFrom; + + const EXTENSION: &'static str = "glsl"; +} + +// Note: we use this clone to send the shaders to a background thread +// TODO: use Arc-ed asset and clone that instead +#[derive(Clone)] +pub struct Shaders { + shaders: HashMap>, +} + +impl assets::Compound for Shaders { + // TODO: Taking the specifier argument as a base for shaders specifiers + // would allow to use several shaders groups easily + fn load( + _: &assets::AssetCache, + _: &str, + ) -> Result { + let shaders = [ + "include.constants", + "include.globals", + "include.sky", + "include.light", + "include.srgb", + "include.random", + "include.lod", + "include.shadows", + "antialias.none", + "antialias.fxaa", + "antialias.msaa-x4", + "antialias.msaa-x8", + "antialias.msaa-x16", + "include.cloud.none", + "include.cloud.regular", + "figure-vert", + "light-shadows-figure-vert", + "light-shadows-directed-vert", + "point-light-shadows-vert", + "skybox-vert", + "skybox-frag", + "debug-vert", + "debug-frag", + "figure-frag", + "terrain-vert", + "terrain-frag", + "fluid-vert", + "fluid-frag.cheap", + "fluid-frag.shiny", + "sprite-vert", + "sprite-frag", + "particle-vert", + "particle-frag", + "ui-vert", + "ui-frag", + "lod-terrain-vert", + "lod-terrain-frag", + "clouds-vert", + "clouds-frag", + "postprocess-vert", + "postprocess-frag", + "blit-vert", + "blit-frag", + //"player-shadow-frag", + //"light-shadows-geom", + ]; + + let shaders = shaders + .iter() + .map(|shader| { + let full_specifier = ["voxygen.shaders.", shader].concat(); + let asset = AssetExt::load(&full_specifier)?; + Ok((String::from(*shader), asset)) + }) + .collect::, assets::Error>>()?; + + Ok(Self { shaders }) + } +} + +impl Shaders { + pub fn get(&self, shader: &str) -> Option> { + self.shaders.get(shader).map(|a| a.read()) + } +} diff --git a/voxygen/src/render/renderer/shadow_map.rs b/voxygen/src/render/renderer/shadow_map.rs new file mode 100644 index 0000000000..b7361adebe --- /dev/null +++ b/voxygen/src/render/renderer/shadow_map.rs @@ -0,0 +1,278 @@ +use super::{ + super::{pipelines::shadow, texture::Texture, RenderError, ShadowMapMode}, + Renderer, +}; +use vek::*; + +/// A type that holds shadow map data. Since shadow mapping may not be +/// supported on all platforms, we try to keep it separate. +pub struct ShadowMapRenderer { + pub directed_depth: Texture, + + pub point_depth: Texture, + + pub point_pipeline: shadow::PointShadowPipeline, + pub terrain_directed_pipeline: shadow::ShadowPipeline, + pub figure_directed_pipeline: shadow::ShadowFigurePipeline, + pub layout: shadow::ShadowLayout, +} + +pub enum ShadowMap { + Enabled(ShadowMapRenderer), + Disabled { + dummy_point: Texture, // Cube texture + dummy_directed: Texture, + }, +} + +impl ShadowMap { + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + point: Option, + directed: Option, + figure: Option, + shadow_views: Option<(Texture, Texture)>, + ) -> Self { + if let ( + Some(point_pipeline), + Some(terrain_directed_pipeline), + Some(figure_directed_pipeline), + Some(shadow_views), + ) = (point, directed, figure, shadow_views) + { + let (point_depth, directed_depth) = shadow_views; + + let layout = shadow::ShadowLayout::new(&device); + + Self::Enabled(ShadowMapRenderer { + directed_depth, + point_depth, + + point_pipeline, + terrain_directed_pipeline, + figure_directed_pipeline, + + layout, + }) + } else { + let (dummy_point, dummy_directed) = Self::create_dummy_shadow_tex(&device, &queue); + Self::Disabled { + dummy_point, + dummy_directed, + } + } + } + + fn create_dummy_shadow_tex(device: &wgpu::Device, queue: &wgpu::Queue) -> (Texture, Texture) { + let make_tex = |view_dim, depth| { + let tex = wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 4, + height: 4, + depth_or_array_layers: depth, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24Plus, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, + }; + + let view = wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Depth24Plus), + dimension: Some(view_dim), + aspect: wgpu::TextureAspect::DepthOnly, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }; + + let sampler_info = wgpu::SamplerDescriptor { + label: None, + 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::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + compare: Some(wgpu::CompareFunction::LessEqual), + ..Default::default() + }; + + Texture::new_raw(device, &tex, &view, &sampler_info) + }; + + let cube_tex = make_tex(wgpu::TextureViewDimension::Cube, 6); + let tex = make_tex(wgpu::TextureViewDimension::D2, 1); + + // Clear to 1.0 + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Dummy shadow tex clearing encoder"), + }); + let mut clear = |tex: &Texture| { + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Clear dummy shadow texture"), + color_attachments: &[], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &tex.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), + }); + }; + clear(&cube_tex); + clear(&tex); + drop(clear); + queue.submit(std::iter::once(encoder.finish())); + + (cube_tex, tex) + } + + /// Create textures and views for shadow maps. + /// Returns (point, directed) + pub(super) fn create_shadow_views( + device: &wgpu::Device, + size: (u32, u32), + mode: &ShadowMapMode, + ) -> Result<(Texture, Texture), RenderError> { + // (Attempt to) apply resolution factor to shadow map resolution. + let resolution_factor = mode.resolution.clamped(0.25, 4.0); + + let max_texture_size = Renderer::max_texture_size_raw(device); + // Limit to max texture size, rather than erroring. + let size = Vec2::new(size.0, size.1).map(|e| { + let size = e as f32 * resolution_factor; + // NOTE: We know 0 <= e since we clamped the resolution factor to be between + // 0.25 and 4.0. + if size <= max_texture_size as f32 { + size as u32 + } else { + max_texture_size + } + }); + + let levels = 1; + // Limit to max texture size rather than erroring. + let two_size = size.map(|e| { + u32::checked_next_power_of_two(e) + .filter(|&e| e <= max_texture_size) + .unwrap_or(max_texture_size) + }); + let min_size = size.reduce_min(); + let max_size = size.reduce_max(); + let _min_two_size = two_size.reduce_min(); + let _max_two_size = two_size.reduce_max(); + // For rotated shadow maps, the maximum size of a pixel along any axis is the + // size of a diagonal along that axis. + let diag_size = size.map(f64::from).magnitude(); + let diag_cross_size = f64::from(min_size) / f64::from(max_size) * diag_size; + let (diag_size, _diag_cross_size) = + if 0.0 < diag_size && diag_size <= f64::from(max_texture_size) { + // NOTE: diag_cross_size must be non-negative, since it is the ratio of a + // non-negative and a positive number (if max_size were zero, + // diag_size would be 0 too). And it must be <= diag_size, + // since min_size <= max_size. Therefore, if diag_size fits in a + // u16, so does diag_cross_size. + (diag_size as u32, diag_cross_size as u32) + } else { + // Limit to max texture resolution rather than error. + (max_texture_size as u32, max_texture_size as u32) + }; + let diag_two_size = u32::checked_next_power_of_two(diag_size) + .filter(|&e| e <= max_texture_size) + // Limit to max texture resolution rather than error. + .unwrap_or(max_texture_size); + + let point_shadow_tex = wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: diag_two_size / 4, + height: diag_two_size / 4, + depth_or_array_layers: 6, + }, + mip_level_count: levels, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24Plus, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, + }; + + let point_shadow_view = wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Depth24Plus), + dimension: Some(wgpu::TextureViewDimension::Cube), + aspect: wgpu::TextureAspect::DepthOnly, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }; + + let directed_shadow_tex = wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: diag_two_size, + height: diag_two_size, + depth_or_array_layers: 1, + }, + mip_level_count: levels, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24Plus, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, + }; + + let directed_shadow_view = wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Depth24Plus), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::DepthOnly, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }; + + let sampler_info = wgpu::SamplerDescriptor { + label: None, + 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::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + compare: Some(wgpu::CompareFunction::LessEqual), + ..Default::default() + }; + + let point_shadow_tex = + Texture::new_raw(device, &point_shadow_tex, &point_shadow_view, &sampler_info); + let directed_shadow_tex = Texture::new_raw( + device, + &directed_shadow_tex, + &directed_shadow_view, + &sampler_info, + ); + + Ok((point_shadow_tex, directed_shadow_tex)) + } + + pub fn textures(&self) -> (&Texture, &Texture) { + match self { + Self::Enabled(renderer) => (&renderer.point_depth, &renderer.directed_depth), + Self::Disabled { + dummy_point, + dummy_directed, + } => (dummy_point, dummy_directed), + } + } + + pub fn is_enabled(&self) -> bool { matches!(self, Self::Enabled(_)) } +} diff --git a/voxygen/src/render/texture.rs b/voxygen/src/render/texture.rs index ae9edbced1..e15911316e 100644 --- a/voxygen/src/render/texture.rs +++ b/voxygen/src/render/texture.rs @@ -1,186 +1,236 @@ -use super::{gfx_backend, RenderError}; -use gfx::{self, traits::Factory}; +use super::RenderError; +use core::num::NonZeroU32; use image::{DynamicImage, GenericImageView}; -use vek::Vec2; - -type DefaultShaderFormat = (gfx::format::R8_G8_B8_A8, gfx::format::Srgb); +use wgpu::Extent3d; /// Represents an image that has been uploaded to the GPU. -#[derive(Clone)] -pub struct Texture -where - F::Surface: gfx::format::TextureSurface, - F::Channel: gfx::format::TextureChannel, - ::DataType: Copy, -{ - pub tex: gfx::handle::Texture::Surface>, - pub srv: gfx::handle::ShaderResourceView< - gfx_backend::Resources, - ::View, - >, - pub sampler: gfx::handle::Sampler, +pub struct Texture { + pub tex: wgpu::Texture, + pub view: wgpu::TextureView, + pub sampler: wgpu::Sampler, + size: Extent3d, + /// TODO: consider making Texture generic over the format + format: wgpu::TextureFormat, } -impl Texture -where - F::Surface: gfx::format::TextureSurface, - F::Channel: gfx::format::TextureChannel, - ::DataType: Copy, -{ +impl Texture { pub fn new( - factory: &mut gfx_backend::Factory, + device: &wgpu::Device, + queue: &wgpu::Queue, image: &DynamicImage, - filter_method: Option, - wrap_mode: Option, - border: Option, + filter_method: Option, + address_mode: Option, ) -> Result { - // TODO: Actualy handle images that aren't in rgba format properly. + let format = match &image { + DynamicImage::ImageLuma8(_) => wgpu::TextureFormat::R8Unorm, + DynamicImage::ImageLumaA8(_) => panic!("ImageLuma8 unsupported"), + DynamicImage::ImageRgb8(_) => panic!("ImageRgb8 unsupported"), + DynamicImage::ImageRgba8(_) => wgpu::TextureFormat::Rgba8UnormSrgb, + DynamicImage::ImageBgr8(_) => panic!("ImageBgr8 unsupported"), + DynamicImage::ImageBgra8(_) => panic!("ImageBgra8 unsupported"), + DynamicImage::ImageLuma16(_) => panic!("ImageLuma16 unsupported"), + DynamicImage::ImageLumaA16(_) => panic!("ImageLumaA16 unsupported"), + DynamicImage::ImageRgb16(_) => panic!("ImageRgb16 unsupported"), + DynamicImage::ImageRgba16(_) => panic!("ImageRgba16 unsupported"), + }; + + // TODO: Actually handle images that aren't in rgba format properly. let buffer = image.as_flat_samples_u8().ok_or_else(|| { RenderError::CustomError( "We currently do not support color formats using more than 4 bytes / pixel.".into(), ) })?; - let (tex, srv) = factory - .create_texture_immutable_u8::( - gfx::texture::Kind::D2( - image.width() as u16, - image.height() as u16, - gfx::texture::AaMode::Single, - ), - gfx::texture::Mipmap::Provided, - // Guarenteed to be correct, since all the conversions from DynamicImage to - // FlatSamples go through the underlying ImageBuffer's implementation of - // as_flat_samples(), which guarantees that the resulting FlatSamples is - // well-formed. - &[buffer.as_slice()], - ) - .map_err(RenderError::CombinedError)?; - let mut sampler_info = gfx::texture::SamplerInfo::new( - filter_method.unwrap_or(gfx::texture::FilterMethod::Scale), - wrap_mode.unwrap_or(gfx::texture::WrapMode::Clamp), + let bytes_per_pixel = u32::from(buffer.layout.channels); + + let size = Extent3d { + width: image.width(), + height: image.height(), + depth_or_array_layers: 1, + }; + + let tex = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }); + + queue.write_texture( + wgpu::ImageCopyTexture { + texture: &tex, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + buffer.as_slice(), + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(image.width() * bytes_per_pixel), + rows_per_image: NonZeroU32::new(image.height()), + }, + wgpu::Extent3d { + width: image.width(), + height: image.height(), + depth_or_array_layers: 1, + }, ); - let transparent = [0.0, 0.0, 0.0, 1.0].into(); - sampler_info.border = border.unwrap_or(transparent); + + let sampler_info = wgpu::SamplerDescriptor { + label: None, + address_mode_u: address_mode.unwrap_or(wgpu::AddressMode::ClampToEdge), + address_mode_v: address_mode.unwrap_or(wgpu::AddressMode::ClampToEdge), + address_mode_w: address_mode.unwrap_or(wgpu::AddressMode::ClampToEdge), + mag_filter: filter_method.unwrap_or(wgpu::FilterMode::Nearest), + min_filter: filter_method.unwrap_or(wgpu::FilterMode::Nearest), + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }; + + let view = tex.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: Some(format), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }); + Ok(Self { tex, - srv, - sampler: factory.create_sampler(sampler_info), + view, + sampler: device.create_sampler(&sampler_info), + size, + format, }) } pub fn new_dynamic( - factory: &mut gfx_backend::Factory, - width: u16, - height: u16, - ) -> Result { - let tex = factory.create_texture( - gfx::texture::Kind::D2( - width, - height, - gfx::texture::AaMode::Single, - ), - 1_u8, - gfx::memory::Bind::SHADER_RESOURCE, - gfx::memory::Usage::Dynamic, - Some(<::Channel as gfx::format::ChannelTyped>::get_channel_type()), - ) - .map_err(|err| RenderError::CombinedError(gfx::CombinedError::Texture(err)))?; + device: &wgpu::Device, + queue: &wgpu::Queue, + width: u32, + height: u32, + ) -> Self { + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; - let srv = factory - .view_texture_as_shader_resource::(&tex, (0, 0), gfx::format::Swizzle::new()) - .map_err(|err| RenderError::CombinedError(gfx::CombinedError::Resource(err)))?; + let tex_info = wgpu::TextureDescriptor { + label: None, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + // TODO: nondynamic version doesn't seeem to have different usage, unify code? + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }; - Ok(Self { - tex, - srv, - sampler: factory.create_sampler(gfx::texture::SamplerInfo::new( - gfx::texture::FilterMethod::Scale, - gfx::texture::WrapMode::Clamp, - )), - }) - } - - pub fn new_immutable_raw( - factory: &mut gfx_backend::Factory, - kind: gfx::texture::Kind, - mipmap: gfx::texture::Mipmap, - data: &[&[::DataType]], - sampler_info: gfx::texture::SamplerInfo, - ) -> Result { - let (tex, srv) = factory - .create_texture_immutable::(kind, mipmap, data) - .map_err(RenderError::CombinedError)?; - - Ok(Self { - tex, - srv, - sampler: factory.create_sampler(sampler_info), - }) + let sampler_info = wgpu::SamplerDescriptor { + label: None, + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }; + + let view_info = wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Rgba8UnormSrgb), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }; + + let texture = Self::new_raw(device, &tex_info, &view_info, &sampler_info); + texture.clear(queue); // Needs to be fully initialized for partial writes to work on Dx12 AMD + texture } + /// Note: the user is responsible for making sure the texture is fully + /// initialized before doing partial writes on Dx12 AMD: https://github.com/gfx-rs/wgpu/issues/1306 pub fn new_raw( - _device: &mut gfx_backend::Device, - factory: &mut gfx_backend::Factory, - kind: gfx::texture::Kind, - max_levels: u8, - bind: gfx::memory::Bind, - usage: gfx::memory::Usage, - levels: (u8, u8), - swizzle: gfx::format::Swizzle, - sampler_info: gfx::texture::SamplerInfo, - ) -> Result { - let tex = factory - .create_texture( - kind, - max_levels as gfx::texture::Level, - bind | gfx::memory::Bind::SHADER_RESOURCE, - usage, - Some(<::Channel as gfx::format::ChannelTyped>::get_channel_type()) - ) - .map_err(|err| RenderError::CombinedError(gfx::CombinedError::Texture(err)))?; + device: &wgpu::Device, + texture_info: &wgpu::TextureDescriptor, + view_info: &wgpu::TextureViewDescriptor, + sampler_info: &wgpu::SamplerDescriptor, + ) -> Self { + let tex = device.create_texture(texture_info); + let view = tex.create_view(view_info); - let srv = factory - .view_texture_as_shader_resource::(&tex, levels, swizzle) - .map_err(|err| RenderError::CombinedError(gfx::CombinedError::Resource(err)))?; - - Ok(Self { + Self { tex, - srv, - sampler: factory.create_sampler(sampler_info), - }) + view, + sampler: device.create_sampler(sampler_info), + size: texture_info.size, + format: texture_info.format, + } + } + + /// Clears the texture data to 0 + pub fn clear(&self, queue: &wgpu::Queue) { + let size = self.size; + let byte_len = size.width as usize + * size.height as usize + * size.depth_or_array_layers as usize + * self.format.describe().block_size as usize; + let zeros = vec![0; byte_len]; + + self.update(queue, [0, 0], [size.width, size.height], &zeros); } /// Update a texture with the given data (used for updating the glyph cache /// texture). + pub fn update(&self, queue: &wgpu::Queue, offset: [u32; 2], size: [u32; 2], data: &[u8]) { + let bytes_per_pixel = self.format.describe().block_size as u32; - pub fn update( - &self, - encoder: &mut gfx::Encoder, - offset: [u16; 2], - size: [u16; 2], - data: &[::DataType], - ) -> Result<(), RenderError> { - let info = gfx::texture::ImageInfoCommon { - xoffset: offset[0], - yoffset: offset[1], - zoffset: 0, - width: size[0], - height: size[1], - depth: 0, - format: (), - mipmap: 0, - }; - encoder - .update_texture::<::Surface, F>( - &self.tex, None, info, data, - ) - .map_err(RenderError::TexUpdateError) + debug_assert_eq!( + data.len(), + size[0] as usize * size[1] as usize * bytes_per_pixel as usize + ); + // TODO: Only works for 2D images + queue.write_texture( + wgpu::ImageCopyTexture { + texture: &self.tex, + mip_level: 0, + origin: wgpu::Origin3d { + x: offset[0], + y: offset[1], + z: 0, + }, + }, + data, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(size[0] * bytes_per_pixel), + rows_per_image: NonZeroU32::new(size[1]), + }, + wgpu::Extent3d { + width: size[0], + height: size[1], + depth_or_array_layers: 1, + }, + ); } /// Get dimensions of the represented image. - pub fn get_dimensions(&self) -> Vec2 { - let (w, h, ..) = self.tex.get_info().kind.get_dimensions(); - Vec2::new(w, h) + pub fn get_dimensions(&self) -> vek::Vec3 { + vek::Vec3::new( + self.size.width, + self.size.height, + self.size.depth_or_array_layers, + ) } } diff --git a/voxygen/src/run.rs b/voxygen/src/run.rs index b17e2ad236..f1711dfc6a 100644 --- a/voxygen/src/run.rs +++ b/voxygen/src/run.rs @@ -168,19 +168,9 @@ fn handle_main_events_cleared( if let Some(last) = states.last_mut() { span!(guard, "Render"); let renderer = global_state.window.renderer_mut(); - // Clear the shadow maps. - renderer.clear_shadows(); - // Clear the screen - renderer.clear(); // Render the screen using the global renderer last.render(renderer, &global_state.settings); - // Finish the frame. - global_state.window.renderer_mut().flush(); - // Display the frame on the window. - global_state - .window - .swap_buffers() - .expect("Failed to swap window buffers!"); + drop(guard); } diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index fc34f8c93b..d4aa8aa752 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -1,11 +1,12 @@ use common::{terrain::TerrainGrid, vol::ReadVol}; use common_base::span; -use std::f32::consts::PI; +use core::{f32::consts::PI, fmt::Debug}; +use num::traits::{real::Real, FloatConst}; use treeculler::Frustum; use vek::*; -pub const NEAR_PLANE: f32 = 0.25; -pub const FAR_PLANE: f32 = 100000.0; +pub const NEAR_PLANE: f32 = 0.0625; +pub const FAR_PLANE: f32 = 524288.06; // excessive precision: 524288.0625 const FIRST_PERSON_INTERP_TIME: f32 = 0.1; const THIRD_PERSON_INTERP_TIME: f32 = 0.1; @@ -31,6 +32,9 @@ pub struct Dependents { pub view_mat_inv: Mat4, pub proj_mat: Mat4, pub proj_mat_inv: Mat4, + /// Specifically there for satisfying our treeculler dependency, which can't + /// handle inverted depth planes. + pub proj_mat_treeculler: Mat4, pub cam_pos: Vec3, pub cam_dir: Vec3, } @@ -64,6 +68,249 @@ fn clamp_and_modulate(ori: Vec3) -> Vec3 { } } +/// Generalized method to construct a perspective projection with x ∈ [-1,1], y +/// ∈ [-1,1], z ∈ [0,1] given fov_y_radians, aspect_ratio, 1/n, and 1/f. Note +/// that you pass in *1/n* and *1/f*, not n and f like you normally would for a +/// perspective projection; this is done to enable uniform handling of both +/// finite and infinite far planes. +/// +/// The only requirements on n and f are: 1/n ≠ 1/f, and 0 ≤ 1/n * 1/f. +/// +/// This ensures that the near and far plane are not identical (or else your +/// projection would not cover any distance), and that they have the same sign +/// (or else we cannot rely on clipping to properly fix your scene). This also +/// ensures that at least one of 1/n and 1/f is not 0, and by construction it +/// guarantees that neither n nor f are 0; these are required in order to make +/// sense of the definition of near and far planes, and avoid collapsing all +/// depths to a single point. +/// +/// For "typical" projections (matching perspective_lh_no), you would satisfy +/// the stronger requirements. We give the typical conditions for each bullet +/// point, and then explain the consequences of not satisfying these conditions: +/// +/// * 1/n < 1/f (0 to 1 depth planes, meaning n = near and f = far; if f < n, +/// depth planes go from 1 to 0, meaning f = near and n = far, aka "reverse +/// depth"). +/// +/// This is by far the most +/// likely thing to want to change; inverted depth coordinates have *far* +/// better accuracy for DirectX / Metal / WGPU-like APIs, when using +/// floating point depth, while not being *worse* than the alternative +/// (OpenGL-like depth, or when using fixed-point / integer depth). For +/// maximum benefit, make sure you are using Depth32F, as on most platforms +/// this is the only depth buffer size where floating point can be used. +/// +/// It is a bit unintuitive to prove this, but it turns out that when using +/// 1 to 0 depth planes, the point where the depth buffer has its worst +/// precision is not at the far plane (as with 0 to 1 depth planes) nor at +/// the near plane, as you might expect, but exactly at far/2 (the +/// near plane setting does not affect the point of minimum accuracy at +/// all!). However, don't let this fool you into believing the point of +/// worst precision has simply been moved around--for *any* fixed Δz that is +/// the minimum amount of depth precision you want over the whole range, and +/// any near plane, you can set the far plane farther (generally much much +/// farther!) with reversed clip space than you can with standard clip space +/// while still getting at least that much depth precision in the worst +/// case. Nor is this a small worst-case; for many desirable near and far +/// plane combinations, more than half the visible space will have +/// completely unusable precision under 0 to 1 depth, while having much better +/// than needed precision under 1 to 0 depth. +/// +/// To compute the exact (at least "roughly exact") worst-case accuracy for +/// floating point depth and a given precision target Δz, for reverse clip +/// planes (this can be computed for the non-reversed case too, but it's +/// painful and the values are horrible, so don't bother), we compute +/// (assuming a finite far plane--see below for details on the infinite +/// case) the change in the integer representation of the mantissa at z=n/2: +/// +/// ```ignore +/// e = floor(ln(near/(far - near))/ln(2)) +/// db/dz = 2^(2-e) / ((1 / far - 1 / near) * (far)^2) +/// ``` +/// +/// Then the maximum precision you can safely use to get a change in the +/// integer representation of the mantissa (assuming 32-bit floating points) +/// is around: +/// +/// ```ignore +/// abs(2^(-23) / (db/dz)). +/// ``` +/// +/// In particular, if your worst-case target accuracy over the depth range +/// is Δz, you should be okay if: +/// +/// ```ignore +/// abs(Δz * (db/dz)) * 2^(23) ≥ 1. +/// ``` +/// +/// This only accounts for precision of the final floating-point value, so +/// it's possible that artifacts may be introduced elsewhere during the +/// computation that reduce precision further; the most famous example of +/// this is that OpenGL wipes out most of the precision gains by going from +/// [-1,1] to [0,1] by letting +/// +/// ```ignore +/// clip space depth = depth * 0.5 + 0.5 +/// ``` +/// +/// which results in huge precision errors by removing nearly all the +/// floating point values with the most precision (those close to 0). +/// Fortunately, most such artifacts are absent under the wgpu/DirectX/Metal +/// depth clip space model, so with any luck remaining depth errors due to +/// the perspective warp itself should be minimal. +/// +/// * 0 ≠ 1/far (finite far plane). When this is false, the far plane is at +/// infinity; this removes the restriction of having a far plane at all, often +/// with minimal reduction in accuracy for most values in the scene. In fact, +/// in almost all cases with non-reversed depth planes, it *improves* accuracy +/// over the finite case for the vast majority of the range; however, you +/// should be using reversed depth planes, and if you are then there is a +/// quite natural accuracy vs. distance tradeoff in the infinite case. +/// +/// When using an infinite far plane, the worst-case accuracy is *always* at +/// infinity, and gets progressively worse as you get farther away from the +/// near plane. However, there is a second advantage that may not be +/// immediately apparent: the perspective warp becomes much simpler, +/// potentially removing artifacts! Specifically, in the 0 to 1 depth plane +/// case, the assigned depth value (after perspective division) becomes: +/// +/// ```ignore +/// depth = 1 - near/z +/// ``` +/// +/// while in the 1 to 0 depth plane case (which you should be using), the +/// equation is even simpler: +/// +/// ```ignore +/// depth = near/z +/// ``` +/// +/// In the 1 to 0 case, in particular, you can see that the depth value is +/// *linear in z in log space.* This lets us compute, for any given target +/// precision, a *very* simple worst-case upper bound on the maximum +/// absolute z value for which that precision can be achieved (the upper +/// bound is tight in some cases, but in others may be conservative): +/// +/// ```ignore +/// db/dz ≥ 1/z +/// ``` +/// +/// Plugging that into our old formula, we find that we attain the required +/// precision at least in the range (again, this is for the 1 to 0 infinite +/// case only!): +/// +/// ```ignore +/// abs(z) ≤ Δz * 2^23 +/// ``` +/// +/// One thing you may notice is that this worst-case bound *does not depend +/// on the near plane.* This means that (within reason) you can put the near +/// plane as close as you like and still attain this bound. Of course, the +/// bound is not completely tight, but it should not be off by more than a +/// factor of 2 or so (informally proven, not made rigorous yet), so for most +/// practical purposes you can set the near plane as low as you like in this +/// case. +/// +/// * 0 < 1/near (positive near plane--best used when moving *to* left-handed +/// spaces, as we normally do in OpenGL and DirectX). A use case for *not* +/// doing this is that it allows moving *from* a left-handed space *to* a +/// right-handed space in WGPU / DirectX / Metal coordinates; this means that +/// if matrices were already set up for OpenGL using functions like look_at_rh +/// that assume right-handed coordinates, we can simply switch these to +/// look_at_lh and use a right-handed perspective projection with a negative +/// near plane, to get correct rendering behavior. Details are out of scope +/// for this comment. +/// +/// Note that there is one final, very important thing that affects possible +/// precision--the actual underlying precision of the floating point format at a +/// particular value! As your z values go up, their precision will shrink, so +/// if at all possible try to shrink your z values down to the lowest range in +/// which they can be. Unfortunately, this cannot be part of the perspective +/// projection itself, because by the time z gets to the projection it is +/// usually too late for values to still be integers (or coarse-grained powers +/// of 2). Instead, try to scale down x, y, and z as soon as possible before +/// submitting them to the GPU, ideally by as large as possible of a power of 2 +/// that works for your use case. Not only will this improve depth precision +/// and recall, it will also help address other artifacts caused by values far +/// from z (such as improperly rounded rotations, or improper line equations due +/// to greedy meshing). +/// +/// TODO: Consider passing fractions rather than 1/n and 1/f directly, even +/// though the logic for why it should be okay to pass them directly is probably +/// sound (they are both valid z values in the range, so gl_FragCoord.w will be +/// assigned to this, meaning if they are imprecise enough then the whole +/// calculation will be similarly imprecies). +/// +/// TODO: Since it's a bit confusing that n and f are not always near and far, +/// and a negative near plane can (probably) be emulated with simple actions on +/// the perspective matrix, consider removing this functionailty and replacing +/// our assertion with a single condition: `(1/far) * (1/near) < (1/near)²`. +pub fn perspective_lh_zo_general( + fov_y_radians: T, + aspect_ratio: T, + inv_n: T, + inv_f: T, +) -> Mat4 +where + T: Real + FloatConst + Debug, +{ + // Per comments, we only need these two assertions to make sure our calculations + // make sense. + debug_assert_ne!( + inv_n, inv_f, + "The near and far plane distances cannot be equal, found: {:?} = {:?}", + inv_n, inv_f + ); + debug_assert!( + T::zero() <= inv_n * inv_f, + "The near and far plane distances must have the same sign, found: {:?} * {:?} < 0", + inv_n, + inv_f + ); + + // TODO: Would be nice to separate out the aspect ratio computations. + let two = T::one() + T::one(); + let tan_half_fovy = (fov_y_radians / two).tan(); + let m00 = T::one() / (aspect_ratio * tan_half_fovy); + let m11 = T::one() / tan_half_fovy; + let m23 = -T::one() / (inv_n - inv_f); + let m22 = inv_n * (-m23); + Mat4::new( + m00, + T::zero(), + T::zero(), + T::zero(), + T::zero(), + m11, + T::zero(), + T::zero(), + T::zero(), + T::zero(), + m22, + m23, + T::zero(), + T::zero(), + T::one(), + T::zero(), + ) +} + +/// Same as perspective_lh_zo_general, but for right-handed source spaces. +pub fn perspective_rh_zo_general( + fov_y_radians: T, + aspect_ratio: T, + inv_n: T, + inv_f: T, +) -> Mat4 +where + T: Real + FloatConst + Debug, +{ + let mut m = perspective_lh_zo_general(fov_y_radians, aspect_ratio, inv_n, inv_f); + m[(2, 2)] = -m[(2, 2)]; + m[(3, 2)] = -m[(3, 2)]; + m +} + impl Camera { /// Create a new `Camera` with default parameters. pub fn new(aspect: f32, mode: CameraMode) -> Self { @@ -89,6 +336,7 @@ impl Camera { view_mat_inv: Mat4::identity(), proj_mat: Mat4::identity(), proj_mat_inv: Mat4::identity(), + proj_mat_treeculler: Mat4::identity(), cam_pos: Vec3::zero(), cam_dir: Vec3::unit_y(), }, @@ -135,20 +383,25 @@ impl Camera { * Mat4::translation_3d(-self.focus.map(|e| e.fract())); self.dependents.view_mat_inv = self.dependents.view_mat.inverted(); + // NOTE: We reverse the far and near planes to produce an inverted depth + // buffer (1 to 0 z planes). self.dependents.proj_mat = - Mat4::perspective_rh_no(self.fov, self.aspect, NEAR_PLANE, FAR_PLANE); + perspective_rh_zo_general(self.fov, self.aspect, 1.0 / FAR_PLANE, 1.0 / NEAR_PLANE); + // For treeculler, we also produce a version without inverted depth. + self.dependents.proj_mat_treeculler = + perspective_rh_zo_general(self.fov, self.aspect, 1.0 / NEAR_PLANE, 1.0 / FAR_PLANE); self.dependents.proj_mat_inv = self.dependents.proj_mat.inverted(); // TODO: Make this more efficient. - self.dependents.cam_pos = Vec3::from(self.dependents.view_mat.inverted() * Vec4::unit_w()); + self.dependents.cam_pos = Vec3::from(self.dependents.view_mat_inv * Vec4::unit_w()); self.frustum = Frustum::from_modelview_projection( - (self.dependents.proj_mat + (self.dependents.proj_mat_treeculler * self.dependents.view_mat * Mat4::translation_3d(-self.focus.map(|e| e.trunc()))) .into_col_arrays(), ); - self.dependents.cam_dir = Vec3::from(self.dependents.view_mat.inverted() * -Vec4::unit_z()); + self.dependents.cam_dir = Vec3::from(self.dependents.view_mat_inv * -Vec4::unit_z()); } pub fn frustum(&self) -> &Frustum { &self.frustum } diff --git a/voxygen/src/scene/debug.rs b/voxygen/src/scene/debug.rs new file mode 100644 index 0000000000..e4ddf857ac --- /dev/null +++ b/voxygen/src/scene/debug.rs @@ -0,0 +1,131 @@ +use crate::render::{ + Bound, Consts, DebugDrawer, DebugLocals, DebugVertex, Mesh, Model, Quad, Renderer, Tri, +}; +use common::util::srgba_to_linear; +use hashbrown::{HashMap, HashSet}; +use tracing::warn; +use vek::*; + +#[derive(Debug)] +pub enum DebugShape { + Line([Vec3; 2]), + Cylinder { radius: f32, height: f32 }, +} + +impl DebugShape { + pub fn mesh(&self) -> Mesh { + use core::f32::consts::PI; + let mut mesh = Mesh::new(); + let tri = |x: Vec3, y: Vec3, z: Vec3| { + Tri::::new(x.into(), y.into(), z.into()) + }; + let quad = |x: Vec3, y: Vec3, z: Vec3, w: Vec3| { + Quad::::new(x.into(), y.into(), z.into(), w.into()) + }; + match self { + DebugShape::Line([a, b]) => { + let h = Vec3::new(0.0, 1.0, 0.0); + mesh.push_quad(quad(*a, a + h, b + h, *b)); + }, + DebugShape::Cylinder { radius, height } => { + const SUBDIVISIONS: usize = 16; + for i in 0..SUBDIVISIONS { + let angle = |j: usize| (j as f32 / SUBDIVISIONS as f32) * 2.0 * PI; + let a = Vec3::zero(); + let b = Vec3::new(radius * angle(i).cos(), radius * angle(i).sin(), 0.0); + let c = Vec3::new( + radius * angle(i + 1).cos(), + radius * angle(i + 1).sin(), + 0.0, + ); + let h = Vec3::new(0.0, 0.0, *height); + mesh.push_tri(tri(a, b, c)); + mesh.push_quad(quad(b, c, c + h, b + h)); + mesh.push_tri(tri(a + h, b + h, c + h)); + } + }, + } + mesh + } +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct DebugShapeId(u64); + +pub struct Debug { + next_shape_id: DebugShapeId, + pending_shapes: HashMap, + pending_locals: HashMap, + pending_deletes: HashSet, + models: HashMap, Bound>)>, +} + +impl Debug { + pub fn new() -> Debug { + Debug { + next_shape_id: DebugShapeId(0), + pending_shapes: HashMap::new(), + pending_locals: HashMap::new(), + pending_deletes: HashSet::new(), + models: HashMap::new(), + } + } + + pub fn add_shape(&mut self, shape: DebugShape) -> DebugShapeId { + let id = DebugShapeId(self.next_shape_id.0); + self.next_shape_id.0 += 1; + self.pending_shapes.insert(id, shape); + id + } + + pub fn set_pos_and_color(&mut self, id: DebugShapeId, pos: [f32; 4], color: [f32; 4]) { + self.pending_locals.insert(id, (pos, color)); + } + + pub fn remove_shape(&mut self, id: DebugShapeId) { self.pending_deletes.insert(id); } + + pub fn maintain(&mut self, renderer: &mut Renderer) { + for (id, shape) in self.pending_shapes.drain() { + if let Some(model) = renderer.create_model(&shape.mesh()) { + let locals = renderer.create_debug_bound_locals(&[DebugLocals { + pos: [0.0; 4], + color: [1.0, 0.0, 0.0, 1.0], + }]); + self.models.insert(id, (model, locals)); + } else { + warn!( + "Failed to create model for debug shape {:?}: {:?}", + id, shape + ); + } + } + for (id, (pos, color)) in self.pending_locals.drain() { + if let Some((_, locals)) = self.models.get_mut(&id) { + let lc = srgba_to_linear(color.into()); + let new_locals = [DebugLocals { + pos, + color: [lc.r, lc.g, lc.b, lc.a], + }]; + renderer.update_consts(locals, &new_locals); + } else { + warn!( + "Tried to update locals for nonexistent debug shape {:?}", + id + ); + } + } + for id in self.pending_deletes.drain() { + self.models.remove(&id); + } + } + + pub fn render<'a>(&'a self, drawer: &mut DebugDrawer<'_, 'a>) { + for (model, locals) in self.models.values() { + drawer.draw(model, locals); + } + } +} + +impl Default for Debug { + fn default() -> Debug { Debug::new() } +} diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index e74ba048a2..0165122045 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -1,9 +1,7 @@ use super::{load::BodySpec, FigureModelEntry}; use crate::{ - mesh::{greedy::GreedyMesh, Meshable}, - render::{ - BoneMeshes, ColLightInfo, FigureModel, FigurePipeline, Mesh, Renderer, TerrainPipeline, - }, + mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_terrain}, + render::{BoneMeshes, ColLightInfo, FigureModel, Mesh, Renderer, TerrainVertex}, scene::camera::CameraMode, }; use anim::Skeleton; @@ -34,7 +32,7 @@ use vek::*; /// needed to mesh figures. struct MeshWorkerResponse { col_light: ColLightInfo, - opaque: Mesh, + opaque: Mesh, bounds: anim::vek::Aabb, vertex_range: [Range; N], } @@ -372,16 +370,12 @@ where vertex_range, }) = Arc::get_mut(recv).take().and_then(|cell| cell.take()) { - // FIXME: We really need to stop hard failing on failure to upload - // to the GPU. - let model_entry = col_lights - .create_figure( - renderer, - col_light, - (opaque, bounds), - vertex_range, - ) - .expect("Failed to upload figure data to the GPU!"); + let model_entry = col_lights.create_figure( + renderer, + col_light, + (opaque, bounds), + vertex_range, + ); *model = FigureModelEntryFuture::Done(model_entry); // NOTE: Borrow checker isn't smart enough to figure this out. if let FigureModelEntryFuture::Done(model) = model { @@ -411,7 +405,7 @@ where // Then, set up meshing context. let mut greedy = FigureModel::make_greedy(); - let mut opaque = Mesh::::new(); + let mut opaque = Mesh::::new(); // Choose the most conservative bounds for any LOD model. let mut figure_bounds = anim::vek::Aabb { min: anim::vek::Vec3::zero(), @@ -473,60 +467,57 @@ where fn generate_mesh<'a>( greedy: &mut GreedyMesh<'a>, - opaque_mesh: &mut Mesh, + opaque_mesh: &mut Mesh, segment: &'a Segment, offset: Vec3, bone_idx: u8, ) -> BoneMeshes { - let (opaque, _, _, bounds) = - Meshable::::generate_mesh( - segment, - (greedy, opaque_mesh, offset, Vec3::one(), bone_idx), - ); + let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain( + segment, + (greedy, opaque_mesh, offset, Vec3::one(), bone_idx), + ); (opaque, bounds) } fn generate_mesh_lod_mid<'a>( greedy: &mut GreedyMesh<'a>, - opaque_mesh: &mut Mesh, + opaque_mesh: &mut Mesh, segment: &'a Segment, offset: Vec3, bone_idx: u8, ) -> BoneMeshes { let lod_scale = 0.6; - let (opaque, _, _, bounds) = - Meshable::::generate_mesh( - segment.scaled_by(Vec3::broadcast(lod_scale)), - ( - greedy, - opaque_mesh, - offset * lod_scale, - Vec3::one() / lod_scale, - bone_idx, - ), - ); + let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain( + segment.scaled_by(Vec3::broadcast(lod_scale)), + ( + greedy, + opaque_mesh, + offset * lod_scale, + Vec3::one() / lod_scale, + bone_idx, + ), + ); (opaque, bounds) } fn generate_mesh_lod_low<'a>( greedy: &mut GreedyMesh<'a>, - opaque_mesh: &mut Mesh, + opaque_mesh: &mut Mesh, segment: &'a Segment, offset: Vec3, bone_idx: u8, ) -> BoneMeshes { let lod_scale = 0.3; - let (opaque, _, _, bounds) = - Meshable::::generate_mesh( - segment.scaled_by(Vec3::broadcast(lod_scale)), - ( - greedy, - opaque_mesh, - offset * lod_scale, - Vec3::one() / lod_scale, - bone_idx, - ), - ); + let (opaque, _, _, bounds) = generate_mesh_base_vol_terrain( + segment.scaled_by(Vec3::broadcast(lod_scale)), + ( + greedy, + opaque_mesh, + offset * lod_scale, + Vec3::one() / lod_scale, + bone_idx, + ), + ); (opaque, bounds) } diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 0f5537c113..d5b55c3033 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -7,14 +7,15 @@ pub use load::load_mesh; // TODO: Don't make this public. use crate::{ ecs::comp::Interpolated, render::{ - ColLightFmt, ColLightInfo, Consts, FigureBoneData, FigureLocals, FigureModel, GlobalModel, - Mesh, RenderError, Renderer, ShadowPipeline, TerrainPipeline, Texture, + pipelines::{self, ColLights}, + ColLightInfo, FigureBoneData, FigureDrawer, FigureLocals, FigureModel, FigureShadowDrawer, + Mesh, RenderError, Renderer, SubModel, TerrainVertex, }, scene::{ camera::{Camera, CameraMode, Dependents}, math, terrain::Terrain, - LodData, SceneData, + SceneData, }, }; use anim::{ @@ -61,10 +62,9 @@ pub type CameraData<'a> = (&'a Camera, f32); /// Enough data to render a figure model. pub type FigureModelRef<'a> = ( - &'a Consts, - &'a Consts, - &'a FigureModel, - &'a Texture, + &'a pipelines::figure::BoundLocals, + SubModel<'a, TerrainVertex>, + &'a ColLights, ); /// An entry holding enough information to draw or destroy a figure in a @@ -80,10 +80,20 @@ pub struct FigureModelEntry { /// Texture used to store color/light information for this figure entry. /* TODO: Consider using mipmaps instead of storing multiple texture atlases for different * LOD levels. */ - col_lights: Texture, - /// Models stored in this figure entry; there may be several for one figure, - /// because of LOD models. - pub models: [FigureModel; N], + col_lights: ColLights, + /// Vertex ranges stored in this figure entry; there may be several for one + /// figure, because of LOD models. + lod_vertex_ranges: [Range; N], + model: FigureModel, +} + +impl FigureModelEntry { + pub fn lod_model(&self, lod: usize) -> SubModel { + // Note: Range doesn't impl Copy even for trivially Cloneable things + self.model + .opaque + .submodel(self.lod_vertex_ranges[lod].clone()) + } } struct FigureMgrStates { @@ -4696,20 +4706,17 @@ impl FigureMgr { visible_aabb } - pub fn render_shadows( - &self, - renderer: &mut Renderer, + pub fn render_shadows<'a>( + &'a self, + drawer: &mut FigureShadowDrawer<'_, 'a>, state: &State, tick: u64, - global: &GlobalModel, - (is_daylight, _light_data): super::LightData, (camera, figure_lod_render_distance): CameraData, ) { span!(_guard, "render_shadows", "FigureManager::render_shadows"); let ecs = state.ecs(); - if is_daylight && renderer.render_mode().shadow.is_map() { - ( + ( &ecs.entities(), &ecs.read_storage::(), ecs.read_storage::().maybe(), @@ -4722,7 +4729,7 @@ impl FigureMgr { // Don't render dead entities .filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead)) .for_each(|(entity, pos, _, body, _, inventory, scale)| { - if let Some((locals, bone_consts, model, _)) = self.get_model_for_render( + if let Some((bound, model, _)) = self.get_model_for_render( tick, camera, None, @@ -4734,27 +4741,18 @@ impl FigureMgr { figure_lod_render_distance * scale.map_or(1.0, |s| s.0), |state| state.can_shadow_sun(), ) { - renderer.render_figure_shadow_directed( - model, - global, - locals, - bone_consts, - &global.shadow_mats, - ); + drawer.draw(model, bound); } }); - } } #[allow(clippy::too_many_arguments)] // TODO: Pending review in #587 - pub fn render( - &self, - renderer: &mut Renderer, + pub fn render<'a>( + &'a self, + drawer: &mut FigureDrawer<'_, 'a>, state: &State, player_entity: EcsEntity, tick: u64, - global: &GlobalModel, - lod: &LodData, (camera, figure_lod_render_distance): CameraData, ) { span!(_guard, "render", "FigureManager::render"); @@ -4763,49 +4761,44 @@ impl FigureMgr { let character_state_storage = state.read_storage::(); let character_state = character_state_storage.get(player_entity); - for (entity, pos, _, body, _, inventory, scale) in ( + for (entity, pos, body, _, inventory, scale) in ( &ecs.entities(), &ecs.read_storage::(), - ecs.read_storage::().maybe(), &ecs.read_storage::(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), - ecs.read_storage::().maybe(), + ecs.read_storage::().maybe() ) .join() // Don't render dead entities - .filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead)) + .filter(|(_, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead)) + // Don't render player + .filter(|(entity, _, _, _, _, _)| *entity != player_entity) { - let is_player = entity == player_entity; - - if !is_player { - if let Some((locals, bone_consts, model, col_lights)) = self.get_model_for_render( - tick, - camera, - character_state, - entity, - body, - inventory, - false, - pos.0, - figure_lod_render_distance * scale.map_or(1.0, |s| s.0), - |state| state.visible(), - ) { - renderer.render_figure(model, &col_lights, global, locals, bone_consts, lod); - } + if let Some((bound, model, col_lights)) = self.get_model_for_render( + tick, + camera, + character_state, + entity, + body, + inventory, + false, + pos.0, + figure_lod_render_distance * scale.map_or(1.0, |s| s.0), + |state| state.visible(), + ) { + drawer.draw(model, bound, col_lights); } } } #[allow(clippy::too_many_arguments)] // TODO: Pending review in #587 - pub fn render_player( - &self, - renderer: &mut Renderer, + pub fn render_player<'a>( + &'a self, + drawer: &mut FigureDrawer<'_, 'a>, state: &State, player_entity: EcsEntity, tick: u64, - global: &GlobalModel, - lod: &LodData, (camera, figure_lod_render_distance): CameraData, ) { span!(_guard, "render_player", "FigureManager::render_player"); @@ -4827,7 +4820,7 @@ impl FigureMgr { let inventory_storage = ecs.read_storage::(); let inventory = inventory_storage.get(player_entity); - if let Some((locals, bone_consts, model, col_lights)) = self.get_model_for_render( + if let Some((bound, model, col_lights)) = self.get_model_for_render( tick, camera, character_state, @@ -4839,15 +4832,15 @@ impl FigureMgr { figure_lod_render_distance, |state| state.visible(), ) { - renderer.render_player(model, &col_lights, global, locals, bone_consts, lod); - renderer.render_player_shadow( + drawer.draw(model, bound, col_lights); + /*renderer.render_player_shadow( model, &col_lights, global, bone_consts, lod, &global.shadow_mats, - ); + );*/ } } } @@ -4912,14 +4905,13 @@ impl FigureMgr { }, } = self; let col_lights = &*col_lights_; - if let Some((locals, bone_consts, model_entry)) = match body { + if let Some((bound, model_entry)) = match body { Body::Humanoid(body) => character_states .get(&entity) .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), model_cache.get_model( col_lights, *body, @@ -4935,8 +4927,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), quadruped_small_model_cache.get_model( col_lights, *body, @@ -4952,8 +4943,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), quadruped_medium_model_cache.get_model( col_lights, *body, @@ -4969,8 +4959,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), quadruped_low_model_cache.get_model( col_lights, *body, @@ -4986,8 +4975,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), bird_medium_model_cache.get_model( col_lights, *body, @@ -5003,8 +4991,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), fish_medium_model_cache.get_model( col_lights, *body, @@ -5020,8 +5007,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), theropod_model_cache.get_model( col_lights, *body, @@ -5037,8 +5023,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), dragon_model_cache.get_model( col_lights, *body, @@ -5054,8 +5039,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), bird_large_model_cache.get_model( col_lights, *body, @@ -5071,8 +5055,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), fish_small_model_cache.get_model( col_lights, *body, @@ -5088,8 +5071,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), biped_large_model_cache.get_model( col_lights, *body, @@ -5105,8 +5087,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), biped_small_model_cache.get_model( col_lights, *body, @@ -5122,8 +5103,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), golem_model_cache.get_model( col_lights, *body, @@ -5139,8 +5119,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), object_model_cache.get_model( col_lights, *body, @@ -5156,8 +5135,7 @@ impl FigureMgr { .filter(|state| filter_state(&*state)) .map(move |state| { ( - state.locals(), - state.bone_consts(), + state.bound(), ship_model_cache.get_model( col_lights, *body, @@ -5175,14 +5153,14 @@ impl FigureMgr { let figure_mid_detail_distance = figure_lod_render_distance * 0.5; let model = if pos.distance_squared(cam_pos) > figure_low_detail_distance.powi(2) { - &model_entry.models[2] + model_entry.lod_model(2) } else if pos.distance_squared(cam_pos) > figure_mid_detail_distance.powi(2) { - &model_entry.models[1] + model_entry.lod_model(1) } else { - &model_entry.models[0] + model_entry.lod_model(0) }; - Some((locals, bone_consts, model, col_lights_.texture(model_entry))) + Some((bound, model, col_lights_.texture(model_entry))) } else { // trace!("Body has no saved figure"); None @@ -5211,7 +5189,7 @@ impl FigureColLights { pub fn texture<'a, const N: usize>( &'a self, model: &'a FigureModelEntry, - ) -> &'a Texture { + ) -> &'a ColLights { /* &self.col_lights */ &model.col_lights } @@ -5222,50 +5200,51 @@ impl FigureColLights { /// NOTE: Panics if the vertex range bounds are not in range of the opaque /// model stored in the BoneMeshes parameter. This is part of the /// function contract. + /// + /// NOTE: Panics if the provided mesh is empty. FIXME: do something else pub fn create_figure( &mut self, renderer: &mut Renderer, (tex, tex_size): ColLightInfo, - (opaque, bounds): (Mesh, math::Aabb), - vertex_range: [Range; N], - ) -> Result, RenderError> { + (opaque, bounds): (Mesh, math::Aabb), + vertex_ranges: [Range; N], + ) -> FigureModelEntry { span!(_guard, "create_figure", "FigureColLights::create_figure"); let atlas = &mut self.atlas; let allocation = atlas - .allocate(guillotiere::Size::new( - i32::from(tex_size.x), - i32::from(tex_size.y), - )) + .allocate(guillotiere::Size::new(tex_size.x as i32, tex_size.y as i32)) .expect("Not yet implemented: allocate new atlas on allocation failure."); - let col_lights = ShadowPipeline::create_col_lights(renderer, &(tex, tex_size))?; + let col_lights = pipelines::shadow::create_col_lights(renderer, &(tex, tex_size)); + let col_lights = renderer.figure_bind_col_light(col_lights); let model_len = u32::try_from(opaque.vertices().len()) .expect("The model size for this figure does not fit in a u32!"); - let model = renderer.create_model(&opaque)?; + let model = renderer + .create_model(&opaque) + .expect("The model contains no vertices!"); - Ok(FigureModelEntry { + vertex_ranges.iter().for_each(|range| { + assert!( + range.start <= range.end && range.end <= model_len, + "The provided vertex range for figure mesh {:?} does not fit in the model, which \ + is of size {:?}!", + range, + model_len + ); + }); + + FigureModelEntry { _bounds: bounds, - models: vertex_range.map(|range| { - assert!( - range.start <= range.end && range.end <= model_len, - "The provided vertex range for figure mesh {:?} does not fit in the model, \ - which is of size {:?}!", - range, - model_len - ); - FigureModel { - opaque: model.submodel(range), - } - }), - col_lights, allocation, - }) + col_lights, + lod_vertex_ranges: vertex_ranges, + model: FigureModel { opaque: model }, + } } #[allow(clippy::unnecessary_wraps)] fn make_atlas(renderer: &mut Renderer) -> Result { let max_texture_size = renderer.max_texture_size(); - let atlas_size = - guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size)); + let atlas_size = guillotiere::Size::new(max_texture_size as i32, max_texture_size as i32); let atlas = AtlasAllocator::with_options(atlas_size, &guillotiere::AllocatorOptions { // TODO: Verify some good empirical constants. small_size_threshold: 32, @@ -5298,8 +5277,6 @@ impl FigureColLights { } pub struct FigureStateMeta { - bone_consts: Consts, - locals: Consts, lantern_offset: anim::vek::Vec3, state_time: f32, last_ori: anim::vek::Quaternion, @@ -5311,6 +5288,7 @@ pub struct FigureStateMeta { last_light: f32, last_glow: (Vec3, f32), acc_vel: f32, + bound: pipelines::figure::BoundLocals, } impl FigureStateMeta { @@ -5345,8 +5323,6 @@ impl FigureState { let bone_consts = figure_bone_data_from_anim(&buf); Self { meta: FigureStateMeta { - bone_consts: renderer.create_consts(bone_consts).unwrap(), - locals: renderer.create_consts(&[FigureLocals::default()]).unwrap(), lantern_offset, state_time: 0.0, last_ori: Ori::default().into(), @@ -5358,6 +5334,7 @@ impl FigureState { last_light: 1.0, last_glow: (Vec3::zero(), 0.0), acc_vel: 0.0, + bound: renderer.create_figure_bound_locals(&[FigureLocals::default()], bone_consts), }, skeleton, } @@ -5469,18 +5446,13 @@ impl FigureState { self.last_light, self.last_glow, ); - renderer.update_consts(&mut self.locals, &[locals]).unwrap(); + renderer.update_consts(&mut self.meta.bound.0, &[locals]); let lantern_offset = anim::compute_matrices(&self.skeleton, mat, buf); let new_bone_consts = figure_bone_data_from_anim(buf); - renderer - .update_consts( - &mut self.meta.bone_consts, - &new_bone_consts[0..S::BONE_COUNT], - ) - .unwrap(); + renderer.update_consts(&mut self.meta.bound.1, &new_bone_consts[0..S::BONE_COUNT]); self.lantern_offset = lantern_offset; let smoothing = (5.0 * dt).min(1.0); @@ -5497,9 +5469,7 @@ impl FigureState { } } - pub fn locals(&self) -> &Consts { &self.locals } - - pub fn bone_consts(&self) -> &Consts { &self.bone_consts } + pub fn bound(&self) -> &pipelines::figure::BoundLocals { &self.bound } pub fn skeleton_mut(&mut self) -> &mut S { &mut self.skeleton } } @@ -5507,5 +5477,5 @@ impl FigureState { fn figure_bone_data_from_anim( mats: &[anim::FigureBoneData; anim::MAX_BONE_COUNT], ) -> &[FigureBoneData] { - gfx::memory::cast_slice(mats) + bytemuck::cast_slice(mats) } diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs index 76dab1b870..2e31737c08 100644 --- a/voxygen/src/scene/lod.rs +++ b/voxygen/src/scene/lod.rs @@ -1,7 +1,7 @@ use crate::{ render::{ - pipelines::lod_terrain::{Locals, LodData, Vertex}, - Consts, GlobalModel, LodTerrainPipeline, Mesh, Model, Quad, Renderer, + pipelines::lod_terrain::{LodData, Vertex}, + FirstPassDrawer, LodTerrainVertex, Mesh, Model, Quad, Renderer, }, settings::Settings, }; @@ -10,8 +10,7 @@ use common::{spiral::Spiral2d, util::srgba_to_linear}; use vek::*; pub struct Lod { - model: Option<(u32, Model)>, - locals: Consts, + model: Option<(u32, Model)>, data: LodData, } @@ -25,15 +24,15 @@ impl Lod { pub fn new(renderer: &mut Renderer, client: &Client, settings: &Settings) -> Self { Self { model: None, - locals: renderer.create_consts(&[Locals::default()]).unwrap(), data: LodData::new( renderer, - client.world_data().chunk_size(), + client.world_data().chunk_size().as_(), client.world_data().lod_base.raw(), client.world_data().lod_alt.raw(), client.world_data().lod_horizon.raw(), settings.graphics.lod_detail.max(100).min(2500), - water_color().into_array().into(), + /* TODO: figure out how we want to do this without color borders? + * water_color().into_array().into(), */ ), } } @@ -61,14 +60,14 @@ impl Lod { } } - pub fn render(&self, renderer: &mut Renderer, global: &GlobalModel) { + pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>) { if let Some((_, model)) = self.model.as_ref() { - renderer.render_lod_terrain(&model, global, &self.locals, &self.data); + drawer.draw_lod_terrain(&model); } } } -fn create_lod_terrain_mesh(detail: u32) -> Mesh { +fn create_lod_terrain_mesh(detail: u32) -> Mesh { // detail is even, so we choose odd detail (detail + 1) to create two even // halves with an empty hole. let detail = detail + 1; diff --git a/voxygen/src/scene/math.rs b/voxygen/src/scene/math.rs index dcd43c4efd..dcf55404e7 100644 --- a/voxygen/src/scene/math.rs +++ b/voxygen/src/scene/math.rs @@ -80,7 +80,7 @@ pub fn calc_view_frustum_world_coord>( inv_proj_view: Mat4, ) -> [Vec3; 8] { let mut world_pts = aabb_to_points(Aabb { - min: -Vec3::one(), + min: Vec3::new(-T::one(), -T::one(), T::zero()), max: Vec3::one(), }); mat_mul_points(inv_proj_view, &mut world_pts, |p| Vec3::from(p) / p.w); diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index daff82a064..50ef89f52f 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1,4 +1,5 @@ pub mod camera; +pub mod debug; pub mod figure; pub mod lod; pub mod math; @@ -8,6 +9,7 @@ pub mod terrain; pub use self::{ camera::{Camera, CameraMode}, + debug::{Debug, DebugShape, DebugShapeId}, figure::FigureMgr, lod::Lod, particle::ParticleMgr, @@ -16,9 +18,9 @@ pub use self::{ use crate::{ audio::{ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend}, render::{ - create_clouds_mesh, create_pp_mesh, create_skybox_mesh, CloudsLocals, CloudsPipeline, - Consts, GlobalModel, Globals, Light, LodData, Model, PostProcessLocals, - PostProcessPipeline, Renderer, Shadow, ShadowLocals, SkyboxLocals, SkyboxPipeline, + create_skybox_mesh, CloudsLocals, Consts, Drawer, GlobalModel, Globals, GlobalsBindGroup, + Light, Model, PointLightMatrix, PostProcessLocals, Renderer, Shadow, ShadowLocals, + SkyboxVertex, }, settings::Settings, window::{AnalogGameInput, Event}, @@ -34,6 +36,7 @@ use common::{ use common_base::span; use common_state::State; use comp::item::Reagent; +use hashbrown::HashMap; use num::traits::{Float, FloatConst}; use specs::{Entity as EcsEntity, Join, WorldExt}; use vek::*; @@ -41,7 +44,7 @@ use vek::*; // TODO: Don't hard-code this. const CURSOR_PAN_SCALE: f32 = 0.005; -const MAX_LIGHT_COUNT: usize = 31; +const MAX_LIGHT_COUNT: usize = 20; // 31 (total shadow_mats is limited to 128 with default max_uniform_buffer_binding_size) const MAX_SHADOW_COUNT: usize = 24; const NUM_DIRECTED_LIGHTS: usize = 1; const LIGHT_DIST_RADIUS: f32 = 64.0; // The distance beyond which lights may not emit light from their origin @@ -67,30 +70,19 @@ struct EventLight { } struct Skybox { - model: Model, - locals: Consts, -} - -struct Clouds { - model: Model, - locals: Consts, -} - -struct PostProcess { - model: Model, - locals: Consts, + model: Model, } pub struct Scene { data: GlobalModel, + globals_bind_group: GlobalsBindGroup, camera: Camera, camera_input_state: Vec2, event_lights: Vec, skybox: Skybox, - clouds: Clouds, - postprocess: PostProcess, terrain: Terrain, + pub debug: Debug, pub lod: Lod, loaded_distance: f32, /// x coordinate is sea level (minimum height for any land chunk), and y @@ -141,7 +133,7 @@ impl<'a> SceneData<'a> { /// W_e = 2 is the width of the image plane (for our projections, since they go /// from -1 to 1) n_e = near_plane is the near plane for the view frustum /// θ = (fov / 2) is the half-angle of the FOV (the one passed to -/// Mat4::projection_rh_no). +/// Mat4::projection_rh_zo). /// /// Although the widths for the x and y image planes are the same, they are /// different in this framework due to the introduction of an aspect ratio: @@ -273,42 +265,36 @@ impl Scene { client: &Client, settings: &Settings, ) -> Self { - let resolution = renderer.get_resolution().map(|e| e as f32); + let resolution = renderer.resolution().map(|e| e as f32); let sprite_render_context = lazy_init(renderer); + let data = GlobalModel { + globals: renderer.create_consts(&[Globals::default()]), + lights: renderer.create_consts(&[Light::default(); MAX_LIGHT_COUNT]), + shadows: renderer.create_consts(&[Shadow::default(); MAX_SHADOW_COUNT]), + shadow_mats: renderer.create_shadow_bound_locals(&[ShadowLocals::default()]), + point_light_matrices: Box::new([PointLightMatrix::default(); MAX_LIGHT_COUNT * 6 + 6]), + }; + + let lod = Lod::new(renderer, client, settings); + + let globals_bind_group = renderer.bind_globals(&data, lod.get_data()); + + let terrain = Terrain::new(renderer, &data, lod.get_data(), sprite_render_context); + Self { - data: GlobalModel { - globals: renderer.create_consts(&[Globals::default()]).unwrap(), - lights: renderer - .create_consts(&[Light::default(); MAX_LIGHT_COUNT]) - .unwrap(), - shadows: renderer - .create_consts(&[Shadow::default(); MAX_SHADOW_COUNT]) - .unwrap(), - shadow_mats: renderer - .create_consts(&[ShadowLocals::default(); MAX_LIGHT_COUNT * 6 + 6]) - .unwrap(), - }, + data, + globals_bind_group, camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson), camera_input_state: Vec2::zero(), event_lights: Vec::new(), skybox: Skybox { model: renderer.create_model(&create_skybox_mesh()).unwrap(), - locals: renderer.create_consts(&[SkyboxLocals::default()]).unwrap(), }, - clouds: Clouds { - model: renderer.create_model(&create_clouds_mesh()).unwrap(), - locals: renderer.create_consts(&[CloudsLocals::default()]).unwrap(), - }, - postprocess: PostProcess { - model: renderer.create_model(&create_pp_mesh()).unwrap(), - locals: renderer - .create_consts(&[PostProcessLocals::default()]) - .unwrap(), - }, - terrain: Terrain::new(renderer, sprite_render_context), - lod: Lod::new(renderer, client, settings), + terrain, + debug: Debug::new(), + lod, loaded_distance: 0.0, map_bounds: Vec2::new( client.world_data().min_chunk_alt(), @@ -590,9 +576,7 @@ impl Scene { ); lights.sort_by_key(|light| light.get_pos().distance_squared(player_pos) as i32); lights.truncate(MAX_LIGHT_COUNT); - renderer - .update_consts(&mut self.data.lights, &lights) - .expect("Failed to update light constants"); + renderer.update_consts(&mut self.data.lights, &lights); // Update event lights let dt = ecs.fetch::().0; @@ -629,9 +613,7 @@ impl Scene { .collect::>(); shadows.sort_by_key(|shadow| shadow.get_pos().distance_squared(player_pos) as i32); shadows.truncate(MAX_SHADOW_COUNT); - renderer - .update_consts(&mut self.data.shadows, &shadows) - .expect("Failed to update light constants"); + renderer.update_consts(&mut self.data.shadows, &shadows); // Remember to put the new loaded distance back in the scene. self.loaded_distance = loaded_distance; @@ -642,60 +624,50 @@ impl Scene { let focus_off = focus_pos.map(|e| e.trunc()); // Update global constants. - renderer - .update_consts(&mut self.data.globals, &[Globals::new( - view_mat, - proj_mat, - cam_pos, - focus_pos, - self.loaded_distance, - self.lod.get_data().tgt_detail as f32, - self.map_bounds, - time_of_day, - scene_data.state.get_time(), - renderer.get_resolution(), - Vec2::new(SHADOW_NEAR, SHADOW_FAR), - lights.len(), - shadows.len(), - NUM_DIRECTED_LIGHTS, - scene_data - .state - .terrain() - .get((cam_pos + focus_off).map(|e| e.floor() as i32)) - .map(|b| b.kind()) - .unwrap_or(BlockKind::Air), - self.select_pos.map(|e| e - focus_off.map(|e| e as i32)), - scene_data.gamma, - scene_data.exposure, - scene_data.ambiance, - self.camera.get_mode(), - scene_data.sprite_render_distance as f32 - 20.0, - )]) - .expect("Failed to update global constants"); - renderer - .update_consts(&mut self.clouds.locals, &[CloudsLocals::new( - proj_mat_inv, - view_mat_inv, - )]) - .expect("Failed to update cloud locals"); - renderer - .update_consts(&mut self.postprocess.locals, &[PostProcessLocals::new( - proj_mat_inv, - view_mat_inv, - )]) - .expect("Failed to update post-process locals"); + renderer.update_consts(&mut self.data.globals, &[Globals::new( + view_mat, + proj_mat, + cam_pos, + focus_pos, + self.loaded_distance, + self.lod.get_data().tgt_detail as f32, + self.map_bounds, + time_of_day, + scene_data.state.get_time(), + renderer.resolution().as_(), + Vec2::new(SHADOW_NEAR, SHADOW_FAR), + lights.len(), + shadows.len(), + NUM_DIRECTED_LIGHTS, + scene_data + .state + .terrain() + .get((cam_pos + focus_off).map(|e| e.floor() as i32)) + .map(|b| b.kind()) + .unwrap_or(BlockKind::Air), + self.select_pos.map(|e| e - focus_off.map(|e| e as i32)), + scene_data.gamma, + scene_data.exposure, + scene_data.ambiance, + self.camera.get_mode(), + scene_data.sprite_render_distance as f32 - 20.0, + )]); + renderer.update_clouds_locals(CloudsLocals::new(proj_mat_inv, view_mat_inv)); + renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv)); // Maintain LoD. self.lod.maintain(renderer); + // Maintain debug shapes + self.debug.maintain(renderer); + // Maintain the terrain. let (_visible_bounds, visible_light_volume, visible_psr_bounds) = self.terrain.maintain( renderer, &scene_data, focus_pos, self.loaded_distance, - view_mat, - proj_mat, + &self.camera, ); // Maintain the figures. @@ -732,7 +704,11 @@ impl Scene { // to transform it correctly into texture coordinates, as well as // OpenGL coordinates. Note that the matrix for directional light // is *already* linear in the depth buffer. - let texture_mat = Mat4::scaling_3d(0.5f32) * Mat4::translation_3d(1.0f32); + // + // Also, observe that we flip the texture sampling matrix in order to account + // for the fact that DirectX renders top-down. + let texture_mat = Mat4::::scaling_3d::>(Vec3::new(0.5, -0.5, 1.0)) + * Mat4::translation_3d(Vec3::new(1.0, -1.0, 0.0)); // We need to compute these offset matrices to transform world space coordinates // to the translated ones we use when multiplying by the light space // matrix; this helps avoid precision loss during the @@ -744,226 +720,281 @@ impl Scene { let new_dir = math::Vec3::from(view_dir); let new_dir = new_dir.normalized(); let up: math::Vec3 = math::Vec3::unit_y(); - directed_shadow_mats.push(math::Mat4::look_at_rh( - look_at, - look_at + directed_light_dir, - up, - )); + let light_view_mat = math::Mat4::look_at_rh(look_at, look_at + directed_light_dir, up); + { + // NOTE: Light view space, right-handed. + let v_p_orig = + math::Vec3::from(light_view_mat * math::Vec4::from_direction(new_dir)); + let mut v_p = v_p_orig.normalized(); + let cos_gamma = new_dir + .map(f64::from) + .dot(directed_light_dir.map(f64::from)); + let sin_gamma = (1.0 - cos_gamma * cos_gamma).sqrt(); + let gamma = sin_gamma.asin(); + let view_mat = math::Mat4::from_col_array(view_mat.into_col_array()); + // coordinates are transformed from world space (right-handed) to view space + // (right-handed). + let bounds1 = math::fit_psr( + view_mat.map_cols(math::Vec4::from), + visible_light_volume.iter().copied(), + math::Vec4::homogenized, + ); + let n_e = f64::from(-bounds1.max.z); + let factor = compute_warping_parameter_perspective( + gamma, + n_e, + f64::from(fov), + f64::from(aspect_ratio), + ); + + v_p.z = 0.0; + v_p.normalize(); + let l_r: math::Mat4 = if factor > EPSILON_UPSILON { + // NOTE: Our coordinates are now in left-handed space, but v_p isn't; however, + // v_p has no z component, so we don't have to adjust it for left-handed + // spaces. + math::Mat4::look_at_lh(math::Vec3::zero(), math::Vec3::unit_z(), v_p) + } else { + math::Mat4::identity() + }; + // Convert from right-handed to left-handed coordinates. + let directed_proj_mat = math::Mat4::new( + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + ); + + let light_all_mat = l_r * directed_proj_mat * light_view_mat; + // coordinates are transformed from world space (right-handed) to rotated light + // space (left-handed). + let bounds0 = math::fit_psr( + light_all_mat, + visible_light_volume.iter().copied(), + math::Vec4::homogenized, + ); + // Vague idea: project z_n from the camera view to the light view (where it's + // tilted by γ). + // + // NOTE: To transform a normal by M, we multiply by the transpose of the inverse + // of M. For the cases below, we are transforming by an + // already-inverted matrix, so the transpose of its inverse is + // just the transpose of the original matrix. + let (z_0, z_1) = { + let f_e = f64::from(-bounds1.min.z).max(n_e); + // view space, right-handed coordinates. + let p_z = bounds1.max.z; + // rotated light space, left-handed coordinates. + let p_y = bounds0.min.y; + let p_x = bounds0.center().x; + // moves from view-space (right-handed) to world space (right-handed) + let view_inv = view_mat.inverted(); + // moves from rotated light space (left-handed) to world space (right-handed). + let light_all_inv = light_all_mat.inverted(); + + // moves from view-space (right-handed) to world-space (right-handed). + let view_point = view_inv + * math::Vec4::from_point( + -math::Vec3::unit_z() * p_z, /* + math::Vec4::unit_w() */ + ); + let view_plane = view_mat.transposed() * -math::Vec4::unit_z(); + + // moves from rotated light space (left-handed) to world space (right-handed). + let light_point = light_all_inv + * math::Vec4::from_point( + math::Vec3::unit_y() * p_y, /* + math::Vec4::unit_w() */ + ); + let light_plane = light_all_mat.transposed() * math::Vec4::unit_y(); + + // moves from rotated light space (left-handed) to world space (right-handed). + let shadow_point = light_all_inv + * math::Vec4::from_point( + math::Vec3::unit_x() * p_x, /* + math::Vec4::unit_w() */ + ); + let shadow_plane = light_all_mat.transposed() * math::Vec4::unit_x(); + + // Find the point at the intersection of the three planes; note that since the + // equations are already in right-handed world space, we don't need to negate + // the z coordinates. + let solve_p0 = math::Mat4::new( + view_plane.x, + view_plane.y, + view_plane.z, + 0.0, + light_plane.x, + light_plane.y, + light_plane.z, + 0.0, + shadow_plane.x, + shadow_plane.y, + shadow_plane.z, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ); + + // in world-space (right-handed). + let plane_dist = math::Vec4::new( + view_plane.dot(view_point), + light_plane.dot(light_point), + shadow_plane.dot(shadow_point), + 1.0, + ); + let p0_world = solve_p0.inverted() * plane_dist; + // in rotated light-space (left-handed). + let p0 = light_all_mat * p0_world; + let mut p1 = p0; + // in rotated light-space (left-handed). + p1.y = bounds0.max.y; + + // transforms from rotated light-space (left-handed) to view space + // (right-handed). + let view_from_light_mat = view_mat * light_all_inv; + // z0 and z1 are in view space (right-handed). + let z0 = view_from_light_mat * p0; + let z1 = view_from_light_mat * p1; + + // Extract the homogenized forward component (right-handed). + // + // NOTE: I don't think the w component should be anything but 1 here, but + // better safe than sorry. + ( + f64::from(z0.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e), + f64::from(z1.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e), + ) + }; + + // all of this is in rotated light-space (left-handed). + let mut light_focus_pos: math::Vec3 = math::Vec3::zero(); + light_focus_pos.x = bounds0.center().x; + light_focus_pos.y = bounds0.min.y; + light_focus_pos.z = bounds0.center().z; + + let d = f64::from(bounds0.max.y - bounds0.min.y).abs(); + + let w_l_y = d; + + // NOTE: See section 5.1.2.2 of Lloyd's thesis. + // NOTE: Since z_1 and z_0 are in the same coordinate space, we don't have to + // worry about the handedness of their ratio. + let alpha = z_1 / z_0; + let alpha_sqrt = alpha.sqrt(); + let directed_near_normal = if factor < 0.0 { + // Standard shadow map to LiSPSM + (1.0 + alpha_sqrt - factor * (alpha - 1.0)) / ((alpha - 1.0) * (factor + 1.0)) + } else { + // LiSPSM to PSM + ((alpha_sqrt - 1.0) * (factor * alpha_sqrt + 1.0)).recip() + }; + + // Equation 5.14 - 5.16 + let y_ = |v: f64| w_l_y * (v + directed_near_normal).abs(); + let directed_near = y_(0.0) as f32; + let directed_far = y_(1.0) as f32; + light_focus_pos.y = if factor > EPSILON_UPSILON { + light_focus_pos.y - directed_near + } else { + light_focus_pos.y + }; + // Left-handed translation. + let w_v: math::Mat4 = math::Mat4::translation_3d(-math::Vec3::new( + light_focus_pos.x, + light_focus_pos.y, + light_focus_pos.z, + )); + let shadow_view_mat: math::Mat4 = w_v * light_all_mat; + let w_p: math::Mat4 = { + if factor > EPSILON_UPSILON { + // Projection for y + let near = directed_near; + let far = directed_far; + let left = -1.0; + let right = 1.0; + let bottom = -1.0; + let top = 1.0; + let s_x = 2.0 * near / (right - left); + let o_x = (right + left) / (right - left); + let s_z = 2.0 * near / (top - bottom); + let o_z = (top + bottom) / (top - bottom); + + let s_y = (far + near) / (far - near); + let o_y = -2.0 * far * near / (far - near); + + math::Mat4::new( + s_x, o_x, 0.0, 0.0, 0.0, s_y, 0.0, o_y, 0.0, o_z, s_z, 0.0, 0.0, 1.0, + 0.0, 0.0, + ) + } else { + math::Mat4::identity() + } + }; + + let shadow_all_mat: math::Mat4 = w_p * shadow_view_mat; + // coordinates are transformed from world space (right-handed) + // to post-warp light space (left-handed), then homogenized. + let math::Aabb:: { + min: + math::Vec3 { + x: xmin, + y: ymin, + z: zmin, + }, + max: + math::Vec3 { + x: xmax, + y: ymax, + z: zmax, + }, + } = math::fit_psr( + shadow_all_mat, + visible_light_volume.iter().copied(), + math::Vec4::homogenized, + ); + let s_x = 2.0 / (xmax - xmin); + let s_y = 2.0 / (ymax - ymin); + let s_z = 1.0 / (zmax - zmin); + let o_x = -(xmax + xmin) / (xmax - xmin); + let o_y = -(ymax + ymin) / (ymax - ymin); + let o_z = -zmin / (zmax - zmin); + let directed_proj_mat = Mat4::new( + s_x, 0.0, 0.0, o_x, 0.0, s_y, 0.0, o_y, 0.0, 0.0, s_z, o_z, 0.0, 0.0, 0.0, 1.0, + ); + + let shadow_all_mat: Mat4 = + Mat4::from_col_arrays(shadow_all_mat.into_col_arrays()); + + let directed_texture_proj_mat = texture_mat * directed_proj_mat; + let shadow_locals = ShadowLocals::new( + directed_proj_mat * shadow_all_mat, + directed_texture_proj_mat * shadow_all_mat, + ); + + renderer.update_consts(&mut self.data.shadow_mats, &[shadow_locals]); + } + directed_shadow_mats.push(light_view_mat); // This leaves us with five dummy slots, which we push as defaults. directed_shadow_mats .extend_from_slice(&[math::Mat4::default(); 6 - NUM_DIRECTED_LIGHTS] as _); // Now, construct the full projection matrices in the first two directed light // slots. let mut shadow_mats = Vec::with_capacity(6 * (lights.len() + 1)); - shadow_mats.extend(directed_shadow_mats.iter().enumerate().map( - move |(idx, &light_view_mat)| { - if idx >= NUM_DIRECTED_LIGHTS { - return ShadowLocals::new(Mat4::identity(), Mat4::identity()); - } - - let v_p_orig = - math::Vec3::from(light_view_mat * math::Vec4::from_direction(new_dir)); - let mut v_p = v_p_orig.normalized(); - let cos_gamma = new_dir - .map(f64::from) - .dot(directed_light_dir.map(f64::from)); - let sin_gamma = (1.0 - cos_gamma * cos_gamma).sqrt(); - let gamma = sin_gamma.asin(); - let view_mat = math::Mat4::from_col_array(view_mat.into_col_array()); - let bounds1 = math::fit_psr( - view_mat.map_cols(math::Vec4::from), - visible_light_volume.iter().copied(), - math::Vec4::homogenized, - ); - let n_e = f64::from(-bounds1.max.z); - let factor = compute_warping_parameter_perspective( - gamma, - n_e, - f64::from(fov), - f64::from(aspect_ratio), - ); - - v_p.z = 0.0; - v_p.normalize(); - let l_r: math::Mat4 = if factor > EPSILON_UPSILON { - math::Mat4::look_at_rh(math::Vec3::zero(), -math::Vec3::unit_z(), v_p) - } else { - math::Mat4::identity() - }; - let directed_proj_mat = math::Mat4::new( - 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, - 1.0, - ); - - let light_all_mat = l_r * directed_proj_mat * light_view_mat; - let bounds0 = math::fit_psr( - light_all_mat, - visible_light_volume.iter().copied(), - math::Vec4::homogenized, - ); - // Vague idea: project z_n from the camera view to the light view (where it's - // tilted by γ). - let (z_0, z_1) = { - let p_z = bounds1.max.z; - let p_y = bounds0.min.y; - let p_x = bounds0.center().x; - let view_inv = view_mat.inverted(); - let light_all_inv = light_all_mat.inverted(); - - let view_point = view_inv * math::Vec4::new(0.0, 0.0, p_z, 1.0); - let view_plane = - view_inv * math::Vec4::from_direction(math::Vec3::unit_z()); - - let light_point = light_all_inv * math::Vec4::new(0.0, p_y, 0.0, 1.0); - let light_plane = - light_all_inv * math::Vec4::from_direction(math::Vec3::unit_y()); - - let shadow_point = light_all_inv * math::Vec4::new(p_x, 0.0, 0.0, 1.0); - let shadow_plane = - light_all_inv * math::Vec4::from_direction(math::Vec3::unit_x()); - - let solve_p0 = math::Mat4::new( - view_plane.x, - view_plane.y, - view_plane.z, - -view_plane.dot(view_point), - light_plane.x, - light_plane.y, - light_plane.z, - -light_plane.dot(light_point), - shadow_plane.x, - shadow_plane.y, - shadow_plane.z, - -shadow_plane.dot(shadow_point), - 0.0, - 0.0, - 0.0, - 1.0, - ); - - let p0_world = solve_p0.inverted() * math::Vec4::unit_w(); - let p0 = light_all_mat * p0_world; - let mut p1 = p0; - p1.y = bounds0.max.y; - - let view_from_light_mat = view_mat * light_all_inv; - let z0 = view_from_light_mat * p0; - let z1 = view_from_light_mat * p1; - - (f64::from(z0.z), f64::from(z1.z)) - }; - - let mut light_focus_pos: math::Vec3 = math::Vec3::zero(); - light_focus_pos.x = bounds0.center().x; - light_focus_pos.y = bounds0.min.y; - light_focus_pos.z = bounds0.center().z; - - let d = f64::from(bounds0.max.y - bounds0.min.y).abs(); - - let w_l_y = d; - - // NOTE: See section 5.1.2.2 of Lloyd's thesis. - let alpha = z_1 / z_0; - let alpha_sqrt = alpha.sqrt(); - let directed_near_normal = if factor < 0.0 { - // Standard shadow map to LiSPSM - (1.0 + alpha_sqrt - factor * (alpha - 1.0)) - / ((alpha - 1.0) * (factor + 1.0)) - } else { - // LiSPSM to PSM - ((alpha_sqrt - 1.0) * (factor * alpha_sqrt + 1.0)).recip() - }; - - // Equation 5.14 - 5.16 - let y_ = |v: f64| w_l_y * (v + directed_near_normal).abs(); - let directed_near = y_(0.0) as f32; - let directed_far = y_(1.0) as f32; - light_focus_pos.y = if factor > EPSILON_UPSILON { - light_focus_pos.y - directed_near - } else { - light_focus_pos.y - }; - let w_v: math::Mat4 = math::Mat4::translation_3d(-math::Vec3::new( - light_focus_pos.x, - light_focus_pos.y, - light_focus_pos.z, - )); - let shadow_view_mat: math::Mat4 = w_v * light_all_mat; - let w_p: math::Mat4 = { - if factor > EPSILON_UPSILON { - // Projection for y - let near = directed_near; - let far = directed_far; - let left = -1.0; - let right = 1.0; - let bottom = -1.0; - let top = 1.0; - let s_x = 2.0 * near / (right - left); - let o_x = (right + left) / (right - left); - let s_z = 2.0 * near / (top - bottom); - let o_z = (top + bottom) / (top - bottom); - - let s_y = (far + near) / (far - near); - let o_y = -2.0 * far * near / (far - near); - - math::Mat4::new( - s_x, o_x, 0.0, 0.0, 0.0, s_y, 0.0, o_y, 0.0, o_z, s_z, 0.0, 0.0, - 1.0, 0.0, 0.0, - ) - } else { - math::Mat4::identity() - } - }; - - let shadow_all_mat: math::Mat4 = w_p * shadow_view_mat; - let math::Aabb:: { - min: - math::Vec3 { - x: xmin, - y: ymin, - z: zmin, - }, - max: - math::Vec3 { - x: xmax, - y: ymax, - z: zmax, - }, - } = math::fit_psr( - shadow_all_mat, - visible_light_volume.iter().copied(), - math::Vec4::homogenized, - ); - let s_x = 2.0 / (xmax - xmin); - let s_y = 2.0 / (ymax - ymin); - let s_z = 2.0 / (zmax - zmin); - let o_x = -(xmax + xmin) / (xmax - xmin); - let o_y = -(ymax + ymin) / (ymax - ymin); - let o_z = -(zmax + zmin) / (zmax - zmin); - let directed_proj_mat = Mat4::new( - s_x, 0.0, 0.0, o_x, 0.0, s_y, 0.0, o_y, 0.0, 0.0, s_z, o_z, 0.0, 0.0, 0.0, - 1.0, - ); - - let shadow_all_mat: Mat4 = - Mat4::from_col_arrays(shadow_all_mat.into_col_arrays()); - - let directed_texture_proj_mat = texture_mat * directed_proj_mat; - ShadowLocals::new( - directed_proj_mat * shadow_all_mat, - directed_texture_proj_mat * shadow_all_mat, - ) - }, - )); + shadow_mats.resize_with(6, PointLightMatrix::default); // Now, we tackle point lights. // First, create a perspective projection matrix at 90 degrees (to cover a whole - // face of the cube map we're using). - let shadow_proj = Mat4::perspective_rh_no( + // face of the cube map we're using); we use a negative near plane to exactly + // match OpenGL's behavior if we use a left-handed coordinate system everywhere + // else. + let shadow_proj = camera::perspective_rh_zo_general( 90.0f32.to_radians(), point_shadow_aspect, - SHADOW_NEAR, - SHADOW_FAR, + 1.0 / SHADOW_NEAR, + 1.0 / SHADOW_FAR, ); + // NOTE: We negate here to emulate a right-handed projection with a negative + // near plane, which produces the correct transformation to exactly match + // OpenGL's rendering behavior if we use a left-handed coordinate + // system everywhere else. + let shadow_proj = shadow_proj * Mat4::scaling_3d(-1.0); + // Next, construct the 6 orientations we'll use for the six faces, in terms of // their (forward, up) vectors. let orientations = [ @@ -974,6 +1005,7 @@ impl Scene { (Vec3::new(0.0, 0.0, 1.0), Vec3::new(0.0, -1.0, 0.0)), (Vec3::new(0.0, 0.0, -1.0), Vec3::new(0.0, -1.0, 0.0)), ]; + // NOTE: We could create the shadow map collection at the same time as the // lights, but then we'd have to sort them both, which wastes time. Plus, we // want to prepend our directed lights. @@ -984,16 +1016,13 @@ impl Scene { orientations.iter().map(move |&(forward, up)| { // NOTE: We don't currently try to linearize point lights or need a separate // transform for them. - ShadowLocals::new( - shadow_proj * Mat4::look_at_rh(eye, eye + forward, up), - Mat4::identity(), - ) + PointLightMatrix::new(shadow_proj * Mat4::look_at_lh(eye, eye + forward, up)) }) })); - renderer - .update_consts(&mut self.data.shadow_mats, &shadow_mats) - .expect("Failed to update light constants"); + for (i, val) in shadow_mats.into_iter().enumerate() { + self.data.point_light_matrices[i] = val + } } // Remove unused figures. @@ -1013,10 +1042,12 @@ impl Scene { .maintain(audio, scene_data.state, client, &self.camera); } - /// Render the scene using the provided `Renderer`. - pub fn render( - &mut self, - renderer: &mut Renderer, + pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group } + + /// Render the scene using the provided `Drawer`. + pub fn render<'a>( + &'a self, + drawer: &mut Drawer<'a>, state: &State, player_entity: EcsEntity, tick: u64, @@ -1028,84 +1059,120 @@ impl Scene { let focus_pos = self.camera.get_focus_pos(); let cam_pos = self.camera.dependents().cam_pos + focus_pos.map(|e| e.trunc()); - let global = &self.data; - let light_data = (is_daylight, &*self.light_data); let camera_data = (&self.camera, scene_data.figure_lod_render_distance); // would instead have this as an extension. - if renderer.render_mode().shadow.is_map() && (is_daylight || !light_data.1.is_empty()) { + if drawer.render_mode().shadow.is_map() && (is_daylight || !self.light_data.is_empty()) { if is_daylight { - // Set up shadow mapping. - renderer.start_shadows(); + if let Some(mut shadow_pass) = drawer.shadow_pass() { + // Render terrain directed shadows. + self.terrain + .render_shadows(&mut shadow_pass.draw_terrain_shadows(), focus_pos); + + // Render figure directed shadows. + self.figure_mgr.render_shadows( + &mut shadow_pass.draw_figure_shadows(), + state, + tick, + camera_data, + ); + } } - // Render terrain shadows. - self.terrain - .render_shadows(renderer, global, light_data, focus_pos); + // Render terrain point light shadows. + drawer.draw_point_shadows( + &self.data.point_light_matrices, + self.terrain.chunks_for_point_shadows(focus_pos), + ) + } - // Render figure shadows. - self.figure_mgr - .render_shadows(renderer, state, tick, global, light_data, camera_data); + if let Some(mut first_pass) = drawer.first_pass() { + self.figure_mgr.render_player( + &mut first_pass.draw_figures(), + state, + player_entity, + tick, + camera_data, + ); - if is_daylight { - // Flush shadows. - renderer.flush_shadows(); + self.terrain.render(&mut first_pass, focus_pos); + + self.figure_mgr.render( + &mut first_pass.draw_figures(), + state, + player_entity, + tick, + camera_data, + ); + + self.lod.render(&mut first_pass); + + // Render the skybox. + first_pass.draw_skybox(&self.skybox.model); + + // Draws translucent terrain and sprites + self.terrain.render_translucent( + &mut first_pass, + focus_pos, + cam_pos, + scene_data.sprite_render_distance, + ); + + // Render particle effects. + self.particle_mgr + .render(&mut first_pass.draw_particles(), scene_data); + + // Render debug shapes + self.debug.render(&mut first_pass.draw_debug()); + } + } + + pub fn maintain_debug_hitboxes( + &mut self, + client: &Client, + settings: &Settings, + hitboxes: &mut HashMap, + ) { + let ecs = client.state().ecs(); + let mut current_entities = hashbrown::HashSet::new(); + if settings.interface.toggle_hitboxes { + let positions = ecs.read_component::(); + let colliders = ecs.read_component::(); + let groups = ecs.read_component::(); + for (entity, pos, collider, group) in + (&ecs.entities(), &positions, &colliders, groups.maybe()).join() + { + if let comp::Collider::Box { + radius, + z_min, + z_max, + } = collider + { + current_entities.insert(entity); + let shape_id = hitboxes.entry(entity).or_insert_with(|| { + self.debug.add_shape(DebugShape::Cylinder { + radius: *radius, + height: *z_max - *z_min, + }) + }); + let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min, 0.0]; + let color = if group == Some(&comp::group::ENEMY) { + [1.0, 0.0, 0.0, 0.5] + } else if group == Some(&comp::group::NPC) { + [0.0, 0.0, 1.0, 0.5] + } else { + [0.0, 1.0, 0.0, 0.5] + }; + self.debug.set_pos_and_color(*shape_id, hb_pos, color); + } } } - let lod = self.lod.get_data(); - - self.figure_mgr.render_player( - renderer, - state, - player_entity, - tick, - global, - lod, - camera_data, - ); - - // Render terrain and figures. - self.terrain.render(renderer, global, lod, focus_pos); - - self.figure_mgr.render( - renderer, - state, - player_entity, - tick, - global, - lod, - camera_data, - ); - self.lod.render(renderer, global); - - // Render the skybox. - renderer.render_skybox(&self.skybox.model, global, &self.skybox.locals, lod); - - self.terrain.render_translucent( - renderer, - global, - lod, - focus_pos, - cam_pos, - scene_data.sprite_render_distance, - ); - - // Render particle effects. - self.particle_mgr.render(renderer, scene_data, global, lod); - - // Render clouds (a post-processing effect) - renderer.render_clouds( - &self.clouds.model, - &global.globals, - &self.clouds.locals, - self.lod.get_data(), - ); - - renderer.render_post_process( - &self.postprocess.model, - &global.globals, - &self.postprocess.locals, - self.lod.get_data(), - ); + hitboxes.retain(|k, v| { + let keep = current_entities.contains(k); + if !keep { + self.debug.remove_shape(*v); + } + keep + }); } } diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 0cc0e5f1b6..0f960d56a0 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -1,9 +1,9 @@ use super::{terrain::BlocksOfInterest, SceneData, Terrain}; use crate::{ - mesh::{greedy::GreedyMesh, Meshable}, + mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_particle}, render::{ - pipelines::particle::ParticleMode, GlobalModel, Instances, Light, LodData, Model, - ParticleInstance, ParticlePipeline, Renderer, + pipelines::particle::ParticleMode, Instances, Light, Model, ParticleDrawer, + ParticleInstance, ParticleVertex, Renderer, }, }; use common::{ @@ -38,7 +38,7 @@ pub struct ParticleMgr { instances: Instances, /// GPU Vertex Buffers - model_cache: HashMap<&'static str, Model>, + model_cache: HashMap<&'static str, Model>, } impl ParticleMgr { @@ -276,15 +276,18 @@ impl ParticleMgr { self.maintain_shockwave_particles(scene_data); self.maintain_aura_particles(scene_data); self.maintain_buff_particles(scene_data); + + self.upload_particles(renderer); } else { // remove all particle lifespans - self.particles.clear(); + if !self.particles.is_empty() { + self.particles.clear(); + self.upload_particles(renderer); + } // remove all timings self.scheduler.clear(); } - - self.upload_particles(renderer); } fn maintain_body_particles(&mut self, scene_data: &SceneData) { @@ -1178,13 +1181,7 @@ impl ParticleMgr { self.instances = gpu_instances; } - pub fn render( - &self, - renderer: &mut Renderer, - scene_data: &SceneData, - global: &GlobalModel, - lod: &LodData, - ) { + pub fn render<'a>(&'a self, drawer: &mut ParticleDrawer<'_, 'a>, scene_data: &SceneData) { span!(_guard, "render", "ParticleMgr::render"); if scene_data.particles_enabled { let model = &self @@ -1192,7 +1189,7 @@ impl ParticleMgr { .get(DEFAULT_MODEL_KEY) .expect("Expected particle model in cache"); - renderer.render_particles(model, global, &self.instances, lod); + drawer.draw(model, &self.instances); } } @@ -1211,7 +1208,7 @@ fn default_instances(renderer: &mut Renderer) -> Instances { const DEFAULT_MODEL_KEY: &str = "voxygen.voxel.particle"; -fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model> { +fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model> { let mut model_cache = HashMap::new(); model_cache.entry(DEFAULT_MODEL_KEY).or_insert_with(|| { @@ -1220,14 +1217,12 @@ fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model::generate_mesh(segment, &mut greedy).0; + let mut mesh = generate_mesh_base_vol_particle(segment, &mut greedy).0; // Center particle vertices around origin for vert in mesh.vertices_mut() { vert.pos[0] -= segment_size.x as f32 / 2.0; diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index 2b0b0135a9..54fd63bea4 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -1,15 +1,13 @@ use crate::{ - mesh::{greedy::GreedyMesh, Meshable}, + mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_terrain}, render::{ - create_clouds_mesh, create_pp_mesh, create_skybox_mesh, BoneMeshes, CloudsLocals, - CloudsPipeline, Consts, FigureModel, FigurePipeline, GlobalModel, Globals, Light, Mesh, - Model, PostProcessLocals, PostProcessPipeline, Renderer, Shadow, ShadowLocals, - SkyboxLocals, SkyboxPipeline, TerrainPipeline, + create_skybox_mesh, BoneMeshes, Consts, FigureModel, FirstPassDrawer, GlobalModel, Globals, + GlobalsBindGroup, Light, LodData, Mesh, Model, PointLightMatrix, Renderer, Shadow, + ShadowLocals, SkyboxVertex, TerrainVertex, }, scene::{ camera::{self, Camera, CameraMode}, figure::{load_mesh, FigureColLights, FigureModelCache, FigureModelEntry, FigureState}, - LodData, }, window::{Event, PressState}, }; @@ -30,7 +28,6 @@ use common::{ terrain::BlockKind, vol::{BaseVol, ReadVol}, }; -use tracing::error; use vek::*; use winit::event::MouseButton; @@ -45,41 +42,26 @@ impl ReadVol for VoidVol { fn generate_mesh<'a>( greedy: &mut GreedyMesh<'a>, - mesh: &mut Mesh, + mesh: &mut Mesh, segment: Segment, offset: Vec3, bone_idx: u8, ) -> BoneMeshes { let (opaque, _, /* shadow */ _, bounds) = - Meshable::::generate_mesh( - segment, - (greedy, mesh, offset, Vec3::one(), bone_idx), - ); + generate_mesh_base_vol_terrain(segment, (greedy, mesh, offset, Vec3::one(), bone_idx)); (opaque /* , shadow */, bounds) } struct Skybox { - model: Model, - locals: Consts, -} - -struct PostProcess { - model: Model, - locals: Consts, -} - -struct Clouds { - model: Model, - locals: Consts, + model: Model, } pub struct Scene { data: GlobalModel, + globals_bind_group: GlobalsBindGroup, camera: Camera, skybox: Skybox, - clouds: Clouds, - postprocess: PostProcess, lod: LodData, map_bounds: Vec2, @@ -109,16 +91,12 @@ pub struct SceneData<'a> { impl Scene { pub fn new(renderer: &mut Renderer, backdrop: Option<&str>, client: &Client) -> Self { let start_angle = 90.0f32.to_radians(); - let resolution = renderer.get_resolution().map(|e| e as f32); + let resolution = renderer.resolution().map(|e| e as f32); let map_bounds = Vec2::new( client.world_data().min_chunk_alt(), client.world_data().max_chunk_alt(), ); - let map_border = [0.0, 0.0, 0.0, 0.0]; - let map_image = [0]; - let alt_image = [0]; - let horizon_image = [0x_00_01_00_01]; let mut camera = Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson); camera.set_focus_pos(Vec3::unit_z() * 1.5); @@ -127,39 +105,24 @@ impl Scene { let mut col_lights = FigureColLights::new(renderer); - Self { - data: GlobalModel { - globals: renderer.create_consts(&[Globals::default()]).unwrap(), - lights: renderer.create_consts(&[Light::default(); 32]).unwrap(), - shadows: renderer.create_consts(&[Shadow::default(); 32]).unwrap(), - shadow_mats: renderer - .create_consts(&[ShadowLocals::default(); 6]) - .unwrap(), - }, + let data = GlobalModel { + globals: renderer.create_consts(&[Globals::default()]), + lights: renderer.create_consts(&[Light::default(); 20]), + shadows: renderer.create_consts(&[Shadow::default(); 24]), + shadow_mats: renderer.create_shadow_bound_locals(&[ShadowLocals::default()]), + point_light_matrices: Box::new([PointLightMatrix::default(); 126]), + }; + let lod = LodData::dummy(renderer); + let globals_bind_group = renderer.bind_globals(&data, &lod); + + Self { + data, + globals_bind_group, skybox: Skybox { model: renderer.create_model(&create_skybox_mesh()).unwrap(), - locals: renderer.create_consts(&[SkyboxLocals::default()]).unwrap(), }, - clouds: Clouds { - model: renderer.create_model(&create_clouds_mesh()).unwrap(), - locals: renderer.create_consts(&[CloudsLocals::default()]).unwrap(), - }, - postprocess: PostProcess { - model: renderer.create_model(&create_pp_mesh()).unwrap(), - locals: renderer - .create_consts(&[PostProcessLocals::default()]) - .unwrap(), - }, - lod: LodData::new( - renderer, - Vec2::new(1, 1), - &map_image, - &alt_image, - &horizon_image, - 1, - map_border.into(), - ), + lod, map_bounds, figure_model_cache: FigureModelCache::new(), @@ -177,9 +140,9 @@ impl Scene { // total size is bounded by 2^24 * 3 * 1.5 which is bounded by // 2^27, which fits in a u32. let range = 0..opaque_mesh.vertices().len() as u32; - let model = col_lights - .create_figure(renderer, greedy.finalize(), (opaque_mesh, bounds), [range]) - .unwrap(); + let model = + col_lights + .create_figure(renderer, greedy.finalize(), (opaque_mesh, bounds), [range]); let mut buf = [Default::default(); anim::MAX_BONE_COUNT]; state.update( renderer, @@ -277,7 +240,7 @@ impl Scene { const SHADOW_NEAR: f32 = 1.0; const SHADOW_FAR: f32 = 25.0; - if let Err(e) = renderer.update_consts(&mut self.data.globals, &[Globals::new( + renderer.update_consts(&mut self.data.globals, &[Globals::new( view_mat, proj_mat, cam_pos, @@ -287,7 +250,7 @@ impl Scene { self.map_bounds, TIME, scene_data.time, - renderer.get_resolution(), + renderer.resolution().as_(), Vec2::new(SHADOW_NEAR, SHADOW_FAR), 0, 0, @@ -299,9 +262,7 @@ impl Scene { scene_data.ambiance, self.camera.get_mode(), 250.0, - )]) { - error!(?e, "Renderer failed to update"); - } + )]); self.figure_model_cache .clean(&mut self.col_lights, scene_data.tick); @@ -384,20 +345,16 @@ impl Scene { } } - pub fn render( - &mut self, - renderer: &mut Renderer, + pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group } + + pub fn render<'a>( + &'a self, + drawer: &mut FirstPassDrawer<'a>, tick: u64, body: Option, inventory: Option<&Inventory>, ) { - renderer.render_skybox( - &self.skybox.model, - &self.data, - &self.skybox.locals, - &self.lod, - ); - + let mut figure_drawer = drawer.draw_figures(); if let Some(body) = body { let model = &self.figure_model_cache.get_model( &self.col_lights, @@ -409,40 +366,23 @@ impl Scene { ); if let Some(model) = model { - renderer.render_figure( - &model.models[0], + figure_drawer.draw( + model.lod_model(0), + self.figure_state.bound(), &self.col_lights.texture(model), - &self.data, - self.figure_state.locals(), - self.figure_state.bone_consts(), - &self.lod, ); } } if let Some((model, state)) = &self.backdrop { - renderer.render_figure( - &model.models[0], + figure_drawer.draw( + model.lod_model(0), + state.bound(), &self.col_lights.texture(model), - &self.data, - state.locals(), - state.bone_consts(), - &self.lod, ); } + drop(figure_drawer); - renderer.render_clouds( - &self.clouds.model, - &self.data.globals, - &self.clouds.locals, - &self.lod, - ); - - renderer.render_post_process( - &self.postprocess.model, - &self.data.globals, - &self.postprocess.locals, - &self.lod, - ); + drawer.draw_skybox(&self.skybox.model); } } diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 65e34bde73..36495d259c 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -1,16 +1,25 @@ mod watcher; + pub use self::watcher::{BlocksOfInterest, Interaction}; use crate::{ - mesh::{greedy::GreedyMesh, terrain::SUNLIGHT, Meshable}, + mesh::{ + greedy::GreedyMesh, + segment::generate_mesh_base_vol_sprite, + terrain::{generate_mesh, SUNLIGHT}, + }, render::{ - ColLightFmt, ColLightInfo, Consts, FluidPipeline, GlobalModel, Instances, Mesh, Model, - RenderError, Renderer, ShadowPipeline, SpriteInstance, SpriteLocals, SpritePipeline, - TerrainLocals, TerrainPipeline, Texture, + pipelines::{self, ColLights}, + ColLightInfo, FirstPassDrawer, FluidVertex, GlobalModel, Instances, LodData, Mesh, Model, + RenderError, Renderer, SpriteGlobalsBindGroup, SpriteInstance, SpriteVertex, SpriteVerts, + TerrainLocals, TerrainShadowDrawer, TerrainVertex, SPRITE_VERT_PAGE_SIZE, }, }; -use super::{math, LodData, SceneData}; +use super::{ + camera::{self, Camera}, + math, SceneData, +}; use common::{ assets::{self, AssetExt, DotVoxAsset}, figure::Segment, @@ -35,6 +44,7 @@ use treeculler::{BVol, Frustum, AABB}; use vek::*; const SPRITE_SCALE: Vec3 = Vec3::new(1.0 / 11.0, 1.0 / 11.0, 1.0 / 11.0); +const SPRITE_LOD_LEVELS: usize = 5; #[derive(Clone, Copy, Debug)] struct Visibility { @@ -61,22 +71,22 @@ type LightMapFn = Arc) -> f32 + Send + Sync>; pub struct TerrainChunkData { // GPU data load_time: f32, - opaque_model: Model, - fluid_model: Option>, + opaque_model: Option>, + fluid_model: Option>, /// If this is `None`, this texture is not allocated in the current atlas, /// and therefore there is no need to free its allocation. - col_lights: Option, + col_lights_alloc: Option, /// The actual backing texture for this chunk. Use this for rendering /// purposes. The texture is reference-counted, so it will be /// automatically freed when no chunks are left that need it (though /// shadow chunks will still keep it alive; we could deal with this by /// making this an `Option`, but it probably isn't worth it since they /// shouldn't be that much more nonlocal than regular chunks). - texture: Texture, + col_lights: Arc>, light_map: LightMapFn, glow_map: LightMapFn, - sprite_instances: HashMap<(SpriteKind, usize), Instances>, - locals: Consts, + sprite_instances: [Instances; SPRITE_LOD_LEVELS], + locals: pipelines::terrain::BoundLocals, pub blocks_of_interest: BlocksOfInterest, visible: Visibility, @@ -98,8 +108,8 @@ struct ChunkMeshState { /// Just the mesh part of a mesh worker response. pub struct MeshWorkerResponseMesh { z_bounds: (f32, f32), - opaque_mesh: Mesh, - fluid_mesh: Mesh, + opaque_mesh: Mesh, + fluid_mesh: Mesh, col_lights_info: ColLightInfo, light_map: LightMapFn, glow_map: LightMapFn, @@ -109,7 +119,7 @@ pub struct MeshWorkerResponseMesh { /// mesh of a chunk. struct MeshWorkerResponse { pos: Vec2, - sprite_instances: HashMap<(SpriteKind, usize), Vec>, + sprite_instances: [Vec; SPRITE_LOD_LEVELS], /// If None, this update was requested without meshing. mesh: Option, started_tick: u64, @@ -169,22 +179,26 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug + ' max_texture_size: u16, chunk: Arc, range: Aabb, - sprite_data: &HashMap<(SpriteKind, usize), Vec>, + sprite_data: &HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>, sprite_config: &SpriteSpec, ) -> MeshWorkerResponse { span!(_guard, "mesh_worker"); let blocks_of_interest = BlocksOfInterest::from_chunk(&chunk); + let mesh; let (light_map, glow_map) = if let Some((light_map, glow_map)) = &skip_remesh { mesh = None; (&**light_map, &**glow_map) } else { let (opaque_mesh, fluid_mesh, _shadow_mesh, (bounds, col_lights_info, light_map, glow_map)) = - volume.generate_mesh(( - range, - Vec2::new(max_texture_size, max_texture_size), - &blocks_of_interest, - )); + generate_mesh( + &volume, + ( + range, + Vec2::new(max_texture_size, max_texture_size), + &blocks_of_interest, + ), + ); mesh = Some(MeshWorkerResponseMesh { // TODO: Take sprite bounds into account somehow? z_bounds: (bounds.min.z, bounds.max.z), @@ -198,12 +212,13 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug + ' let mesh = mesh.as_ref().unwrap(); (&*mesh.light_map, &*mesh.glow_map) }; + MeshWorkerResponse { pos, // Extract sprite locations from volume sprite_instances: { span!(_guard, "extract sprite_instances"); - let mut instances = HashMap::new(); + let mut instances = [(); SPRITE_LOD_LEVELS].map(|()| Vec::new()); for x in 0..V::RECT_SIZE.x as i32 { for y in 0..V::RECT_SIZE.y as i32 { @@ -231,23 +246,39 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug + ' let key = (sprite, variation); // NOTE: Safe because we called sprite_config_for already. // NOTE: Safe because 0 ≤ ori < 8 - let sprite_data = &sprite_data[&key][0]; - let instance = SpriteInstance::new( - Mat4::identity() + let light = light_map(wpos); + let glow = glow_map(wpos); + + for (lod_level, sprite_data) in + instances.iter_mut().zip(&sprite_data[&key]) + { + let mat = Mat4::identity() + // Scaling for different LOD resolutions + .scaled_3d(sprite_data.scale) + // Offset .translated_3d(sprite_data.offset) + .scaled_3d(SPRITE_SCALE) .rotated_z(f32::consts::PI * 0.25 * ori as f32) .translated_3d( - (rel_pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)) - / SPRITE_SCALE, - ), - cfg.wind_sway, - rel_pos, - ori, - light_map(wpos), - glow_map(wpos), - ); - - instances.entry(key).or_insert(Vec::new()).push(instance); + rel_pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0) + ); + // Add an instance for each page in the sprite model + for page in sprite_data.vert_pages.clone() { + // TODO: could be more efficient to create once and clone while + // modifying vert_page + let instance = SpriteInstance::new( + mat, + cfg.wind_sway, + sprite_data.scale.z, + rel_pos, + ori, + light, + glow, + page, + ); + lod_level.push(instance); + } + } } } } @@ -262,10 +293,11 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug + ' } struct SpriteData { - /* mat: Mat4, */ - locals: Consts, - model: Model, - /* scale: Vec3, */ + // Sprite vert page ranges that need to be drawn + vert_pages: core::ops::Range, + // Scale + scale: Vec3, + // Offset offset: Vec3, } @@ -316,14 +348,15 @@ pub struct Terrain { mesh_recv_overflow: f32, // GPU data - sprite_data: Arc>>, - sprite_col_lights: Texture, + // Maps sprite kind + variant to data detailing how to render it + sprite_data: Arc>, + sprite_globals: SpriteGlobalsBindGroup, + sprite_col_lights: Arc>, /// As stated previously, this is always the very latest texture into which /// we allocate. Code cannot assume that this is the assigned texture /// for any particular chunk; look at the `texture` field in /// `TerrainChunkData` for that. - col_lights: Texture, - waves: Texture, + col_lights: Arc>, phantom: PhantomData, } @@ -335,8 +368,10 @@ impl TerrainChunkData { #[derive(Clone)] pub struct SpriteRenderContext { sprite_config: Arc, - sprite_data: Arc>>, - sprite_col_lights: Texture, + // Maps sprite kind + variant to data detailing how to render it + sprite_data: Arc>, + sprite_col_lights: Arc>, + sprite_verts_buffer: Arc, } pub type SpriteRenderContextLazy = Box SpriteRenderContext>; @@ -346,16 +381,11 @@ impl SpriteRenderContext { pub fn new(renderer: &mut Renderer) -> SpriteRenderContextLazy { let max_texture_size = renderer.max_texture_size(); - struct SpriteDataResponse { - locals: [SpriteLocals; 8], - model: Mesh, - offset: Vec3, - } - struct SpriteWorkerResponse { sprite_config: Arc, - sprite_data: HashMap<(SpriteKind, usize), Vec>, + sprite_data: HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>, sprite_col_lights: ColLightInfo, + sprite_mesh: Mesh, } let join_handle = std::thread::spawn(move || { @@ -363,17 +393,14 @@ impl SpriteRenderContext { let sprite_config = Arc::::load_expect("voxygen.voxel.sprite_manifest").cloned(); - let max_size = - guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size)); + let max_size = guillotiere::Size::new(max_texture_size as i32, max_texture_size as i32); let mut greedy = GreedyMesh::new(max_size); - let mut locals_buffer = [SpriteLocals::default(); 8]; + let mut sprite_mesh = Mesh::new(); let sprite_config_ = &sprite_config; // NOTE: Tracks the start vertex of the next model to be meshed. - let sprite_data: HashMap<(SpriteKind, usize), _> = SpriteKind::into_enum_iter() .filter_map(|kind| Some((kind, kind.elim_case_pure(&sprite_config_.0).as_ref()?))) .flat_map(|(kind, sprite_config)| { - let wind_sway = sprite_config.wind_sway; sprite_config.variations.iter().enumerate().map( move |( variation, @@ -401,68 +428,60 @@ impl SpriteRenderContext { ) .unwrap_or(zero); let max_model_size = Vec3::new(31.0, 31.0, 63.0); - let model_scale = max_model_size.map2(model_size, |max_sz: f32, cur_sz| { - let scale = max_sz / max_sz.max(cur_sz as f32); - if scale < 1.0 && (cur_sz as f32 * scale).ceil() > max_sz { - scale - 0.001 - } else { - scale - } - }); - let sprite_mat: Mat4 = - Mat4::translation_3d(offset).scaled_3d(SPRITE_SCALE); - move |greedy: &mut GreedyMesh| { - ( - (kind, variation), - scaled - .iter() - .map(|&lod_scale_orig| { - let lod_scale = model_scale - * if lod_scale_orig == 1.0 { - Vec3::broadcast(1.0) - } else { - lod_axes * lod_scale_orig - + lod_axes - .map(|e| if e == 0.0 { 1.0 } else { 0.0 }) - }; - // Mesh generation exclusively acts using side effects; it - // has no - // interesting return value, but updates the mesh. - let mut opaque_mesh = Mesh::new(); - Meshable::::generate_mesh( - Segment::from(&model.read().0).scaled_by(lod_scale), - (greedy, &mut opaque_mesh, false), - ); + let model_scale = + max_model_size.map2(model_size, |max_sz: f32, cur_sz| { + let scale = max_sz / max_sz.max(cur_sz as f32); + if scale < 1.0 && (cur_sz as f32 * scale).ceil() > max_sz { + scale - 0.001 + } else { + scale + } + }); + move |greedy: &mut GreedyMesh, sprite_mesh: &mut Mesh| { + let lod_sprite_data = scaled.map(|lod_scale_orig| { + let lod_scale = model_scale + * if lod_scale_orig == 1.0 { + Vec3::broadcast(1.0) + } else { + lod_axes * lod_scale_orig + + lod_axes.map(|e| if e == 0.0 { 1.0 } else { 0.0 }) + }; - let sprite_scale = Vec3::one() / lod_scale; - let sprite_mat: Mat4 = - sprite_mat * Mat4::scaling_3d(sprite_scale); - locals_buffer.iter_mut().enumerate().for_each( - |(ori, locals)| { - let sprite_mat = sprite_mat - .rotated_z(f32::consts::PI * 0.25 * ori as f32); - *locals = SpriteLocals::new( - sprite_mat, - sprite_scale, - offset, - wind_sway, - ); - }, - ); + // Get starting page count of opaque mesh + let start_page_num = sprite_mesh.vertices().len() + / SPRITE_VERT_PAGE_SIZE as usize; + // Mesh generation exclusively acts using side effects; it + // has no interesting return value, but updates the mesh. + generate_mesh_base_vol_sprite( + Segment::from(&model.read().0).scaled_by(lod_scale), + (greedy, sprite_mesh, false), + ); + // Get the number of pages after the model was meshed + let end_page_num = (sprite_mesh.vertices().len() + + SPRITE_VERT_PAGE_SIZE as usize + - 1) + / SPRITE_VERT_PAGE_SIZE as usize; + // Fill the current last page up with degenerate verts + sprite_mesh.vertices_mut_vec().resize_with( + end_page_num * SPRITE_VERT_PAGE_SIZE as usize, + SpriteVertex::default, + ); - SpriteDataResponse { - model: opaque_mesh, - offset, - locals: locals_buffer, - } - }) - .collect::>(), - ) + let sprite_scale = Vec3::one() / lod_scale; + + SpriteData { + vert_pages: start_page_num as u32..end_page_num as u32, + scale: sprite_scale, + offset, + } + }); + + ((kind, variation), lod_sprite_data) } }, ) }) - .map(|mut f| f(&mut greedy)) + .map(|f| f(&mut greedy, &mut sprite_mesh)) .collect(); let sprite_col_lights = greedy.finalize(); @@ -471,6 +490,7 @@ impl SpriteRenderContext { sprite_config, sprite_data, sprite_col_lights, + sprite_mesh, } }); @@ -485,6 +505,7 @@ impl SpriteRenderContext { sprite_config, sprite_data, sprite_col_lights, + sprite_mesh, } = join_handle .take() .expect( @@ -494,41 +515,19 @@ impl SpriteRenderContext { .join() .unwrap(); - let sprite_data = sprite_data - .into_iter() - .map(|(key, models)| { - ( - key, - models - .into_iter() - .map( - |SpriteDataResponse { - locals, - model, - offset, - }| { - SpriteData { - locals: renderer - .create_consts(&locals) - .expect("Failed to upload sprite locals to the GPU!"), - model: renderer.create_model(&model).expect( - "Failed to upload sprite model data to the GPU!", - ), - offset, - } - }, - ) - .collect(), - ) - }) - .collect(); - let sprite_col_lights = ShadowPipeline::create_col_lights(renderer, &sprite_col_lights) - .expect("Failed to upload sprite color and light data to the GPU!"); + let sprite_col_lights = + pipelines::shadow::create_col_lights(renderer, &sprite_col_lights); + let sprite_col_lights = renderer.sprite_bind_col_light(sprite_col_lights); + + // Write sprite model to a 1D texture + let sprite_verts_buffer = renderer.create_sprite_verts(sprite_mesh); Self { + // TODO: these are all Arcs, would it makes sense to factor out the Arc? sprite_config: Arc::clone(&sprite_config), sprite_data: Arc::new(sprite_data), - sprite_col_lights, + sprite_col_lights: Arc::new(sprite_col_lights), + sprite_verts_buffer: Arc::new(sprite_verts_buffer), } }; Box::new(move |renderer| init.get_or_init(|| closure(renderer)).clone()) @@ -536,7 +535,12 @@ impl SpriteRenderContext { } impl Terrain { - pub fn new(renderer: &mut Renderer, sprite_render_context: SpriteRenderContext) -> Self { + pub fn new( + renderer: &mut Renderer, + global_model: &GlobalModel, + lod_data: &LodData, + sprite_render_context: SpriteRenderContext, + ) -> Self { // Create a new mpsc (Multiple Produced, Single Consumer) pair for communicating // with worker threads that are meshing chunks. let (send, recv) = channel::unbounded(); @@ -556,26 +560,22 @@ impl Terrain { mesh_recv_overflow: 0.0, sprite_data: sprite_render_context.sprite_data, sprite_col_lights: sprite_render_context.sprite_col_lights, - waves: renderer - .create_texture( - &assets::Image::load_expect("voxygen.texture.waves").read().0, - Some(gfx::texture::FilterMethod::Trilinear), - Some(gfx::texture::WrapMode::Tile), - None, - ) - .expect("Failed to create wave texture"), - col_lights, + sprite_globals: renderer.bind_sprite_globals( + global_model, + lod_data, + &sprite_render_context.sprite_verts_buffer, + ), + col_lights: Arc::new(col_lights), phantom: PhantomData, } } fn make_atlas( renderer: &mut Renderer, - ) -> Result<(AtlasAllocator, Texture), RenderError> { + ) -> Result<(AtlasAllocator, ColLights), RenderError> { span!(_guard, "make_atlas", "Terrain::make_atlas"); let max_texture_size = renderer.max_texture_size(); - let atlas_size = - guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size)); + let atlas_size = guillotiere::Size::new(max_texture_size as i32, max_texture_size as i32); let atlas = AtlasAllocator::with_options(atlas_size, &guillotiere::AllocatorOptions { // TODO: Verify some good empirical constants. small_size_threshold: 128, @@ -583,28 +583,48 @@ impl Terrain { ..guillotiere::AllocatorOptions::default() }); let texture = renderer.create_texture_raw( - gfx::texture::Kind::D2( - max_texture_size, - max_texture_size, - gfx::texture::AaMode::Single, - ), - 1_u8, - gfx::memory::Bind::SHADER_RESOURCE, - gfx::memory::Usage::Dynamic, - (0, 0), - gfx::format::Swizzle::new(), - gfx::texture::SamplerInfo::new( - gfx::texture::FilterMethod::Bilinear, - gfx::texture::WrapMode::Clamp, - ), - )?; - Ok((atlas, texture)) + &wgpu::TextureDescriptor { + label: Some("Atlas texture"), + size: wgpu::Extent3d { + width: max_texture_size, + height: max_texture_size, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsage::COPY_DST | wgpu::TextureUsage::SAMPLED, + }, + &wgpu::TextureViewDescriptor { + label: Some("Atlas texture view"), + format: Some(wgpu::TextureFormat::Rgba8Unorm), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }, + &wgpu::SamplerDescriptor { + label: Some("Atlas sampler"), + 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::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }, + ); + let col_light = renderer.terrain_bind_col_light(texture); + Ok((atlas, col_light)) } fn remove_chunk_meta(&mut self, _pos: Vec2, chunk: &TerrainChunkData) { // No need to free the allocation if the chunk is not allocated in the current // atlas, since we don't bother tracking it at that point. - if let Some(col_lights) = chunk.col_lights { + if let Some(col_lights) = chunk.col_lights_alloc { self.atlas.deallocate(col_lights); } /* let (zmin, zmax) = chunk.z_bounds; @@ -736,9 +756,14 @@ impl Terrain { scene_data: &SceneData, focus_pos: Vec3, loaded_distance: f32, - view_mat: Mat4, - proj_mat: Mat4, + camera: &Camera, ) -> (Aabb, Vec>, math::Aabr) { + let camera::Dependents { + view_mat, + proj_mat_treeculler, + .. + } = camera.dependents(); + // Remove any models for chunks that have been recently removed. // Note: Does this before adding to todo list just in case removed chunks were // replaced with new chunks (although this would probably be recorded as @@ -947,7 +972,6 @@ impl Terrain { break; } - // Find the area of the terrain we want. Because meshing needs to compute things // like ambient occlusion and edge elision, we also need the borders // of the chunk's neighbours too (hence the `- 1` and `+ 1`). let aabr = Aabr { @@ -1014,7 +1038,7 @@ impl Terrain { skip_remesh, started_tick, volume, - max_texture_size, + max_texture_size as u16, chunk, aabb, &sprite_data, @@ -1044,18 +1068,12 @@ impl Terrain { // data structure (convert the mesh to a model first of course). Some(todo) if response.started_tick <= todo.started_tick => { let started_tick = todo.started_tick; - let sprite_instances = response - .sprite_instances - .into_iter() - .map(|(kind, instances)| { - ( - kind, - renderer - .create_instances(&instances) - .expect("Failed to upload chunk sprite instances to the GPU!"), - ) - }) - .collect(); + + let sprite_instances = response.sprite_instances.map(|instances| { + renderer + .create_instances(&instances) + .expect("Failed to upload chunk sprite instances to the GPU!") + }); if let Some(mesh) = response.mesh { // Full update, insert the whole chunk. @@ -1070,89 +1088,63 @@ impl Terrain { let atlas = &mut self.atlas; let chunks = &mut self.chunks; let col_lights = &mut self.col_lights; - let allocation = atlas - .allocate(guillotiere::Size::new( - i32::from(tex_size.x), - i32::from(tex_size.y), - )) - .unwrap_or_else(|| { - // Atlas allocation failure: try allocating a new texture and atlas. - let (new_atlas, new_col_lights) = Self::make_atlas(renderer) - .expect("Failed to create atlas texture"); + let alloc_size = + guillotiere::Size::new(i32::from(tex_size.x), i32::from(tex_size.y)); - // We reset the atlas and clear allocations from existing chunks, - // even though we haven't yet - // checked whether the new allocation can fit in - // the texture. This is reasonable because we don't have a fallback - // if a single chunk can't fit in an empty atlas of maximum size. - // - // TODO: Consider attempting defragmentation first rather than just - // always moving everything into the new chunk. - chunks.iter_mut().for_each(|(_, chunk)| { - chunk.col_lights = None; - }); - *atlas = new_atlas; - *col_lights = new_col_lights; + let allocation = atlas.allocate(alloc_size).unwrap_or_else(|| { + // Atlas allocation failure: try allocating a new texture and atlas. + let (new_atlas, new_col_lights) = + Self::make_atlas(renderer).expect("Failed to create atlas texture"); - atlas - .allocate(guillotiere::Size::new( - i32::from(tex_size.x), - i32::from(tex_size.y), - )) - .expect("Chunk data does not fit in a texture of maximum size.") + // We reset the atlas and clear allocations from existing chunks, + // even though we haven't yet + // checked whether the new allocation can fit in + // the texture. This is reasonable because we don't have a fallback + // if a single chunk can't fit in an empty atlas of maximum size. + // + // TODO: Consider attempting defragmentation first rather than just + // always moving everything into the new chunk. + chunks.iter_mut().for_each(|(_, chunk)| { + chunk.col_lights_alloc = None; }); + *atlas = new_atlas; + *col_lights = Arc::new(new_col_lights); + + atlas + .allocate(alloc_size) + .expect("Chunk data does not fit in a texture of maximum size.") + }); // NOTE: Cast is safe since the origin was a u16. let atlas_offs = Vec2::new( - allocation.rectangle.min.x as u16, - allocation.rectangle.min.y as u16, + allocation.rectangle.min.x as u32, + allocation.rectangle.min.y as u32, ); - if let Err(err) = renderer.update_texture( - col_lights, + renderer.update_texture( + &col_lights.texture, atlas_offs.into_array(), - tex_size.into_array(), + tex_size.map(u32::from).into_array(), &tex, - ) { - warn!("Failed to update texture: {:?}", err); - } + ); self.insert_chunk(response.pos, TerrainChunkData { load_time, - opaque_model: renderer - .create_model(&mesh.opaque_mesh) - .expect("Failed to upload chunk mesh to the GPU!"), - fluid_model: if mesh.fluid_mesh.vertices().len() > 0 { - Some( - renderer - .create_model(&mesh.fluid_mesh) - .expect("Failed to upload chunk mesh to the GPU!"), - ) - } else { - None - }, - col_lights: Some(allocation.id), - texture: self.col_lights.clone(), + opaque_model: renderer.create_model(&mesh.opaque_mesh), + fluid_model: renderer.create_model(&mesh.fluid_mesh), + col_lights_alloc: Some(allocation.id), + col_lights: Arc::clone(&self.col_lights), light_map: mesh.light_map, glow_map: mesh.glow_map, sprite_instances, - locals: renderer - .create_consts(&[TerrainLocals { - model_offs: Vec3::from( - response.pos.map2(VolGrid2d::::chunk_size(), |e, sz| { - e as f32 * sz as f32 - }), - ) - .into_array(), - atlas_offs: Vec4::new( - i32::from(atlas_offs.x), - i32::from(atlas_offs.y), - 0, - 0, - ) - .into_array(), - load_time, - }]) - .expect("Failed to upload chunk locals to the GPU!"), + locals: renderer.create_terrain_bound_locals(&[TerrainLocals::new( + Vec3::from( + response.pos.map2(VolGrid2d::::chunk_size(), |e, sz| { + e as f32 * sz as f32 + }), + ), + atlas_offs, + load_time, + )]), visible: Visibility { in_range: false, in_frustum: false, @@ -1186,7 +1178,7 @@ impl Terrain { span!(guard, "Construct view frustum"); let focus_off = focus_pos.map(|e| e.trunc()); let frustum = Frustum::from_modelview_projection( - (proj_mat * view_mat * Mat4::translation_3d(-focus_off)).into_col_arrays(), + (proj_mat_treeculler * view_mat * Mat4::translation_3d(-focus_off)).into_col_arrays(), ); drop(guard); @@ -1263,10 +1255,13 @@ impl Terrain { let focus_off = math::Vec3::from(focus_off); let visible_bounds_fine = visible_bounding_box.as_::(); let inv_proj_view = - math::Mat4::from_col_arrays((proj_mat * view_mat).into_col_arrays()) + math::Mat4::from_col_arrays((proj_mat_treeculler * view_mat).into_col_arrays()) .as_::() .inverted(); let ray_direction = math::Vec3::::from(ray_direction); + // NOTE: We use proj_mat_treeculler here because + // calc_focused_light_volume_points makes the assumption that the + // near plane lies before the far plane. let visible_light_volume = math::calc_focused_light_volume_points( inv_proj_view, ray_direction.as_::(), @@ -1368,18 +1363,12 @@ impl Terrain { pub fn shadow_chunk_count(&self) -> usize { self.shadow_chunks.len() } - pub fn render_shadows( - &self, - renderer: &mut Renderer, - global: &GlobalModel, - (is_daylight, light_data): super::LightData, + pub fn render_shadows<'a>( + &'a self, + drawer: &mut TerrainShadowDrawer<'_, 'a>, focus_pos: Vec3, ) { span!(_guard, "render_shadows", "Terrain::render_shadows"); - if !renderer.render_mode().shadow.is_map() { - return; - }; - let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| { (e as i32).div_euclid(sz as i32) }); @@ -1396,77 +1385,80 @@ impl Terrain { // NOTE: We also render shadows for dead chunks that were found to still be // potential shadow casters, to avoid shadows suddenly disappearing at // very steep sun angles (e.g. sunrise / sunset). - if is_daylight { - chunk_iter - .clone() - .filter(|chunk| chunk.can_shadow_sun()) - .chain(self.shadow_chunks.iter().map(|(_, chunk)| chunk)) - .for_each(|chunk| { - // Directed light shadows. - renderer.render_terrain_shadow_directed( - &chunk.opaque_model, - global, - &chunk.locals, - &global.shadow_mats, - ); - }); - } - - // Point shadows - // - // NOTE: We don't bother retaining chunks unless they cast sun shadows, so we - // don't use `shadow_chunks` here. - light_data.iter().take(1).for_each(|_light| { - chunk_iter.clone().for_each(|chunk| { - if chunk.can_shadow_point { - renderer.render_shadow_point( - &chunk.opaque_model, - global, - &chunk.locals, - &global.shadow_mats, - ); - } - }); - }); + chunk_iter + .filter(|chunk| chunk.can_shadow_sun()) + .chain(self.shadow_chunks.iter().map(|(_, chunk)| chunk)) + .filter_map(|chunk| { + chunk + .opaque_model + .as_ref() + .map(|model| (model, &chunk.locals)) + }) + .for_each(|(model, locals)| drawer.draw(model, locals)); } - pub fn render( + pub fn chunks_for_point_shadows( &self, - renderer: &mut Renderer, - global: &GlobalModel, - lod: &LodData, focus_pos: Vec3, - ) { - span!(_guard, "render", "Terrain::render"); + ) -> impl Clone + + Iterator< + Item = ( + &Model, + &pipelines::terrain::BoundLocals, + ), + > { let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| { (e as i32).div_euclid(sz as i32) }); let chunk_iter = Spiral2d::new() - .filter_map(|rpos| { + .filter_map(move |rpos| { let pos = focus_chunk + rpos; - self.chunks.get(&pos).map(|c| (pos, c)) + self.chunks.get(&pos) }) .take(self.chunks.len()); - for (_, chunk) in chunk_iter { - if chunk.visible.is_visible() { - renderer.render_terrain_chunk( - &chunk.opaque_model, - &chunk.texture, - global, - &chunk.locals, - lod, - ); - } - } + // Point shadows + // + // NOTE: We don't bother retaining chunks unless they cast sun shadows, so we + // don't use `shadow_chunks` here. + chunk_iter + .filter(|chunk| chunk.can_shadow_point) + .filter_map(|chunk| { + chunk + .opaque_model + .as_ref() + .map(|model| (model, &chunk.locals)) + }) } - pub fn render_translucent( - &self, - renderer: &mut Renderer, - global: &GlobalModel, - lod: &LodData, + pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>, focus_pos: Vec3) { + span!(_guard, "render", "Terrain::render"); + let mut drawer = drawer.draw_terrain(); + + let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| { + (e as i32).div_euclid(sz as i32) + }); + + Spiral2d::new() + .filter_map(|rpos| { + let pos = focus_chunk + rpos; + self.chunks.get(&pos) + }) + .take(self.chunks.len()) + .filter(|chunk| chunk.visible.is_visible()) + .filter_map(|chunk| { + chunk + .opaque_model + .as_ref() + .map(|model| (model, &chunk.col_lights, &chunk.locals)) + }) + .for_each(|(model, col_lights, locals)| drawer.draw(model, col_lights, locals)); + } + + pub fn render_translucent<'a>( + &'a self, + drawer: &mut FirstPassDrawer<'a>, focus_pos: Vec3, cam_pos: Vec3, sprite_render_distance: f32, @@ -1488,68 +1480,54 @@ impl Terrain { // TODO: move to separate functions span!(guard, "Terrain sprites"); let chunk_size = V::RECT_SIZE.map(|e| e as f32); - let chunk_mag = (chunk_size * (f32::consts::SQRT_2 * 0.5)).magnitude_squared(); - for (pos, chunk) in chunk_iter.clone() { - if chunk.visible.is_visible() { - let sprite_low_detail_distance = sprite_render_distance * 0.75; - let sprite_mid_detail_distance = sprite_render_distance * 0.5; - let sprite_hid_detail_distance = sprite_render_distance * 0.35; - let sprite_high_detail_distance = sprite_render_distance * 0.15; + + let sprite_low_detail_distance = sprite_render_distance * 0.75; + let sprite_mid_detail_distance = sprite_render_distance * 0.5; + let sprite_hid_detail_distance = sprite_render_distance * 0.35; + let sprite_high_detail_distance = sprite_render_distance * 0.15; + + let mut sprite_drawer = drawer.draw_sprites(&self.sprite_globals, &self.sprite_col_lights); + chunk_iter + .clone() + .filter(|(_, c)| c.visible.is_visible()) + .for_each(|(pos, chunk)| { + // Skip chunk if it has no sprites + if chunk.sprite_instances[0].count() == 0 { + return; + } let chunk_center = pos.map2(chunk_size, |e, sz| (e as f32 + 0.5) * sz); let focus_dist_sqrd = Vec2::from(focus_pos).distance_squared(chunk_center); - let dist_sqrd = - Vec2::from(cam_pos) - .distance_squared(chunk_center) - .min(Vec2::from(cam_pos).distance_squared(chunk_center - chunk_size * 0.5)) - .min(Vec2::from(cam_pos).distance_squared( - chunk_center - chunk_size.x * 0.5 + chunk_size.y * 0.5, - )) - .min( - Vec2::from(cam_pos).distance_squared(chunk_center + chunk_size.x * 0.5), - ) - .min(Vec2::from(cam_pos).distance_squared( - chunk_center + chunk_size.x * 0.5 - chunk_size.y * 0.5, - )); - if focus_dist_sqrd < sprite_render_distance.powi(2) { - for (kind, instances) in (&chunk.sprite_instances).into_iter() { - let SpriteData { model, locals, .. } = if kind - .0 - .elim_case_pure(&self.sprite_config.0) - .as_ref() - .map(|config| config.wind_sway >= 0.4) - .unwrap_or(false) - && dist_sqrd <= chunk_mag - || dist_sqrd < sprite_high_detail_distance.powi(2) - { - &self.sprite_data[&kind][0] - } else if dist_sqrd < sprite_hid_detail_distance.powi(2) { - &self.sprite_data[&kind][1] - } else if dist_sqrd < sprite_mid_detail_distance.powi(2) { - &self.sprite_data[&kind][2] - } else if dist_sqrd < sprite_low_detail_distance.powi(2) { - &self.sprite_data[&kind][3] - } else { - &self.sprite_data[&kind][4] - }; - renderer.render_sprites( - model, - &self.sprite_col_lights, - global, - &chunk.locals, - locals, - &instances, - lod, - ); - } + let dist_sqrd = Aabr { + min: chunk_center - chunk_size * 0.5, + max: chunk_center + chunk_size * 0.5, } - } - } + .projected_point(cam_pos.xy()) + .distance_squared(cam_pos.xy()); + + if focus_dist_sqrd < sprite_render_distance.powi(2) { + let lod_level = if dist_sqrd < sprite_high_detail_distance.powi(2) { + 0 + } else if dist_sqrd < sprite_hid_detail_distance.powi(2) { + 1 + } else if dist_sqrd < sprite_mid_detail_distance.powi(2) { + 2 + } else if dist_sqrd < sprite_low_detail_distance.powi(2) { + 3 + } else { + 4 + }; + + sprite_drawer.draw(&chunk.locals, &chunk.sprite_instances[lod_level]); + } + }); + drop(sprite_drawer); drop(guard); // Translucent + span!(guard, "Fluid chunks"); + let mut fluid_drawer = drawer.draw_fluid(); chunk_iter - .clone() .filter(|(_, chunk)| chunk.visible.is_visible()) .filter_map(|(_, chunk)| { chunk @@ -1561,13 +1539,12 @@ impl Terrain { .into_iter() .rev() // Render back-to-front .for_each(|(model, locals)| { - renderer.render_fluid_chunk( + fluid_drawer.draw( model, - global, locals, - lod, - &self.waves, ) }); + drop(fluid_drawer); + drop(guard); } } diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 969e70287b..4cf91a6171 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -39,11 +39,12 @@ use crate::{ key_state::KeyState, menu::char_selection::CharSelectionState, render::Renderer, - scene::{camera, terrain::Interaction, CameraMode, Scene, SceneData}, + scene::{camera, terrain::Interaction, CameraMode, DebugShapeId, Scene, SceneData}, settings::Settings, window::{AnalogGameInput, Event, GameInput}, Direction, Error, GlobalState, PlayState, PlayStateResult, }; +use hashbrown::HashMap; use settings_change::Language::ChangeLanguage; /// The action to perform after a tick @@ -73,6 +74,7 @@ pub struct SessionState { selected_entity: Option<(specs::Entity, std::time::Instant)>, interactable: Option, saved_zoom_dist: Option, + hitboxes: HashMap, } /// Represents an active game session (i.e., the one being played). @@ -120,6 +122,7 @@ impl SessionState { selected_entity: None, interactable: None, saved_zoom_dist: None, + hitboxes: HashMap::new(), } } @@ -139,6 +142,8 @@ impl SessionState { span!(_guard, "tick", "Session::tick"); let mut client = self.client.borrow_mut(); + self.scene + .maintain_debug_hitboxes(&client, &global_state.settings, &mut self.hitboxes); for event in client.tick(self.inputs.clone(), dt, crate::ecs::sys::add_local_systems)? { match event { client::Event::Chat(m) => { @@ -1387,7 +1392,16 @@ impl PlayState for SessionState { /// This method should be called once per frame. fn render(&mut self, renderer: &mut Renderer, settings: &Settings) { span!(_guard, "render", "::render"); - // Render the screen using the global renderer + let mut drawer = match renderer + .start_recording_frame(self.scene.global_bind_group()) + .expect("Unrecoverable render error when starting a new frame!") + { + Some(d) => d, + // Couldn't get swap chain texture this frame + None => return, + }; + + // Render world { let client = self.client.borrow(); @@ -1409,16 +1423,27 @@ impl PlayState for SessionState { particles_enabled: settings.graphics.particles_enabled, is_aiming: self.is_aiming, }; + self.scene.render( - renderer, + &mut drawer, client.state(), client.entity(), client.get_tick(), &scene_data, ); } + + // Clouds + if let Some(mut second_pass) = drawer.second_pass() { + second_pass.draw_clouds(); + } + // PostProcess and UI + let mut third_pass = drawer.third_pass(); + third_pass.draw_postprocess(); // Draw the UI to the screen - self.hud.render(renderer, self.scene.globals()); + if let Some(mut ui_drawer) = third_pass.draw_ui() { + self.hud.render(&mut ui_drawer); + }; // Note: this semicolon is needed for the third_pass borrow to be dropped before it's lifetime ends } } diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index a6d2f4c7c8..cbb4fdaa64 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -96,6 +96,7 @@ pub enum Interface { SpeechBubbleIcon(bool), ToggleHelp(bool), ToggleDebug(bool), + ToggleHitboxes(bool), ToggleTips(bool), CrosshairTransp(f32), @@ -446,6 +447,9 @@ impl SettingsChange { Interface::ToggleDebug(toggle_debug) => { settings.interface.toggle_debug = toggle_debug; }, + Interface::ToggleHitboxes(toggle_hitboxes) => { + settings.interface.toggle_hitboxes = toggle_hitboxes; + }, Interface::ToggleTips(loading_tips) => { settings.interface.loading_tips = loading_tips; }, diff --git a/voxygen/src/settings/interface.rs b/voxygen/src/settings/interface.rs index b3101b7245..0e632d4765 100644 --- a/voxygen/src/settings/interface.rs +++ b/voxygen/src/settings/interface.rs @@ -10,6 +10,7 @@ use vek::*; #[serde(default)] pub struct InterfaceSettings { pub toggle_debug: bool, + pub toggle_hitboxes: bool, pub sct: bool, pub sct_player_batch: bool, pub sct_damage_batch: bool, @@ -44,6 +45,7 @@ impl Default for InterfaceSettings { fn default() -> Self { Self { toggle_debug: false, + toggle_hitboxes: false, sct: true, sct_player_batch: false, sct_damage_batch: false, diff --git a/voxygen/src/ui/cache.rs b/voxygen/src/ui/cache.rs index fe2eaa13f5..dcf5041b8e 100644 --- a/voxygen/src/ui/cache.rs +++ b/voxygen/src/ui/cache.rs @@ -1,6 +1,6 @@ use super::graphic::{Graphic, GraphicCache, Id as GraphicId}; use crate::{ - render::{Mesh, Renderer, Texture, UiPipeline}, + render::{Mesh, Renderer, Texture, UiTextureBindGroup, UiVertex}, Error, }; use conrod_core::{text::GlyphCache, widget::Id}; @@ -8,44 +8,50 @@ use hashbrown::HashMap; use vek::*; // Multiplied by current window size -const GLYPH_CACHE_SIZE: u16 = 1; +const GLYPH_CACHE_SIZE: u32 = 1; // Glyph cache tolerances const SCALE_TOLERANCE: f32 = 0.5; const POSITION_TOLERANCE: f32 = 0.5; -type TextCache = HashMap>; +type TextCache = HashMap>; pub struct Cache { // Map from text ids to their positioned glyphs. text_cache: TextCache, glyph_cache: GlyphCache<'static>, - glyph_cache_tex: Texture, + glyph_cache_tex: (Texture, UiTextureBindGroup), graphic_cache: GraphicCache, } // TODO: Should functions be returning UiError instead of Error? impl Cache { pub fn new(renderer: &mut Renderer) -> Result { - let (w, h) = renderer.get_resolution().into_tuple(); + let (w, h) = renderer.resolution().into_tuple(); let max_texture_size = renderer.max_texture_size(); let glyph_cache_dims = Vec2::new(w, h).map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size).max(512)); + let glyph_cache_tex = { + let tex = renderer.create_dynamic_texture(glyph_cache_dims); + let bind = renderer.ui_bind_texture(&tex); + (tex, bind) + }; + Ok(Self { text_cache: Default::default(), glyph_cache: GlyphCache::builder() - .dimensions(glyph_cache_dims.x as u32, glyph_cache_dims.y as u32) + .dimensions(glyph_cache_dims.x, glyph_cache_dims.y) .scale_tolerance(SCALE_TOLERANCE) .position_tolerance(POSITION_TOLERANCE) .build(), - glyph_cache_tex: renderer.create_dynamic_texture(glyph_cache_dims.map(|e| e as u16))?, + glyph_cache_tex, graphic_cache: GraphicCache::new(renderer), }) } - pub fn glyph_cache_tex(&self) -> &Texture { &self.glyph_cache_tex } + pub fn glyph_cache_tex(&self) -> &(Texture, UiTextureBindGroup) { &self.glyph_cache_tex } pub fn cache_mut_and_tex( &mut self, @@ -53,7 +59,7 @@ impl Cache { &mut GraphicCache, &mut TextCache, &mut GlyphCache<'static>, - &Texture, + &(Texture, UiTextureBindGroup), ) { ( &mut self.graphic_cache, @@ -82,14 +88,18 @@ impl Cache { self.text_cache.clear(); let max_texture_size = renderer.max_texture_size(); let cache_dims = renderer - .get_resolution() + .resolution() .map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size).max(512)); self.glyph_cache = GlyphCache::builder() - .dimensions(cache_dims.x as u32, cache_dims.y as u32) + .dimensions(cache_dims.x, cache_dims.y) .scale_tolerance(SCALE_TOLERANCE) .position_tolerance(POSITION_TOLERANCE) .build(); - self.glyph_cache_tex = renderer.create_dynamic_texture(cache_dims.map(|e| e as u16))?; + self.glyph_cache_tex = { + let tex = renderer.create_dynamic_texture(cache_dims); + let bind = renderer.ui_bind_texture(&tex); + (tex, bind) + }; Ok(()) } } diff --git a/voxygen/src/ui/graphic/mod.rs b/voxygen/src/ui/graphic/mod.rs index 241e14dbbc..ac79c42d5c 100644 --- a/voxygen/src/ui/graphic/mod.rs +++ b/voxygen/src/ui/graphic/mod.rs @@ -4,7 +4,7 @@ mod renderer; pub use renderer::{SampleStrat, Transform}; use crate::{ - render::{RenderError, Renderer, Texture}, + render::{Renderer, Texture, UiTextureBindGroup}, ui::KeyedJobs, }; use common::{figure::Segment, slowjob::SlowJobPool}; @@ -51,7 +51,7 @@ pub enum Rotation { /// Fraction of the total graphic cache size const ATLAS_CUTOFF_FRAC: f32 = 0.2; /// Multiplied by current window size -const GRAPHIC_CACHE_RELATIVE_SIZE: u16 = 1; +const GRAPHIC_CACHE_RELATIVE_SIZE: u32 = 1; #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] pub struct Id(u32); @@ -142,7 +142,7 @@ pub struct GraphicCache { // Atlases with the index of their texture in the textures vec atlases: Vec<(SimpleAtlasAllocator, usize)>, - textures: Vec, + textures: Vec<(Texture, UiTextureBindGroup)>, // Stores the location of graphics rendered at a particular resolution and cached on the cpu cache_map: HashMap, @@ -191,7 +191,7 @@ impl GraphicCache { pub fn get_graphic(&self, id: Id) -> Option<&Graphic> { self.graphic_map.get(&id) } /// Used to acquire textures for rendering - pub fn get_tex(&self, id: TexId) -> &Texture { + pub fn get_tex(&self, id: TexId) -> &(Texture, UiTextureBindGroup) { self.textures.get(id.0).expect("Invalid TexId used") } @@ -293,7 +293,7 @@ impl GraphicCache { // color. assert!(border.is_none()); // Transfer to the gpu - upload_image(renderer, aabr, &textures[idx], &image); + upload_image(renderer, aabr, &textures[idx].0, &image); } return Some((transformed_aabr(aabr.map(|e| e as f64)), TexId(idx))); @@ -314,7 +314,7 @@ impl GraphicCache { // Graphics over a particular size are sent to their own textures let location = if let Some(border_color) = border_color { // Create a new immutable texture. - let texture = create_image(renderer, image, border_color).unwrap(); + let texture = create_image(renderer, image, border_color); // NOTE: All mutations happen only after the upload succeeds! let index = textures.len(); textures.push(texture); @@ -335,7 +335,7 @@ impl GraphicCache { valid: true, aabr, }); - upload_image(renderer, aabr, &textures[texture_idx], &image); + upload_image(renderer, aabr, &textures[texture_idx].0, &image); break; } } @@ -355,7 +355,7 @@ impl GraphicCache { let atlas_idx = atlases.len(); textures.push(texture); atlases.push((atlas, tex_idx)); - upload_image(renderer, aabr, &textures[tex_idx], &image); + upload_image(renderer, aabr, &textures[tex_idx].0, &image); CachedDetails::Atlas { atlas_idx, valid: true, @@ -365,7 +365,11 @@ impl GraphicCache { } } else { // Create a texture just for this - let texture = renderer.create_dynamic_texture(dims).unwrap(); + let texture = { + let tex = renderer.create_dynamic_texture(dims.map(|e| e as u32)); + let bind = renderer.ui_bind_texture(&tex); + (tex, bind) + }; // NOTE: All mutations happen only after the texture creation succeeds! let index = textures.len(); textures.push(texture); @@ -376,7 +380,7 @@ impl GraphicCache { // Note texture should always match the cached dimensions max: dims, }, - &textures[index], + &textures[index].0, &image, ); CachedDetails::Texture { index, valid: true } @@ -440,20 +444,28 @@ fn draw_graphic( } } -fn atlas_size(renderer: &Renderer) -> Vec2 { +fn atlas_size(renderer: &Renderer) -> Vec2 { let max_texture_size = renderer.max_texture_size(); - renderer.get_resolution().map(|e| { + renderer.resolution().map(|e| { (e * GRAPHIC_CACHE_RELATIVE_SIZE) .max(512) .min(max_texture_size) }) } -fn create_atlas_texture(renderer: &mut Renderer) -> (SimpleAtlasAllocator, Texture) { +fn create_atlas_texture( + renderer: &mut Renderer, +) -> (SimpleAtlasAllocator, (Texture, UiTextureBindGroup)) { let size = atlas_size(renderer); - let atlas = SimpleAtlasAllocator::new(size2(i32::from(size.x), i32::from(size.y))); - let texture = renderer.create_dynamic_texture(size).unwrap(); + // Note: here we assume the atlas size is under i32::MAX + let atlas = SimpleAtlasAllocator::new(size2(size.x as i32, size.y as i32)); + let texture = { + let tex = renderer.create_dynamic_texture(size); + let bind = renderer.ui_bind_texture(&tex); + (tex, bind) + }; + (atlas, texture) } @@ -466,29 +478,34 @@ fn aabr_from_alloc_rect(rect: guillotiere::Rectangle) -> Aabr { } fn upload_image(renderer: &mut Renderer, aabr: Aabr, tex: &Texture, image: &RgbaImage) { + let aabr = aabr.map(|e| e as u32); let offset = aabr.min.into_array(); let size = aabr.size().into_array(); - if let Err(e) = renderer.update_texture( + renderer.update_texture( tex, offset, size, // NOTE: Rgba texture, so each pixel is 4 bytes, ergo this cannot fail. // We make the cast parameters explicit for clarity. - gfx::memory::cast_slice::(&image), - ) { - warn!(?e, "Failed to update texture"); - } + bytemuck::cast_slice::(&image), + ); } fn create_image( renderer: &mut Renderer, image: RgbaImage, - border_color: Rgba, -) -> Result { - renderer.create_texture( - &DynamicImage::ImageRgba8(image), - None, - Some(gfx::texture::WrapMode::Border), - Some(border_color.into_array().into()), - ) + _border_color: Rgba, // See TODO below +) -> (Texture, UiTextureBindGroup) { + let tex = renderer + .create_texture( + &DynamicImage::ImageRgba8(image), + None, + //TODO: either use the desktop only border color or just emulate this + // Some(border_color.into_array().into()), + Some(wgpu::AddressMode::ClampToBorder), + ) + .expect("create_texture only panics is non ImageRbga8 is passed"); + let bind = renderer.ui_bind_texture(&tex); + + (tex, bind) } diff --git a/voxygen/src/ui/ice/cache.rs b/voxygen/src/ui/ice/cache.rs index 2757450571..aeb629bc88 100644 --- a/voxygen/src/ui/ice/cache.rs +++ b/voxygen/src/ui/ice/cache.rs @@ -1,6 +1,6 @@ use super::graphic::{Graphic, GraphicCache, Id as GraphicId}; use crate::{ - render::{Renderer, Texture}, + render::{Renderer, Texture, UiTextureBindGroup}, Error, }; use common::assets::{self, AssetExt}; @@ -12,7 +12,7 @@ use std::{ use vek::*; // Multiplied by current window size -const GLYPH_CACHE_SIZE: u16 = 1; +const GLYPH_CACHE_SIZE: u32 = 1; // Glyph cache tolerances // TODO: consider scaling based on dpi as well as providing as an option to the // user @@ -46,36 +46,42 @@ pub struct FontId(pub(super) glyph_brush::FontId); pub struct Cache { glyph_brush: RefCell, - glyph_cache_tex: Texture, + glyph_cache_tex: (Texture, UiTextureBindGroup), graphic_cache: GraphicCache, } // TODO: Should functions be returning UiError instead of Error? impl Cache { pub fn new(renderer: &mut Renderer, default_font: Font) -> Result { - let (w, h) = renderer.get_resolution().into_tuple(); + let (w, h) = renderer.resolution().into_tuple(); let max_texture_size = renderer.max_texture_size(); let glyph_cache_dims = - Vec2::new(w, h).map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size as u16).max(512)); + Vec2::new(w, h).map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size).max(512)); let glyph_brush = GlyphBrushBuilder::using_font(default_font) - .initial_cache_size((glyph_cache_dims.x as u32, glyph_cache_dims.y as u32)) + .initial_cache_size((glyph_cache_dims.x, glyph_cache_dims.y)) .draw_cache_scale_tolerance(SCALE_TOLERANCE) .draw_cache_position_tolerance(POSITION_TOLERANCE) .build(); + let glyph_cache_tex = { + let tex = renderer.create_dynamic_texture(glyph_cache_dims); + let bind = renderer.ui_bind_texture(&tex); + (tex, bind) + }; + Ok(Self { glyph_brush: RefCell::new(glyph_brush), - glyph_cache_tex: renderer.create_dynamic_texture(glyph_cache_dims.map(|e| e as u16))?, + glyph_cache_tex, graphic_cache: GraphicCache::new(renderer), }) } - pub fn glyph_cache_tex(&self) -> &Texture { &self.glyph_cache_tex } + pub fn glyph_cache_tex(&self) -> &(Texture, UiTextureBindGroup) { &self.glyph_cache_tex } - pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphBrush, &Texture) { + pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphBrush, &(Texture, UiTextureBindGroup)) { (self.glyph_brush.get_mut(), &self.glyph_cache_tex) } @@ -117,6 +123,7 @@ impl Cache { self.graphic_cache.replace_graphic(id, graphic) } + // TODO: combine resize functions // Resizes and clears the GraphicCache pub fn resize_graphic_cache(&mut self, renderer: &mut Renderer) { self.graphic_cache.clear_cache(renderer); @@ -126,15 +133,20 @@ impl Cache { pub fn resize_glyph_cache(&mut self, renderer: &mut Renderer) -> Result<(), Error> { let max_texture_size = renderer.max_texture_size(); let cache_dims = renderer - .get_resolution() - .map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size as u16).max(512)); + .resolution() + .map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size).max(512)); let glyph_brush = self.glyph_brush.get_mut(); *glyph_brush = glyph_brush .to_builder() - .initial_cache_size((cache_dims.x as u32, cache_dims.y as u32)) + .initial_cache_size((cache_dims.x, cache_dims.y)) .build(); - self.glyph_cache_tex = renderer.create_dynamic_texture(cache_dims.map(|e| e as u16))?; + self.glyph_cache_tex = { + let tex = renderer.create_dynamic_texture(cache_dims); + let bind = renderer.ui_bind_texture(&tex); + (tex, bind) + }; + Ok(()) } } diff --git a/voxygen/src/ui/ice/mod.rs b/voxygen/src/ui/ice/mod.rs index 4103e1f562..849ab7a76a 100644 --- a/voxygen/src/ui/ice/mod.rs +++ b/voxygen/src/ui/ice/mod.rs @@ -14,7 +14,11 @@ use super::{ graphic::{self, Graphic}, scale::{Scale, ScaleMode}, }; -use crate::{render::Renderer, window::Window, Error}; +use crate::{ + render::{Renderer, UiDrawer}, + window::Window, + Error, +}; use common::slowjob::SlowJobPool; use common_base::span; use iced::{mouse, Cache, Size, UserInterface}; @@ -42,7 +46,7 @@ impl IcedUi { let renderer = window.renderer_mut(); let scaled_resolution = scale.scaled_resolution().map(|e| e as f32); - let physical_resolution = scale.physical_resolution(); + let physical_resolution = renderer.resolution(); // TODO: examine how much mem fonts take up and reduce clones if significant Ok(Self { @@ -159,7 +163,7 @@ impl IcedUi { // Avoid panic in graphic cache when minimizing. // Somewhat inefficient for elements that won't change size after a window // resize - let physical_resolution = self.scale.physical_resolution(); + let physical_resolution = renderer.resolution(); if physical_resolution.map(|e| e > 0).reduce_and() { self.renderer .resize(scaled_resolution, physical_resolution, renderer); @@ -214,5 +218,5 @@ impl IcedUi { (messages, mouse_interaction) } - pub fn render(&self, renderer: &mut Renderer) { self.renderer.render(renderer, None); } + pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { self.renderer.render(drawer); } } diff --git a/voxygen/src/ui/ice/renderer/mod.rs b/voxygen/src/ui/ice/renderer/mod.rs index bc7c29af9a..aaa7d0cf98 100644 --- a/voxygen/src/ui/ice/renderer/mod.rs +++ b/voxygen/src/ui/ice/renderer/mod.rs @@ -15,8 +15,8 @@ use super::{ }; use crate::{ render::{ - create_ui_quad, create_ui_quad_vert_gradient, Consts, DynamicModel, Globals, Mesh, - Renderer, UiLocals, UiMode, UiPipeline, + create_ui_quad, create_ui_quad_vert_gradient, DynamicModel, Mesh, Renderer, UiBoundLocals, + UiDrawer, UiLocals, UiMode, UiVertex, }, Error, }; @@ -83,12 +83,11 @@ pub struct IcedRenderer { //image_map: Map<(Image, Rotation)>, cache: Cache, // Model for drawing the ui - model: DynamicModel, + model: DynamicModel, // Consts to specify positions of ingame elements (e.g. Nametags) - ingame_locals: Vec>, + ingame_locals: Vec, // Consts for default ui drawing position (ie the interface) - interface_locals: Consts, - default_globals: Consts, + interface_locals: UiBoundLocals, // Used to delay cache resizing until after current frame is drawn //need_cache_resize: bool, @@ -105,7 +104,7 @@ pub struct IcedRenderer { // Per-frame/update current_state: State, - mesh: Mesh, + mesh: Mesh, glyphs: Vec<(usize, usize, Rgba, Vec2)>, // Output from glyph_brush in the previous frame // It can sometimes ask you to redraw with these instead (idk if that is done with @@ -119,18 +118,19 @@ impl IcedRenderer { pub fn new( renderer: &mut Renderer, scaled_resolution: Vec2, - physical_resolution: Vec2, + physical_resolution: Vec2, default_font: Font, ) -> Result { let (half_res, align, p_scale) = Self::calculate_resolution_dependents(physical_resolution, scaled_resolution); + let interface_locals = renderer.create_ui_bound_locals(&[UiLocals::default()]); + Ok(Self { cache: Cache::new(renderer, default_font)?, draw_commands: Vec::new(), - model: renderer.create_dynamic_model(100)?, - interface_locals: renderer.create_consts(&[UiLocals::default()])?, - default_globals: renderer.create_consts(&[Globals::default()])?, + model: renderer.create_dynamic_model(100), + interface_locals, ingame_locals: Vec::new(), mesh: Mesh::new(), glyphs: Vec::new(), @@ -170,7 +170,7 @@ impl IcedRenderer { pub fn resize( &mut self, scaled_resolution: Vec2, - physical_resolution: Vec2, + physical_resolution: Vec2, renderer: &mut Renderer, ) { self.win_dims = scaled_resolution; @@ -227,22 +227,20 @@ impl IcedRenderer { .push(DrawCommand::plain(self.start..self.mesh.vertices().len()));*/ // Fill in placeholder glyph quads - let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex(); + let (glyph_cache, (cache_tex, _)) = self.cache.glyph_cache_mut_and_tex(); let half_res = self.half_res; let brush_result = glyph_cache.process_queued( |rect, tex_data| { - let offset = [rect.min[0] as u16, rect.min[1] as u16]; - let size = [rect.width() as u16, rect.height() as u16]; + let offset = rect.min; + let size = [rect.width(), rect.height()]; let new_data = tex_data .iter() .map(|x| [255, 255, 255, *x]) .collect::>(); - if let Err(err) = renderer.update_texture(cache_tex, offset, size, &new_data) { - tracing::warn!("Failed to update glyph cache texture: {:?}", err); - } + renderer.update_texture(cache_tex, offset, size, &new_data); }, // Urgh more allocation we don't need |vertex_data| { @@ -313,18 +311,16 @@ impl IcedRenderer { // Create a larger dynamic model if the mesh is larger than the current model // size. - if self.model.vbuf.len() < self.mesh.vertices().len() { - self.model = renderer - .create_dynamic_model(self.mesh.vertices().len() * 4 / 3) - .unwrap(); + if self.model.len() < self.mesh.vertices().len() { + self.model = renderer.create_dynamic_model(self.mesh.vertices().len() * 4 / 3); } // Update model with new mesh. - renderer.update_model(&self.model, &self.mesh, 0).unwrap(); + renderer.update_model(&self.model, &self.mesh, 0); } // Returns (half_res, align) fn calculate_resolution_dependents( - res: Vec2, + res: Vec2, win_dims: Vec2, ) -> (Vec2, Vec2, f32) { let half_res = res.map(|e| e as f32 / 2.0); @@ -335,7 +331,7 @@ impl IcedRenderer { (half_res, align, p_scale) } - fn update_resolution_dependents(&mut self, res: Vec2) { + fn update_resolution_dependents(&mut self, res: Vec2) { let (half_res, align, p_scale) = Self::calculate_resolution_dependents(res, self.win_dims); self.half_res = half_res; self.align = align; @@ -553,7 +549,9 @@ impl IcedRenderer { Some((aabr, tex_id)) => { let cache_dims = graphic_cache .get_tex(tex_id) + .0 .get_dimensions() + .xy() .map(|e| e as f32); let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims; let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims; @@ -773,26 +771,26 @@ impl IcedRenderer { } } - pub fn render(&self, renderer: &mut Renderer, maybe_globals: Option<&Consts>) { + pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { span!(_guard, "render", "IcedRenderer::render"); - let mut scissor = self.window_scissor; - let globals = maybe_globals.unwrap_or(&self.default_globals); - let mut locals = &self.interface_locals; + let mut drawer = drawer.prepare(&self.interface_locals, &self.model, self.window_scissor); for draw_command in self.draw_commands.iter() { match draw_command { DrawCommand::Scissor(new_scissor) => { - scissor = *new_scissor; + drawer.set_scissor(*new_scissor); }, DrawCommand::WorldPos(index) => { - locals = index.map_or(&self.interface_locals, |i| &self.ingame_locals[i]); + drawer.set_locals( + index.map_or(&self.interface_locals, |i| &self.ingame_locals[i]), + ); }, DrawCommand::Draw { kind, verts } => { + // TODO: don't make these: assert!(!verts.is_empty()); let tex = match kind { DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id), DrawKind::Plain => self.cache.glyph_cache_tex(), }; - let model = self.model.submodel(verts.clone()); - renderer.render_ui_element(model, tex, scissor, globals, locals); + drawer.draw(&tex.1, verts.clone()); // Note: trivial clone }, } } @@ -802,7 +800,7 @@ impl IcedRenderer { // Given the the resolution determines the offset needed to align integer // offsets from the center of the sceen to pixels #[inline(always)] -fn align(res: Vec2) -> Vec2 { +fn align(res: Vec2) -> Vec2 { // TODO: does this logic still apply in iced's coordinate system? // If the resolution is odd then the center of the screen will be within the // middle of a pixel so we need to offset by 0.5 pixels to be on the edge of @@ -810,13 +808,13 @@ fn align(res: Vec2) -> Vec2 { res.map(|e| (e & 1) as f32 * 0.5) } -fn default_scissor(physical_resolution: Vec2) -> Aabr { +fn default_scissor(physical_resolution: Vec2) -> Aabr { let (screen_w, screen_h) = physical_resolution.into_tuple(); Aabr { min: Vec2 { x: 0, y: 0 }, max: Vec2 { - x: screen_w, - y: screen_h, + x: screen_w as u16, + y: screen_h as u16, }, } } diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 9a4ea66dc2..415a83be6f 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -27,8 +27,8 @@ pub use widgets::{ use crate::{ render::{ - create_ui_quad, create_ui_tri, Consts, DynamicModel, Globals, Mesh, RenderError, Renderer, - UiLocals, UiMode, UiPipeline, + create_ui_quad, create_ui_tri, DynamicModel, Mesh, RenderError, Renderer, UiBoundLocals, + UiDrawer, UiLocals, UiMode, UiVertex, }, window::Window, Error, @@ -109,14 +109,13 @@ pub struct Ui { draw_commands: Vec, // Mesh buffer for UI vertices; we reuse its allocation in order to limit vector reallocations // during redrawing. - mesh: Mesh, + mesh: Mesh, // Model for drawing the ui - model: DynamicModel, + model: DynamicModel, // Consts for default ui drawing position (ie the interface) - interface_locals: Consts, - default_globals: Consts, + interface_locals: UiBoundLocals, // Consts to specify positions of ingame elements (e.g. Nametags) - ingame_locals: Vec>, + ingame_locals: Vec, // Window size for updating scaling window_resized: Option>, // Scale factor changed @@ -129,6 +128,8 @@ pub struct Ui { tooltip_manager: TooltipManager, // Item tooltips manager item_tooltip_manager: ItemTooltipManager, + // Scissor for the whole window + window_scissor: Aabr, } impl Ui { @@ -138,6 +139,8 @@ impl Ui { let renderer = window.renderer_mut(); + let physical_resolution = renderer.resolution(); + let mut ui = UiBuilder::new(win_dims).build(); // NOTE: Since we redraw the actual frame each time whether or not the UI needs // to be updated, there's no reason to set the redraw count higher than @@ -158,15 +161,16 @@ impl Ui { scale.scale_factor_logical(), ); + let interface_locals = renderer.create_ui_bound_locals(&[UiLocals::default()]); + Ok(Self { ui, image_map: Map::new(), cache: Cache::new(renderer)?, draw_commands: Vec::new(), mesh: Mesh::new(), - model: renderer.create_dynamic_model(100)?, - interface_locals: renderer.create_consts(&[UiLocals::default()])?, - default_globals: renderer.create_consts(&[Globals::default()])?, + model: renderer.create_dynamic_model(100), + interface_locals, ingame_locals: Vec::new(), window_resized: None, scale_factor_changed: None, @@ -174,6 +178,7 @@ impl Ui { scale, tooltip_manager, item_tooltip_manager, + window_scissor: default_scissor(physical_resolution), }) } @@ -336,12 +341,13 @@ impl Ui { self.scale.window_resized(new_dims); let (w, h) = self.scale.scaled_resolution().into_tuple(); self.ui.handle_event(Input::Resize(w, h)); + self.window_scissor = default_scissor(renderer.resolution()); // Avoid panic in graphic cache when minimizing. // Avoid resetting cache if window size didn't change // Somewhat inefficient for elements that won't change size after a window // resize - let res = renderer.get_resolution(); + let res = renderer.resolution(); res.x > 0 && res.y > 0 && !(old_w == w && old_h == h) } else { false @@ -389,7 +395,7 @@ impl Ui { }; let (half_res, x_align, y_align) = { - let res = renderer.get_resolution(); + let res = renderer.resolution(); ( res.map(|e| e as f32 / 2.0), (res.x & 1) as f32 * 0.5, @@ -569,17 +575,15 @@ impl Ui { tracing::debug!("Updating glyphs and clearing text cache."); if let Err(err) = glyph_cache.cache_queued(|rect, data| { - let offset = [rect.min.x as u16, rect.min.y as u16]; - let size = [rect.width() as u16, rect.height() as u16]; + let offset = [rect.min.x as u32, rect.min.y as u32]; + let size = [rect.width() as u32, rect.height() as u32]; let new_data = data .iter() .map(|x| [255, 255, 255, *x]) .collect::>(); - if let Err(err) = renderer.update_texture(cache_tex, offset, size, &new_data) { - warn!("Failed to update texture: {:?}", err); - } + renderer.update_texture(&cache_tex.0, offset, size, &new_data); }) { // FIXME: If we actually hit this error, it's still possible we could salvage // things in various ways (for instance, the current queue might have extra @@ -615,7 +619,7 @@ impl Ui { let mut current_state = State::Plain; let mut start = 0; - let window_scissor = default_scissor(renderer); + let window_scissor = self.window_scissor; let mut current_scissor = window_scissor; let mut ingame_local_index = 0; @@ -672,7 +676,11 @@ impl Ui { if intersection.is_valid() { intersection } else { - Aabr::new_empty(Vec2::zero()) + // TODO: What should we return here + // We used to return a zero sized aabr but it's invalid to + // use a zero sized scissor so for now we just don't change + // the scissor. + current_scissor } }; if new_scissor != current_scissor { @@ -824,7 +832,9 @@ impl Ui { Some((aabr, tex_id)) => { let cache_dims = graphic_cache .get_tex(tex_id) + .0 .get_dimensions() + .xy() .map(|e| e as f32); let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims; let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims; @@ -932,7 +942,7 @@ impl Ui { let pos_on_screen = (view_projection_mat * Vec4::from_point(parameters.pos)) .homogenized(); - let visible = if pos_on_screen.z > -1.0 && pos_on_screen.z < 1.0 { + let visible = if pos_on_screen.z > 0.0 && pos_on_screen.z < 1.0 { let x = pos_on_screen.x; let y = pos_on_screen.y; let (w, h) = parameters.dims.into_tuple(); @@ -958,15 +968,13 @@ impl Ui { // Push new position command let world_pos = Vec4::from_point(parameters.pos); if self.ingame_locals.len() > ingame_local_index { - renderer - .update_consts( - &mut self.ingame_locals[ingame_local_index], - &[world_pos.into()], - ) - .unwrap(); + renderer.update_consts( + &mut self.ingame_locals[ingame_local_index], + &[world_pos.into()], + ) } else { self.ingame_locals - .push(renderer.create_consts(&[world_pos.into()]).unwrap()); + .push(renderer.create_ui_bound_locals(&[world_pos.into()])); } self.draw_commands .push(DrawCommand::WorldPos(Some(ingame_local_index))); @@ -1011,48 +1019,45 @@ impl Ui { // Create a larger dynamic model if the mesh is larger than the current model // size. - if self.model.vbuf.len() < self.mesh.vertices().len() { - self.model = renderer - .create_dynamic_model(self.mesh.vertices().len() * 4 / 3) - .unwrap(); + if self.model.len() < self.mesh.vertices().len() { + self.model = renderer.create_dynamic_model(self.mesh.vertices().len() * 4 / 3); } // Update model with new mesh. - renderer.update_model(&self.model, &self.mesh, 0).unwrap(); + renderer.update_model(&self.model, &self.mesh, 0); } - pub fn render(&self, renderer: &mut Renderer, maybe_globals: Option<&Consts>) { + pub fn render<'pass, 'data: 'pass>(&'data self, drawer: &mut UiDrawer<'_, 'pass>) { span!(_guard, "render", "Ui::render"); - let mut scissor = default_scissor(renderer); - let globals = maybe_globals.unwrap_or(&self.default_globals); - let mut locals = &self.interface_locals; + let mut drawer = drawer.prepare(&self.interface_locals, &self.model, self.window_scissor); for draw_command in self.draw_commands.iter() { match draw_command { DrawCommand::Scissor(new_scissor) => { - scissor = *new_scissor; + drawer.set_scissor(*new_scissor); }, DrawCommand::WorldPos(index) => { - locals = index.map_or(&self.interface_locals, |i| &self.ingame_locals[i]); + drawer.set_locals( + index.map_or(&self.interface_locals, |i| &self.ingame_locals[i]), + ); }, DrawCommand::Draw { kind, verts } => { let tex = match kind { DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id), DrawKind::Plain => self.cache.glyph_cache_tex(), }; - let model = self.model.submodel(verts.clone()); - renderer.render_ui_element(model, tex, scissor, globals, locals); + drawer.draw(&tex.1, verts.clone()); // Note: trivial clone }, } } } } -fn default_scissor(renderer: &Renderer) -> Aabr { - let (screen_w, screen_h) = renderer.get_resolution().into_tuple(); +fn default_scissor(physical_resolution: Vec2) -> Aabr { + let (screen_w, screen_h) = physical_resolution.into_tuple(); Aabr { min: Vec2 { x: 0, y: 0 }, max: Vec2 { - x: screen_w, - y: screen_h, + x: screen_w as u16, + y: screen_h as u16, }, } } diff --git a/voxygen/src/ui/scale.rs b/voxygen/src/ui/scale.rs index 1d6c1e890c..a609a6c80c 100644 --- a/voxygen/src/ui/scale.rs +++ b/voxygen/src/ui/scale.rs @@ -103,11 +103,6 @@ impl Scale { /// Get logical window size pub fn logical_resolution(&self) -> Vec2 { self.window_dims } - /// Get physical window size - pub fn physical_resolution(&self) -> Vec2 { - (self.window_dims * self.scale_factor).map(|e| e.round() as u16) - } - // Transform point from logical to scaled coordinates. pub fn scale_point(&self, point: Vec2) -> Vec2 { point / self.scale_factor_logical() } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 06179e860a..e72590f769 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -1,6 +1,6 @@ use crate::{ controller::*, - render::{Renderer, WinColorFmt, WinDepthFmt}, + render::Renderer, settings::{ControlSettings, Settings}, ui, Error, }; @@ -10,9 +10,8 @@ use gilrs::{EventType, Gilrs}; use hashbrown::HashMap; use itertools::Itertools; use keyboard_keynames::key_layout::KeyLayout; -use old_school_gfx_glutin_ext::{ContextBuilderExt, WindowInitExt, WindowUpdateExt}; use serde::{Deserialize, Serialize}; -use tracing::{error, info, warn}; +use tracing::{error, warn}; use vek::*; use winit::monitor::VideoMode; @@ -519,7 +518,7 @@ impl KeyMouse { pub struct Window { renderer: Renderer, - window: glutin::ContextWrapper, + window: winit::window::Window, cursor_grabbed: bool, pub pan_sensitivity: u32, pub zoom_sensitivity: u32, @@ -565,26 +564,9 @@ impl Window { false, ); - let (window, device, factory, win_color_view, win_depth_view) = - glutin::ContextBuilder::new() - .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 3))) - .with_vsync(false) - .with_gfx_color_depth::() - .build_windowed(win_builder, &event_loop) - .map_err(|err| Error::BackendError(Box::new(err)))? - .init_gfx::(); + let window = win_builder.build(&event_loop).unwrap(); - let vendor = device.get_info().platform_name.vendor; - let renderer = device.get_info().platform_name.renderer; - let opengl_version = device.get_info().version; - let glsl_version = device.get_info().shading_language; - info!( - ?vendor, - ?renderer, - ?opengl_version, - ?glsl_version, - "selected graphics device" - ); + let renderer = Renderer::new(&window, settings.graphics.render_mode.clone())?; let keypress_map = HashMap::new(); @@ -617,9 +599,9 @@ impl Window { channel::Receiver, ) = channel::unbounded::(); - let scale_factor = window.window().scale_factor(); + let scale_factor = window.scale_factor(); - let key_layout = match KeyLayout::new_from_window(window.window()) { + let key_layout = match KeyLayout::new_from_window(&window) { Ok(kl) => Some(kl), Err(err) => { warn!( @@ -632,13 +614,7 @@ impl Window { }; let mut this = Self { - renderer: Renderer::new( - device, - factory, - win_color_view, - win_depth_view, - settings.graphics.render_mode.clone(), - )?, + renderer, window, cursor_grabbed: false, pan_sensitivity: settings.gameplay.pan_sensitivity, @@ -687,6 +663,7 @@ impl Window { } pub fn fetch_events(&mut self) -> Vec { + span!(_guard, "fetch_events", "Window::fetch_events"); // Refresh ui size (used when changing playstates) if self.needs_refresh_resize { let logical_size = self.logical_size(); @@ -951,11 +928,15 @@ impl Window { match event { WindowEvent::CloseRequested => self.events.push(Event::Close), - WindowEvent::Resized(physical) => { - let (mut color_view, mut depth_view) = self.renderer.win_views_mut(); - self.window.resize(physical); - self.window.update_gfx(&mut color_view, &mut depth_view); - self.renderer.on_resize().unwrap(); + WindowEvent::Resized(_) => { + // We don't use the event provided size because since this event + // more could have happened making the value wrong so we query + // directly from the window, this prevents some errors + let physical = self.window.inner_size(); + + self.renderer + .on_resize(Vec2::new(physical.width, physical.height)) + .unwrap(); // TODO: update users of this event with the fact that it is now the physical // size let winit::dpi::PhysicalSize { width, height } = physical; @@ -963,6 +944,7 @@ impl Window { .push(Event::Resize(Vec2::new(width as u32, height as u32))); }, WindowEvent::ScaleFactorChanged { scale_factor, .. } => { + // TODO: is window resized event emitted? or do we need to handle that here? self.scale_factor = scale_factor; self.events.push(Event::ScaleFactorChanged(scale_factor)); }, @@ -1099,32 +1081,24 @@ impl Window { /// Moves cursor by an offset pub fn offset_cursor(&self, d: Vec2) { if d != Vec2::zero() { - if let Err(err) = - self.window - .window() - .set_cursor_position(winit::dpi::LogicalPosition::new( - d.x as f64 + self.cursor_position.x, - d.y as f64 + self.cursor_position.y, - )) + if let Err(err) = self + .window + .set_cursor_position(winit::dpi::LogicalPosition::new( + d.x as f64 + self.cursor_position.x, + d.y as f64 + self.cursor_position.y, + )) { error!("Error setting cursor position: {:?}", err); } } } - pub fn swap_buffers(&self) -> Result<(), Error> { - span!(_guard, "swap_buffers", "Window::swap_buffers"); - self.window - .swap_buffers() - .map_err(|err| Error::BackendError(Box::new(err))) - } - pub fn is_cursor_grabbed(&self) -> bool { self.cursor_grabbed } pub fn grab_cursor(&mut self, grab: bool) { self.cursor_grabbed = grab; - self.window.window().set_cursor_visible(!grab); - let _ = self.window.window().set_cursor_grab(grab); + self.window.set_cursor_visible(!grab); + let _ = self.window.set_cursor_grab(grab); } /// Moves mouse cursor to center of screen @@ -1132,13 +1106,12 @@ impl Window { pub fn center_cursor(&self) { let dimensions: Vec2 = self.logical_size(); - if let Err(err) = - self.window - .window() - .set_cursor_position(winit::dpi::PhysicalPosition::new( - dimensions[0] / (2_f64), - dimensions[1] / (2_f64), - )) + if let Err(err) = self + .window + .set_cursor_position(winit::dpi::PhysicalPosition::new( + dimensions[0] / (2_f64), + dimensions[1] / (2_f64), + )) { error!("Error centering cursor position: {:?}", err); } @@ -1170,8 +1143,7 @@ impl Window { // the correct resolution already, load that value, otherwise filter it // in this iteration let correct_res = correct_res.unwrap_or_else(|| { - let window = self.window.window(); - window + self.window .current_monitor() .unwrap() .video_modes() @@ -1323,7 +1295,6 @@ impl Window { self .window - .window() .current_monitor().unwrap() .video_modes() // Prefer bit depth over refresh rate @@ -1335,7 +1306,7 @@ impl Window { } pub fn set_fullscreen_mode(&mut self, fullscreen: FullScreenSettings) { - let window = self.window.window(); + let window = &self.window; self.fullscreen = fullscreen; window.set_fullscreen(fullscreen.enabled.then(|| match fullscreen.mode { FullscreenMode::Exclusive => { @@ -1357,61 +1328,50 @@ impl Window { pub fn logical_size(&self) -> Vec2 { let (w, h) = self .window - .window() .inner_size() - .to_logical::(self.window.window().scale_factor()) + .to_logical::(self.window.scale_factor()) .into(); Vec2::new(w, h) } pub fn set_size(&mut self, new_size: Vec2) { - self.window - .window() - .set_inner_size(glutin::dpi::LogicalSize::new( - new_size.x as f64, - new_size.y as f64, - )); + self.window.set_inner_size(winit::dpi::LogicalSize::new( + new_size.x as f64, + new_size.y as f64, + )); } pub fn send_event(&mut self, event: Event) { self.events.push(event) } pub fn take_screenshot(&mut self, settings: &Settings) { - match self.renderer.create_screenshot() { - Ok(img) => { - let mut path = settings.screenshots_path.clone(); - let sender = self.message_sender.clone(); - - let builder = std::thread::Builder::new().name("screenshot".into()); - builder - .spawn(move || { - use std::time::SystemTime; - // Check if folder exists and create it if it does not - if !path.exists() { - if let Err(e) = std::fs::create_dir_all(&path) { - warn!(?e, "Couldn't create folder for screenshot"); - let _result = sender - .send(String::from("Couldn't create folder for screenshot")); - } - } - path.push(format!( - "screenshot_{}.png", - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0) - )); - if let Err(e) = img.save(&path) { - warn!(?e, "Couldn't save screenshot"); - let _result = sender.send(String::from("Couldn't save screenshot")); - } else { - let _result = sender - .send(format!("Screenshot saved to {}", path.to_string_lossy())); - } - }) - .unwrap(); - }, - Err(e) => error!(?e, "Couldn't create screenshot due to renderer error"), - } + let sender = self.message_sender.clone(); + let mut path = settings.screenshots_path.clone(); + self.renderer.create_screenshot(move |image| { + use std::time::SystemTime; + // Check if folder exists and create it if it does not + if !path.exists() { + if let Err(e) = std::fs::create_dir_all(&path) { + warn!(?e, "Couldn't create folder for screenshot"); + let _result = + sender.send(String::from("Couldn't create folder for screenshot")); + } + } + path.push(format!( + "screenshot_{}.png", + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|d| d.as_millis()) + .unwrap_or(0) + )); + // Try to save the image + if let Err(e) = image.into_rgba8().save(&path) { + warn!(?e, "Couldn't save screenshot"); + let _result = sender.send(String::from("Couldn't save screenshot")); + } else { + let _result = + sender.send(format!("Screenshot saved to {}", path.to_string_lossy())); + } + }); } fn is_pressed( @@ -1458,7 +1418,7 @@ impl Window { self.remapping_keybindings = Some(game_input); } - pub fn window(&self) -> &winit::window::Window { self.window.window() } + pub fn window(&self) -> &winit::window::Window { &self.window } pub fn modifiers(&self) -> winit::event::ModifiersState { self.modifiers }