diff --git a/Cargo.lock b/Cargo.lock
index 79d93d624b..87bc42f8d3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -170,7 +170,7 @@ checksum = "0609c78bd572f4edc74310dfb63a01f5609d53fa8b4dd7c4d98aef3b3e8d72d1"
 dependencies = [
  "proc-macro-hack",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -199,11 +199,11 @@ dependencies = [
 
 [[package]]
 name = "ash"
-version = "0.32.1"
+version = "0.31.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06063a002a77d2734631db74e8f4ce7148b77fe522e6bca46f2ae7774fd48112"
+checksum = "c69a8137596e84c22d57f3da1b5de1d4230b1742a710091c85f4d7ce50f00f38"
 dependencies = [
- "libloading 0.7.0",
+ "libloading 0.6.7",
 ]
 
 [[package]]
@@ -242,7 +242,7 @@ checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -446,7 +446,7 @@ checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -693,16 +693,6 @@ 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"
@@ -1223,10 +1213,11 @@ dependencies = [
 [[package]]
 name = "d3d12"
 version = "0.3.2"
-source = "git+https://github.com/gfx-rs/d3d12-rs?rev=be19a243b86e0bafb9937d661fc8eabb3e42b44e#be19a243b86e0bafb9937d661fc8eabb3e42b44e"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a60cceb22c7c53035f8980524fdc7f17cf49681a3c154e6757d30afbec6ec4"
 dependencies = [
  "bitflags",
- "libloading 0.7.0",
+ "libloading 0.6.7",
  "winapi 0.3.9",
 ]
 
@@ -1270,7 +1261,7 @@ dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
  "strsim 0.9.3",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -1284,7 +1275,7 @@ dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
  "strsim 0.10.0",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -1295,7 +1286,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
 dependencies = [
  "darling_core 0.10.2",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -1306,7 +1297,7 @@ checksum = "0a7a1445d54b2f9792e3b31a3e715feabbace393f38dc4ffd49d94ee9bc487d5"
 dependencies = [
  "darling_core 0.12.3",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -1336,7 +1327,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -1468,7 +1459,7 @@ checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -1489,7 +1480,7 @@ dependencies = [
  "darling 0.12.3",
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -1555,7 +1546,7 @@ checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -1768,7 +1759,7 @@ dependencies = [
  "proc-macro-hack",
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -1875,26 +1866,59 @@ dependencies = [
 [[package]]
 name = "gfx-auxil"
 version = "0.8.0"
-source = "git+https://github.com/gfx-rs/gfx?rev=32684a7da923cfd661fe4d3003f4275270e9c40d#32684a7da923cfd661fe4d3003f4275270e9c40d"
+source = "git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7#d54cdcfac68711a91b55682c56da09f8e5b6f4e7"
 dependencies = [
  "fxhash",
- "gfx-hal",
+ "gfx-hal 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "spirv_cross",
+]
+
+[[package]]
+name = "gfx-auxil"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7b33ecf067f2117668d91c9b0f2e5f223ebd1ffec314caa2f3de27bb580186d"
+dependencies = [
+ "fxhash",
+ "gfx-hal 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "spirv_cross",
 ]
 
 [[package]]
 name = "gfx-backend-dx11"
 version = "0.7.0"
-source = "git+https://github.com/gfx-rs/gfx?rev=32684a7da923cfd661fe4d3003f4275270e9c40d#32684a7da923cfd661fe4d3003f4275270e9c40d"
+source = "git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7#d54cdcfac68711a91b55682c56da09f8e5b6f4e7"
 dependencies = [
  "arrayvec",
  "bitflags",
- "gfx-auxil",
- "gfx-hal",
- "libloading 0.7.0",
+ "gfx-auxil 0.8.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "gfx-hal 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "libloading 0.6.7",
  "log",
  "parking_lot 0.11.1",
- "range-alloc",
+ "range-alloc 0.1.2 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "raw-window-handle",
+ "smallvec",
+ "spirv_cross",
+ "thunderdome",
+ "winapi 0.3.9",
+ "wio",
+]
+
+[[package]]
+name = "gfx-backend-dx11"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f851d03c2e8f117e3702bf41201a4fafa447d5cb1276d5375870ae7573d069dd"
+dependencies = [
+ "arrayvec",
+ "bitflags",
+ "gfx-auxil 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gfx-hal 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libloading 0.6.7",
+ "log",
+ "parking_lot 0.11.1",
+ "range-alloc 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "raw-window-handle",
  "smallvec",
  "spirv_cross",
@@ -1906,17 +1930,38 @@ dependencies = [
 [[package]]
 name = "gfx-backend-dx12"
 version = "0.7.0"
-source = "git+https://github.com/gfx-rs/gfx?rev=32684a7da923cfd661fe4d3003f4275270e9c40d#32684a7da923cfd661fe4d3003f4275270e9c40d"
+source = "git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7#d54cdcfac68711a91b55682c56da09f8e5b6f4e7"
 dependencies = [
  "arrayvec",
  "bit-set",
  "bitflags",
  "d3d12",
- "gfx-auxil",
- "gfx-hal",
+ "gfx-auxil 0.8.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "gfx-hal 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
  "log",
  "parking_lot 0.11.1",
- "range-alloc",
+ "range-alloc 0.1.2 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "raw-window-handle",
+ "smallvec",
+ "spirv_cross",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "gfx-backend-dx12"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5032d716a2a5f4dafb4675a794c5dc32081af8fbc7303c93ad93ff5413c6559f"
+dependencies = [
+ "arrayvec",
+ "bit-set",
+ "bitflags",
+ "d3d12",
+ "gfx-auxil 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gfx-hal 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log",
+ "parking_lot 0.11.1",
+ "range-alloc 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "raw-window-handle",
  "smallvec",
  "spirv_cross",
@@ -1927,9 +1972,20 @@ dependencies = [
 [[package]]
 name = "gfx-backend-empty"
 version = "0.7.0"
-source = "git+https://github.com/gfx-rs/gfx?rev=32684a7da923cfd661fe4d3003f4275270e9c40d#32684a7da923cfd661fe4d3003f4275270e9c40d"
+source = "git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7#d54cdcfac68711a91b55682c56da09f8e5b6f4e7"
 dependencies = [
- "gfx-hal",
+ "gfx-hal 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "log",
+ "raw-window-handle",
+]
+
+[[package]]
+name = "gfx-backend-empty"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f07ef26a65954cfdd7b4c587f485100d1bb3b0bd6a51b02d817d6c87cca7a91"
+dependencies = [
+ "gfx-hal 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log",
  "raw-window-handle",
 ]
@@ -1937,20 +1993,44 @@ dependencies = [
 [[package]]
 name = "gfx-backend-gl"
 version = "0.7.0"
-source = "git+https://github.com/gfx-rs/gfx?rev=32684a7da923cfd661fe4d3003f4275270e9c40d#32684a7da923cfd661fe4d3003f4275270e9c40d"
+source = "git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7#d54cdcfac68711a91b55682c56da09f8e5b6f4e7"
 dependencies = [
  "arrayvec",
  "bitflags",
- "fxhash",
- "gfx-hal",
+ "gfx-auxil 0.8.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "gfx-hal 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
  "glow",
  "js-sys",
  "khronos-egl",
- "libloading 0.7.0",
+ "libloading 0.6.7",
  "log",
- "naga",
+ "naga 0.3.1",
  "parking_lot 0.11.1",
  "raw-window-handle",
+ "spirv_cross",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gfx-backend-gl"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6717c50ab601efe4a669bfb44db615e3888695ac8263222aeaa702642b9fbc2"
+dependencies = [
+ "arrayvec",
+ "bitflags",
+ "gfx-auxil 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gfx-hal 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glow",
+ "js-sys",
+ "khronos-egl",
+ "libloading 0.6.7",
+ "log",
+ "naga 0.3.2",
+ "parking_lot 0.11.1",
+ "raw-window-handle",
+ "spirv_cross",
  "wasm-bindgen",
  "web-sys",
 ]
@@ -1958,7 +2038,7 @@ dependencies = [
 [[package]]
 name = "gfx-backend-metal"
 version = "0.7.0"
-source = "git+https://github.com/gfx-rs/gfx?rev=32684a7da923cfd661fe4d3003f4275270e9c40d#32684a7da923cfd661fe4d3003f4275270e9c40d"
+source = "git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7#d54cdcfac68711a91b55682c56da09f8e5b6f4e7"
 dependencies = [
  "arrayvec",
  "bitflags",
@@ -1966,32 +2046,78 @@ dependencies = [
  "cocoa-foundation",
  "copyless",
  "foreign-types",
- "fxhash",
- "gfx-hal",
+ "gfx-auxil 0.8.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "gfx-hal 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
  "log",
- "metal",
- "naga",
+ "metal 0.21.0 (git+https://github.com/gfx-rs/metal-rs?rev=439c986eb7a9b91e88b61def2daa66e4043fcbef)",
+ "naga 0.3.1",
  "objc",
  "parking_lot 0.11.1",
- "profiling",
- "range-alloc",
+ "range-alloc 0.1.2 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
  "raw-window-handle",
+ "spirv_cross",
+ "storage-map",
+]
+
+[[package]]
+name = "gfx-backend-metal"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dc54b456ece69ef49f8893269ebf24ac70969ed34ba2719c3f3abcc8fbff14e"
+dependencies = [
+ "arrayvec",
+ "bitflags",
+ "block",
+ "cocoa-foundation",
+ "copyless",
+ "foreign-types",
+ "gfx-auxil 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gfx-hal 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log",
+ "metal 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "naga 0.3.2",
+ "objc",
+ "parking_lot 0.11.1",
+ "range-alloc 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "raw-window-handle",
+ "spirv_cross",
  "storage-map",
 ]
 
 [[package]]
 name = "gfx-backend-vulkan"
 version = "0.7.0"
-source = "git+https://github.com/gfx-rs/gfx?rev=32684a7da923cfd661fe4d3003f4275270e9c40d#32684a7da923cfd661fe4d3003f4275270e9c40d"
+source = "git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7#d54cdcfac68711a91b55682c56da09f8e5b6f4e7"
 dependencies = [
  "arrayvec",
  "ash",
  "byteorder",
  "core-graphics-types",
- "gfx-hal",
+ "gfx-hal 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
  "inplace_it",
  "log",
- "naga",
+ "naga 0.3.1",
+ "objc",
+ "parking_lot 0.11.1",
+ "raw-window-handle",
+ "smallvec",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "gfx-backend-vulkan"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dabe88b1a5c91e0f969b441cc57e70364858066e4ba937deeb62065654ef9bd9"
+dependencies = [
+ "arrayvec",
+ "ash",
+ "byteorder",
+ "core-graphics-types",
+ "gfx-hal 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "inplace_it",
+ "log",
+ "naga 0.3.2",
  "objc",
  "parking_lot 0.11.1",
  "raw-window-handle",
@@ -2002,10 +2128,22 @@ dependencies = [
 [[package]]
 name = "gfx-hal"
 version = "0.7.0"
-source = "git+https://github.com/gfx-rs/gfx?rev=32684a7da923cfd661fe4d3003f4275270e9c40d#32684a7da923cfd661fe4d3003f4275270e9c40d"
+source = "git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7#d54cdcfac68711a91b55682c56da09f8e5b6f4e7"
 dependencies = [
  "bitflags",
- "naga",
+ "naga 0.3.1",
+ "raw-window-handle",
+ "thiserror",
+]
+
+[[package]]
+name = "gfx-hal"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1d9cc8d3b573dda62d0baca4f02e0209786e22c562caff001d77c389008781d"
+dependencies = [
+ "bitflags",
+ "naga 0.3.2",
  "raw-window-handle",
  "thiserror",
 ]
@@ -2142,12 +2280,13 @@ dependencies = [
 
 [[package]]
 name = "gpu-alloc"
-version = "0.4.5"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc76088804bb65a6f3b880bea9306fdaeffb25ebb453105fafa691282ee9fdba"
+checksum = "1e7724b9aef57ea36d70faf54e0ee6265f86e41de16bed8333efdeab5b00e16b"
 dependencies = [
  "bitflags",
  "gpu-alloc-types",
+ "tracing",
 ]
 
 [[package]]
@@ -2168,6 +2307,7 @@ dependencies = [
  "bitflags",
  "gpu-descriptor-types",
  "hashbrown",
+ "tracing",
 ]
 
 [[package]]
@@ -2603,9 +2743,9 @@ checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
 
 [[package]]
 name = "js-sys"
-version = "0.3.50"
+version = "0.3.46"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c"
+checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -2636,12 +2776,12 @@ dependencies = [
 
 [[package]]
 name = "khronos-egl"
-version = "4.1.0"
+version = "3.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3"
+checksum = "b19cc4a81304db2a0ad69740e83cdc3a9364e3f9bd6d88a87288a4c2deec927b"
 dependencies = [
  "libc",
- "libloading 0.7.0",
+ "libloading 0.6.7",
 ]
 
 [[package]]
@@ -2921,7 +3061,21 @@ dependencies = [
 [[package]]
 name = "metal"
 version = "0.21.0"
-source = "git+https://github.com/gfx-rs/metal-rs?rev=78f632d194c7c16d18b71d7373c4080847d110b0#78f632d194c7c16d18b71d7373c4080847d110b0"
+source = "git+https://github.com/gfx-rs/metal-rs?rev=439c986eb7a9b91e88b61def2daa66e4043fcbef#439c986eb7a9b91e88b61def2daa66e4043fcbef"
+dependencies = [
+ "bitflags",
+ "block",
+ "cocoa-foundation",
+ "foreign-types",
+ "log",
+ "objc",
+]
+
+[[package]]
+name = "metal"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c"
 dependencies = [
  "bitflags",
  "block",
@@ -3049,11 +3203,26 @@ checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 [[package]]
 name = "naga"
 version = "0.3.1"
-source = "git+https://github.com/gfx-rs/naga?tag=gfx-23#4a5ff9a0538510ff3c3efa171941bfb44fc1be9c"
+source = "git+https://github.com/gfx-rs/naga?tag=gfx-9#c12003f5648fcade5f20c01debc4cb12bd47073e"
+dependencies = [
+ "bit-set",
+ "bitflags",
+ "fxhash",
+ "log",
+ "num-traits",
+ "petgraph 0.5.1",
+ "spirv_headers",
+ "thiserror",
+]
+
+[[package]]
+name = "naga"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05089b2acdf0e6a962cdbf5e328402345a27f59fcde1a59fe97a73e8149d416f"
 dependencies = [
  "bit-set",
  "bitflags",
- "codespan-reporting",
  "fxhash",
  "log",
  "num-traits",
@@ -3143,7 +3312,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -3372,7 +3541,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -3480,7 +3649,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -3492,7 +3661,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -3769,7 +3938,7 @@ checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -3889,7 +4058,7 @@ dependencies = [
  "proc-macro-error-attr",
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
  "version_check 0.9.3",
 ]
 
@@ -3934,12 +4103,6 @@ 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"
-
 [[package]]
 name = "prometheus"
 version = "0.12.0"
@@ -4142,7 +4305,13 @@ dependencies = [
 [[package]]
 name = "range-alloc"
 version = "0.1.2"
-source = "git+https://github.com/gfx-rs/gfx?rev=32684a7da923cfd661fe4d3003f4275270e9c40d#32684a7da923cfd661fe4d3003f4275270e9c40d"
+source = "git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7#d54cdcfac68711a91b55682c56da09f8e5b6f4e7"
+
+[[package]]
+name = "range-alloc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6"
 
 [[package]]
 name = "raw-window-handle"
@@ -4253,7 +4422,7 @@ dependencies = [
  "quote 1.0.9",
  "refinery-core",
  "regex",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -4657,7 +4826,7 @@ checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -4679,7 +4848,7 @@ checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -4761,7 +4930,7 @@ checksum = "d5404c36bd155e41a54276ab6aafedad2fb627e5e5849d36ec439c9ddc044a2f"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -4908,7 +5077,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.65",
 ]
 
 [[package]]
@@ -5009,7 +5178,7 @@ dependencies = [
  "quote 1.0.9",
  "serde",
  "serde_derive",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -5025,7 +5194,7 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "sha1",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -5088,7 +5257,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -5096,9 +5265,6 @@ name = "strum"
 version = "0.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c"
-dependencies = [
- "strum_macros",
-]
 
 [[package]]
 name = "strum_macros"
@@ -5109,7 +5275,7 @@ dependencies = [
  "heck",
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -5137,27 +5303,15 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.69"
+version = "1.0.65"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
+checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
  "unicode-xid 0.2.1",
 ]
 
-[[package]]
-name = "synstructure"
-version = "0.12.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
-dependencies = [
- "proc-macro2 1.0.26",
- "quote 1.0.9",
- "syn 1.0.69",
- "unicode-xid 0.2.1",
-]
-
 [[package]]
 name = "tap"
 version = "1.0.1"
@@ -5230,7 +5384,7 @@ checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -5310,7 +5464,7 @@ checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -5395,7 +5549,7 @@ checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -5902,7 +6056,7 @@ version = "0.1.0"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -5989,6 +6143,7 @@ version = "0.9.0"
 dependencies = [
  "backtrace",
  "bincode",
+ "bytemuck",
  "chrono",
  "conrod_core",
  "conrod_winit",
@@ -6044,11 +6199,11 @@ dependencies = [
  "veloren-server",
  "veloren-voxygen-anim",
  "veloren-world",
- "wgpu",
+ "wgpu 0.7.0",
+ "wgpu-profiler",
  "window_clipboard 0.2.0",
  "winit",
  "winres",
- "zerocopy",
 ]
 
 [[package]]
@@ -6183,9 +6338,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.73"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9"
+checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
 dependencies = [
  "cfg-if 1.0.0",
  "wasm-bindgen-macro",
@@ -6193,24 +6348,24 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.73"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae"
+checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
 dependencies = [
  "bumpalo",
  "lazy_static",
  "log",
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.23"
+version = "0.4.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81b8b767af23de6ac18bf2168b690bed2902743ddf0fb39252e36f9e2bfc63ea"
+checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35"
 dependencies = [
  "cfg-if 1.0.0",
  "js-sys",
@@ -6220,9 +6375,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.73"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f"
+checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
 dependencies = [
  "quote 1.0.9",
  "wasm-bindgen-macro-support",
@@ -6230,22 +6385,22 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.73"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
+checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
 dependencies = [
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.73"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489"
+checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
 
 [[package]]
 name = "wasmer"
@@ -6315,7 +6470,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2 1.0.26",
  "quote 1.0.9",
- "syn 1.0.69",
+ "syn 1.0.65",
 ]
 
 [[package]]
@@ -6590,9 +6745,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.50"
+version = "0.3.46"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
+checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -6620,56 +6775,124 @@ dependencies = [
 
 [[package]]
 name = "wgpu"
-version = "0.8.0"
-source = "git+https://github.com/gfx-rs/wgpu-rs.git#0c5a5fc90fdd450a246353591fa6aeeb3f0aba25"
+version = "0.7.0"
+source = "git+https://github.com/gfx-rs/wgpu-rs.git?rev=1f1a7e5dd47a1610733bbc3989414acb62395359#1f1a7e5dd47a1610733bbc3989414acb62395359"
 dependencies = [
  "arrayvec",
  "js-sys",
- "log",
- "naga",
+ "naga 0.3.1",
  "parking_lot 0.11.1",
  "raw-window-handle",
  "smallvec",
+ "tracing",
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "web-sys",
- "wgpu-core",
- "wgpu-types",
+ "wgpu-core 0.7.0",
+ "wgpu-types 0.7.0 (git+https://github.com/gfx-rs/wgpu?rev=3ebe198911b46cb77fcdc481f7d0daf9a962b82e)",
+]
+
+[[package]]
+name = "wgpu"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79a0a0a63fac9492cfaf6e7e4bdf9729c128f1e94124b9e4cbc4004b8cb6d1d8"
+dependencies = [
+ "arrayvec",
+ "js-sys",
+ "naga 0.3.2",
+ "parking_lot 0.11.1",
+ "raw-window-handle",
+ "smallvec",
+ "syn 1.0.65",
+ "tracing",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "wgpu-core 0.7.1",
+ "wgpu-types 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "wgpu-core"
-version = "0.8.0"
-source = "git+https://github.com/gfx-rs/wgpu?rev=e5ddb94be0221b0f53a8f43adfb15458daebfd7c#e5ddb94be0221b0f53a8f43adfb15458daebfd7c"
+version = "0.7.0"
+source = "git+https://github.com/gfx-rs/wgpu?rev=3ebe198911b46cb77fcdc481f7d0daf9a962b82e#3ebe198911b46cb77fcdc481f7d0daf9a962b82e"
 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",
+ "gfx-backend-dx11 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "gfx-backend-dx12 0.7.0",
+ "gfx-backend-empty 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "gfx-backend-gl 0.7.0",
+ "gfx-backend-metal 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "gfx-backend-vulkan 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
+ "gfx-hal 0.7.0 (git+https://github.com/gfx-rs/gfx?rev=d54cdcfac68711a91b55682c56da09f8e5b6f4e7)",
  "gpu-alloc",
  "gpu-descriptor",
- "log",
- "naga",
+ "naga 0.3.1",
  "parking_lot 0.11.1",
- "profiling",
  "raw-window-handle",
  "smallvec",
  "thiserror",
- "wgpu-types",
+ "tracing",
+ "wgpu-types 0.7.0 (git+https://github.com/gfx-rs/wgpu?rev=3ebe198911b46cb77fcdc481f7d0daf9a962b82e)",
+]
+
+[[package]]
+name = "wgpu-core"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89fa2cc5d72236461ac09c5be967012663e29cb62f1a972654cbf35e49dffa8"
+dependencies = [
+ "arrayvec",
+ "bitflags",
+ "cfg_aliases",
+ "copyless",
+ "fxhash",
+ "gfx-backend-dx11 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gfx-backend-dx12 0.7.1",
+ "gfx-backend-empty 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gfx-backend-gl 0.7.1",
+ "gfx-backend-metal 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gfx-backend-vulkan 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gfx-hal 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gpu-alloc",
+ "gpu-descriptor",
+ "naga 0.3.2",
+ "parking_lot 0.11.1",
+ "raw-window-handle",
+ "smallvec",
+ "thiserror",
+ "tracing",
+ "wgpu-types 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "wgpu-profiler"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98580e756637420f660385d6ec86e97080b588325daa3326d0229b39307d5aa3"
+dependencies = [
+ "futures",
+ "wgpu 0.7.1",
 ]
 
 [[package]]
 name = "wgpu-types"
-version = "0.8.0"
-source = "git+https://github.com/gfx-rs/wgpu?rev=e5ddb94be0221b0f53a8f43adfb15458daebfd7c#e5ddb94be0221b0f53a8f43adfb15458daebfd7c"
+version = "0.7.0"
+source = "git+https://github.com/gfx-rs/wgpu?rev=3ebe198911b46cb77fcdc481f7d0daf9a962b82e#3ebe198911b46cb77fcdc481f7d0daf9a962b82e"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "wgpu-types"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72fa9ba80626278fd87351555c363378d08122d7601e58319be3d6fa85a87747"
 dependencies = [
  "bitflags",
 ]
@@ -6947,24 +7170,3 @@ checksum = "0de7bff972b4f2a06c85f6d8454b09df153af7e3a4ec2aac81db1b105b684ddb"
 dependencies = [
  "chrono",
 ]
-
-[[package]]
-name = "zerocopy"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46"
-dependencies = [
- "byteorder",
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb"
-dependencies = [
- "proc-macro2 1.0.26",
- "syn 1.0.69",
- "synstructure",
-]
diff --git a/Cargo.toml b/Cargo.toml
index 8d31bf82bc..bc4135ca98 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -113,3 +113,5 @@ nativeBuildInputs = ["pkg-config"]
 # 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" }
+# patch wgpu so we can use wgpu-profiler crate
+wgpu = { git = "https://github.com/gfx-rs/wgpu-rs.git", rev = "1f1a7e5dd47a1610733bbc3989414acb62395359" }
diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml
index 4e6bdbe6a6..5a741afbd4 100644
--- a/voxygen/Cargo.toml
+++ b/voxygen/Cargo.toml
@@ -46,6 +46,7 @@ i18n = {package = "veloren-i18n", path = "i18n"}
 # Graphics
 winit = {version = "0.24.0", features = ["serde"]}
 wgpu = { git = "https://github.com/gfx-rs/wgpu-rs.git", rev = "1f1a7e5dd47a1610733bbc3989414acb62395359" }
+wgpu-profiler = "0.2.1"
 bytemuck = { version="1.4", features=["derive"] }
 shaderc = "0.6.2"
 
@@ -96,8 +97,7 @@ rand = "0.8"
 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 = { version = "0.20.0", features = ["derive"] } 
+strum = "0.20"
 strum_macros = "0.20"
 treeculler = "0.2"
 tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] }
diff --git a/voxygen/src/hud/settings_window/video.rs b/voxygen/src/hud/settings_window/video.rs
index d41b239d8b..6203fe611e 100644
--- a/voxygen/src/hud/settings_window/video.rs
+++ b/voxygen/src/hud/settings_window/video.rs
@@ -82,6 +82,9 @@ widget_ids! {
         refresh_rate,
         refresh_rate_label,
         //
+        gpu_profiler_button,
+        gpu_profiler_label,
+        //
         particles_button,
         particles_label,
         lossy_terrain_compression_button,
@@ -902,11 +905,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);
 
diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs
index ae15b7a07d..b87482e6b1 100644
--- a/voxygen/src/lib.rs
+++ b/voxygen/src/lib.rs
@@ -8,7 +8,8 @@
     const_generics,
     drain_filter,
     once_cell,
-    trait_alias
+    trait_alias,
+    or_patterns
 )]
 #![recursion_limit = "2048"]
 
diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs
index 5e2b0a9e13..19cb78d36d 100644
--- a/voxygen/src/render/mod.rs
+++ b/voxygen/src/render/mod.rs
@@ -8,8 +8,8 @@ pub mod mesh;
 pub mod model;
 pub mod pipelines;
 pub mod renderer;
+mod scope;
 pub mod texture;
-mod time;
 
 // Reexports
 pub use self::{
@@ -271,4 +271,5 @@ pub struct RenderMode {
     pub shadow: ShadowMode,
     pub upscale_mode: UpscaleMode,
     pub present_mode: PresentMode,
+    pub profiler_enabled: bool,
 }
diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs
index dc592265b1..3530dabbcf 100644
--- a/voxygen/src/render/renderer.rs
+++ b/voxygen/src/render/renderer.rs
@@ -1,6 +1,13 @@
 mod binding;
 pub(super) mod drawer;
-mod spans;
+// Consts and bind groups for post-process and clouds
+mod locals;
+mod shaders;
+mod shadow_map;
+
+use locals::Locals;
+use shaders::Shaders;
+use shadow_map::{ShadowMap, ShadowMapRenderer};
 
 use super::{
     consts::Consts,
@@ -18,141 +25,15 @@ use super::{
 use common::assets::{self, AssetExt, AssetHandle};
 use common_base::span;
 use core::convert::TryFrom;
-use hashbrown::HashMap;
 use tracing::{error, info, warn};
 use vek::*;
 
+// 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).
 // TODO: revert to u16
 pub type ColLightInfo = (Vec<[u8; 4]>, Vec2<u32>);
 
-/// Load from a GLSL file.
-pub struct Glsl(String);
-
-impl From<String> for Glsl {
-    fn from(s: String) -> Glsl { Glsl(s) }
-}
-
-impl assets::Asset for Glsl {
-    type Loader = assets::LoadFrom<String, assets::StringLoader>;
-
-    const EXTENSION: &'static str = "glsl";
-}
-
-struct Shaders {
-    shaders: HashMap<String, AssetHandle<Glsl>>,
-}
-
-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<S: assets::source::Source>(
-        _: &assets::AssetCache<S>,
-        _: &str,
-    ) -> Result<Shaders, assets::Error> {
-        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",
-            "light-shadows-directed-frag",
-            "point-light-shadows-vert",
-            "skybox-vert",
-            "skybox-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",
-            "player-shadow-frag",
-            "light-shadows-geom",
-            "light-shadows-frag",
-        ];
-
-        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::<Result<HashMap<_, _>, assets::Error>>()?;
-
-        Ok(Self { shaders })
-    }
-}
-
-impl Shaders {
-    fn get(&self, shader: &str) -> Option<impl std::ops::Deref<Target = Glsl>> {
-        self.shaders.get(shader).map(|a| a.read())
-    }
-}
-
-/// A type that holds shadow map data.  Since shadow mapping may not be
-/// supported on all platforms, we try to keep it separate.
-struct ShadowMapRenderer {
-    // directed_encoder: gfx::Encoder<gfx_backend::Resources, gfx_backend::CommandBuffer>,
-    // point_encoder: gfx::Encoder<gfx_backend::Resources, gfx_backend::CommandBuffer>,
-    directed_depth: Texture,
-
-    point_depth: Texture,
-
-    point_pipeline: shadow::PointShadowPipeline,
-    terrain_directed_pipeline: shadow::ShadowPipeline,
-    figure_directed_pipeline: shadow::ShadowFigurePipeline,
-    layout: shadow::ShadowLayout,
-}
-
-enum ShadowMap {
-    Enabled(ShadowMapRenderer),
-    Disabled {
-        dummy_point: Texture, // Cube texture
-        dummy_directed: Texture,
-    },
-}
-
-impl ShadowMap {
-    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),
-        }
-    }
-
-    fn is_enabled(&self) -> bool { matches!(self, Self::Enabled(_)) }
-}
-
 /// A type that stores all the layouts associated with this renderer.
 struct Layouts {
     global: GlobalsLayouts,
@@ -167,72 +48,37 @@ struct Layouts {
     ui: ui::UiLayout,
 }
 
-struct Locals {
-    clouds: Consts<clouds::Locals>,
-    clouds_bind: clouds::BindGroup,
-
-    postprocess: Consts<postprocess::Locals>,
-    postprocess_bind: postprocess::BindGroup,
+/// A type that stores all the pipelines associated with this renderer.
+struct Pipelines {
+    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,
+    ui: ui::UiPipeline,
 }
 
-impl Locals {
-    fn new(
-        device: &wgpu::Device,
-        layouts: &Layouts,
-        clouds_locals: Consts<clouds::Locals>,
-        postprocess_locals: Consts<postprocess::Locals>,
-        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);
+/// Render target views
+struct Views {
+    // NOTE: unused for now
+    win_depth: wgpu::TextureView,
 
-        Self {
-            clouds: clouds_locals,
-            clouds_bind,
-            postprocess: postprocess_locals,
-            postprocess_bind,
-        }
-    }
+    tgt_color: wgpu::TextureView,
+    tgt_depth: wgpu::TextureView,
+    // TODO: rename
+    tgt_color_pp: wgpu::TextureView,
+}
 
-    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);
-    }
+/// Shadow rendering textures, layouts, pipelines, and bind groups
+struct Shadow {
+    map: ShadowMap,
+    bind: ShadowTexturesBindGroup,
 }
 
 /// A type that encapsulates rendering state. `Renderer` is central to Voxygen's
@@ -242,51 +88,28 @@ impl Locals {
 pub struct Renderer {
     device: wgpu::Device,
     queue: wgpu::Queue,
+    surface: wgpu::Surface,
     swap_chain: wgpu::SwapChain,
     sc_desc: wgpu::SwapChainDescriptor,
-    surface: wgpu::Surface,
-
-    win_depth_view: wgpu::TextureView,
-
-    tgt_color_view: wgpu::TextureView,
-    tgt_depth_view: wgpu::TextureView,
-    // TODO: rename
-    tgt_color_pp_view: wgpu::TextureView,
 
     sampler: wgpu::Sampler,
     depth_sampler: wgpu::Sampler,
 
-    shadow_map: ShadowMap,
-    shadow_bind: ShadowTexturesBindGroup,
-
     layouts: Layouts,
-
-    figure_pipeline: figure::FigurePipeline,
-    fluid_pipeline: fluid::FluidPipeline,
-    lod_terrain_pipeline: lod_terrain::LodTerrainPipeline,
-    particle_pipeline: particle::ParticlePipeline,
-    clouds_pipeline: clouds::CloudsPipeline,
-    postprocess_pipeline: postprocess::PostProcessPipeline,
-    // Consider reenabling at some time
-    // player_shadow_pipeline: figure::FigurePipeline,
-    skybox_pipeline: skybox::SkyboxPipeline,
-    sprite_pipeline: sprite::SpritePipeline,
-    terrain_pipeline: terrain::TerrainPipeline,
-    ui_pipeline: ui::UiPipeline,
-
-    shaders: AssetHandle<Shaders>,
-
+    pipelines: Pipelines,
+    shadow: Shadow,
     // 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,
 
-    mode: RenderMode,
+    shaders: AssetHandle<Shaders>,
 
+    mode: RenderMode,
     resolution: Vec2<u32>,
 
-    tracer: super::time::GpuTracer<spans::Id>,
+    profiler: wgpu_profiler::GpuProfiler,
 }
 
 impl Renderer {
@@ -334,7 +157,10 @@ impl Renderer {
                 features: wgpu::Features::DEPTH_CLAMPING
                     | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER
                     | wgpu::Features::PUSH_CONSTANTS
-                    | super::time::required_features(),
+                    // TODO: make optional based on enabling profiling
+                    // NOTE: requires recreating the device/queue is this setting changes
+                    // alternatively it could be a compile time feature toggle
+                    | super::scope::required_features(),
                 limits,
             },
             None,
@@ -399,16 +225,7 @@ impl Renderer {
         };
 
         let (
-            skybox_pipeline,
-            figure_pipeline,
-            terrain_pipeline,
-            fluid_pipeline,
-            sprite_pipeline,
-            particle_pipeline,
-            ui_pipeline,
-            lod_terrain_pipeline,
-            clouds_pipeline,
-            postprocess_pipeline,
+            pipelines,
             //player_shadow_pipeline,
             point_shadow_pipeline,
             terrain_directed_shadow_pipeline,
@@ -422,8 +239,7 @@ impl Renderer {
             shadow_views.is_some(),
         )?;
 
-        let (tgt_color_view, tgt_depth_view, tgt_color_pp_view, win_depth_view) =
-            Self::create_rt_views(&device, (dims.width, dims.height), &mode)?;
+        let views = Self::create_rt_views(&device, (dims.width, dims.height), &mode)?;
 
         let shadow_map = if let (
             Some(point_pipeline),
@@ -467,6 +283,11 @@ impl Renderer {
                 .bind_shadow_textures(&device, point, directed)
         };
 
+        let shadow = Shadow {
+            map: shadow_map,
+            bind: shadow_bind,
+        };
+
         let create_sampler = |filter| {
             device.create_sampler(&wgpu::SamplerDescriptor {
                 label: None,
@@ -502,78 +323,52 @@ impl Renderer {
             &layouts,
             clouds_locals,
             postprocess_locals,
-            &tgt_color_view,
-            &tgt_depth_view,
-            &tgt_color_pp_view,
+            &views.tgt_color,
+            &views.tgt_depth,
+            &views.tgt_color_pp,
             &sampler,
             &depth_sampler,
         );
 
-        let tracer =
-            super::time::GpuTracer::new(&device, &queue, "voxygen_gpu_chrome_trace.json").unwrap();
+        let mut profiler = wgpu_profiler::GpuProfiler::new(4, queue.get_timestamp_period());
+        profiler.enable_timer = mode.profiler_enabled;
+        profiler.enable_debug_marker = mode.profiler_enabled;
 
         Ok(Self {
             device,
             queue,
+            surface,
             swap_chain,
             sc_desc,
-            surface,
 
-            win_depth_view,
-
-            tgt_color_view,
-            tgt_depth_view,
-            tgt_color_pp_view,
+            layouts,
+            pipelines,
+            shadow,
+            locals,
+            views,
 
             sampler,
             depth_sampler,
-
-            shadow_map,
-            shadow_bind,
-
-            layouts,
-
-            skybox_pipeline,
-            figure_pipeline,
-            terrain_pipeline,
-            fluid_pipeline,
-            sprite_pipeline,
-            particle_pipeline,
-            ui_pipeline,
-            lod_terrain_pipeline,
-            clouds_pipeline,
-            postprocess_pipeline,
-            shaders,
-            //player_shadow_pipeline,
-            locals,
-
             noise_tex,
 
-            mode,
+            shaders,
 
+            mode,
             resolution: Vec2::new(dims.width, dims.height),
 
-            tracer,
+            profiler,
         })
     }
 
-    /// Get references to the internal render target views that get rendered to
-    /// before post-processing.
-    #[allow(dead_code)]
-    pub fn tgt_views(&self) -> (&wgpu::TextureView, &wgpu::TextureView) {
-        (&self.tgt_color_view, &self.tgt_depth_view)
-    }
-
-    /// Get references to the internal render target views that get displayed
-    /// directly by the window.
-    #[allow(dead_code)]
-    pub fn win_views(&self) -> &wgpu::TextureView { &self.win_depth_view }
-
     /// Change the render mode.
     pub fn set_render_mode(&mut self, mode: RenderMode) -> Result<(), RenderError> {
         self.mode = mode;
         self.sc_desc.present_mode = self.mode.present_mode.into();
 
+        // Enable/disable profiler
+        self.profiler.enable_timer = self.mode.profiler_enabled;
+        self.profiler.enable_debug_marker = self.mode.profiler_enabled;
+
         // Recreate render target
         self.on_resize(self.resolution)?;
 
@@ -597,31 +392,26 @@ impl Renderer {
             self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
 
             // Resize other render targets
-            let (tgt_color_view, tgt_depth_view, tgt_color_pp_view, win_depth_view) =
-                Self::create_rt_views(&mut self.device, (dims.x, dims.y), &self.mode)?;
-            self.win_depth_view = win_depth_view;
-            self.tgt_color_view = tgt_color_view;
-            self.tgt_depth_view = tgt_depth_view;
-            self.tgt_color_pp_view = tgt_color_pp_view;
+            self.views = Self::create_rt_views(&mut self.device, (dims.x, dims.y), &self.mode)?;
             // Rebind views to clouds/postprocess bind groups
             self.locals.rebind(
                 &self.device,
                 &self.layouts,
-                &self.tgt_color_view,
-                &self.tgt_depth_view,
-                &self.tgt_color_pp_view,
+                &self.views.tgt_color,
+                &self.views.tgt_depth,
+                &self.views.tgt_color_pp,
                 &self.sampler,
                 &self.depth_sampler,
             );
 
             if let (ShadowMap::Enabled(shadow_map), ShadowMode::Map(mode)) =
-                (&mut self.shadow_map, self.mode.shadow)
+                (&mut self.shadow.map, self.mode.shadow)
             {
                 match Self::create_shadow_views(&mut self.device, (dims.x, dims.y), &mode) {
                     Ok((point_depth, directed_depth)) => {
                         shadow_map.point_depth = point_depth;
                         shadow_map.directed_depth = directed_depth;
-                        self.shadow_bind = self.layouts.global.bind_shadow_textures(
+                        self.shadow.bind = self.layouts.global.bind_shadow_textures(
                             &self.device,
                             &shadow_map.point_depth,
                             &shadow_map.directed_depth,
@@ -637,19 +427,12 @@ impl Renderer {
         Ok(())
     }
 
+    /// Create render target views
     fn create_rt_views(
         device: &wgpu::Device,
         size: (u32, u32),
         mode: &RenderMode,
-    ) -> Result<
-        (
-            wgpu::TextureView,
-            wgpu::TextureView,
-            wgpu::TextureView,
-            wgpu::TextureView,
-        ),
-        RenderError,
-    > {
+    ) -> Result<Views, RenderError> {
         let upscaled = Vec2::<u32>::from(size)
             .map(|e| (e as f32 * mode.upscale_mode.factor) as u32)
             .into_tuple();
@@ -743,12 +526,12 @@ impl Renderer {
             array_layer_count: None,
         });
 
-        Ok((
-            tgt_color_view,
-            tgt_depth_view,
-            tgt_color_pp_view,
-            win_depth_view,
-        ))
+        Ok(Views {
+            tgt_color: tgt_color_view,
+            tgt_depth: tgt_depth_view,
+            tgt_color_pp: tgt_color_pp_view,
+            win_depth: win_depth_view,
+        })
     }
 
     fn create_dummy_shadow_tex(device: &wgpu::Device, queue: &wgpu::Queue) -> (Texture, Texture) {
@@ -959,7 +742,7 @@ impl Renderer {
 
     /// Get the resolution of the shadow render target.
     pub fn get_shadow_resolution(&self) -> (Vec2<u32>, Vec2<u32>) {
-        if let ShadowMap::Enabled(shadow_map) = &self.shadow_map {
+        if let ShadowMap::Enabled(shadow_map) = &self.shadow.map {
             (
                 shadow_map.point_depth.get_dimensions().xy(),
                 shadow_map.directed_depth.get_dimensions().xy(),
@@ -993,7 +776,6 @@ 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() {
         todo!()
         // unsafe {
@@ -1072,34 +854,16 @@ impl Renderer {
             &self.shaders.read(),
             &self.mode,
             &self.sc_desc,
-            self.shadow_map.is_enabled(),
+            self.shadow.map.is_enabled(),
         ) {
             Ok((
-                skybox_pipeline,
-                figure_pipeline,
-                terrain_pipeline,
-                fluid_pipeline,
-                sprite_pipeline,
-                particle_pipeline,
-                ui_pipeline,
-                lod_terrain_pipeline,
-                clouds_pipeline,
-                postprocess_pipeline,
+                pipelines,
                 //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.pipelines = pipelines;
                 //self.player_shadow_pipeline = player_shadow_pipeline;
                 if let (
                     Some(point_pipeline),
@@ -1110,7 +874,7 @@ impl Renderer {
                     point_shadow_pipeline,
                     terrain_directed_shadow_pipeline,
                     figure_directed_shadow_pipeline,
-                    &mut self.shadow_map,
+                    &mut self.shadow.map,
                 ) {
                     shadow_map.point_pipeline = point_pipeline;
                     shadow_map.terrain_directed_pipeline = terrain_directed_pipeline;
@@ -1275,9 +1039,34 @@ impl Renderer {
 
     /// 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<image::DynamicImage, RenderError> {
-        todo!()
+    //pub fn create_screenshot(&mut self) -> Result<image::DynamicImage,
+    // RenderError> {
+    pub fn create_screenshot(&mut self) {
+        // TODO: check if enabled
+        // TODO: save alongside a screenshot
+        // Take profiler snapshot
+        let profiling_data = if let Some(data) = self.profiler.process_finished_frame() {
+            data
+        } else {
+            error!("Failed to retrieve profiling data");
+            return;
+        };
+
+        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)
+        );
+
+        wgpu_profiler::chrometrace::write_chrometrace(
+            std::path::Path::new(&file_name),
+            &profiling_data,
+        );
+
+        println!("{}", file_name);
+        //todo!()
         // let (width, height) = self.get_resolution().into_tuple();
 
         // let download_buf = self
@@ -2042,7 +1831,6 @@ impl Renderer {
 }
 
 /// Creates all the pipelines used to render.
-#[allow(clippy::type_complexity)] // TODO: Pending review in #587
 fn create_pipelines(
     device: &wgpu::Device,
     layouts: &Layouts,
@@ -2052,16 +1840,7 @@ fn create_pipelines(
     has_shadow_views: bool,
 ) -> Result<
     (
-        skybox::SkyboxPipeline,
-        figure::FigurePipeline,
-        terrain::TerrainPipeline,
-        fluid::FluidPipeline,
-        sprite::SpritePipeline,
-        particle::ParticlePipeline,
-        ui::UiPipeline,
-        lod_terrain::LodTerrainPipeline,
-        clouds::CloudsPipeline,
-        postprocess::PostProcessPipeline,
+        Pipelines,
         //figure::FigurePipeline,
         Option<shadow::PointShadowPipeline>,
         Option<shadow::ShadowPipeline>,
@@ -2351,16 +2130,18 @@ fn create_pipelines(
     );
 
     Ok((
-        skybox_pipeline,
-        figure_pipeline,
-        terrain_pipeline,
-        fluid_pipeline,
-        sprite_pipeline,
-        particle_pipeline,
-        ui_pipeline,
-        lod_terrain_pipeline,
-        clouds_pipeline,
-        postprocess_pipeline,
+        Pipelines {
+            skybox: skybox_pipeline,
+            figure: figure_pipeline,
+            terrain: terrain_pipeline,
+            fluid: fluid_pipeline,
+            sprite: sprite_pipeline,
+            particle: particle_pipeline,
+            ui: ui_pipeline,
+            lod_terrain: lod_terrain_pipeline,
+            clouds: clouds_pipeline,
+            postprocess: postprocess_pipeline,
+        },
         // player_shadow_pipeline,
         Some(point_shadow_pipeline),
         Some(terrain_directed_shadow_pipeline),
diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs
index 0a5308c5fb..1e82c3a0c5 100644
--- a/voxygen/src/render/renderer/drawer.rs
+++ b/voxygen/src/render/renderer/drawer.rs
@@ -8,69 +8,89 @@ use super::{
             clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite,
             terrain, ui, ColLights, GlobalsBindGroup, Light, Shadow,
         },
+        scope::{ManualOwningScope, OwningScope, Scope},
     },
-    spans::{self, OwningSpan, Span},
     Renderer, ShadowMap, ShadowMapRenderer,
 };
 use core::{num::NonZeroU32, ops::Range};
 use std::sync::Arc;
 use vek::Aabr;
 
+// Borrow the fields we need from the renderer so that the GpuProfiler can be
+// dijointly borrowed mutably
+struct RendererBorrow<'frame> {
+    queue: &'frame wgpu::Queue,
+    device: &'frame wgpu::Device,
+    shadow: &'frame super::Shadow,
+    pipelines: &'frame super::Pipelines,
+    locals: &'frame super::locals::Locals,
+    views: &'frame super::Views,
+    mode: &'frame super::super::RenderMode,
+}
+
 pub struct Drawer<'frame> {
-    encoder: Option<wgpu::CommandEncoder>,
-    pub renderer: &'frame mut Renderer,
-    tex: wgpu::SwapChainTexture,
+    encoder: Option<ManualOwningScope<'frame, wgpu::CommandEncoder>>,
+    borrow: RendererBorrow<'frame>,
+    swap_tex: wgpu::SwapChainTexture,
     globals: &'frame GlobalsBindGroup,
 }
 
 impl<'frame> Drawer<'frame> {
     pub fn new(
-        mut encoder: wgpu::CommandEncoder,
+        encoder: wgpu::CommandEncoder,
         renderer: &'frame mut Renderer,
-        tex: wgpu::SwapChainTexture,
+        swap_tex: wgpu::SwapChainTexture,
         globals: &'frame GlobalsBindGroup,
     ) -> Self {
-        renderer.tracer.start_span(&mut encoder, &spans::Id::Frame);
+        let borrow = RendererBorrow {
+            queue: &renderer.queue,
+            device: &renderer.device,
+            shadow: &renderer.shadow,
+            pipelines: &renderer.pipelines,
+            locals: &renderer.locals,
+            views: &renderer.views,
+            mode: &renderer.mode,
+        };
+
+        let mut encoder =
+            ManualOwningScope::start(&mut renderer.profiler, encoder, borrow.device, "frame");
 
         Self {
             encoder: Some(encoder),
-            renderer,
-            tex,
+            borrow,
+            swap_tex,
             globals,
         }
     }
 
-    pub fn shadow_pass(&mut self) -> Option<ShadowPassDrawer> {
-        if let ShadowMap::Enabled(ref shadow_renderer) = self.renderer.shadow_map {
-            let mut render_pass =
-                self.encoder
-                    .as_mut()
-                    .unwrap()
-                    .begin_render_pass(&wgpu::RenderPassDescriptor {
-                        label: Some("shadow pass"),
-                        color_attachments: &[],
-                        depth_stencil_attachment: Some(
-                            wgpu::RenderPassDepthStencilAttachmentDescriptor {
-                                attachment: &shadow_renderer.directed_depth.view,
-                                depth_ops: Some(wgpu::Operations {
-                                    load: wgpu::LoadOp::Clear(1.0),
-                                    store: true,
-                                }),
-                                stencil_ops: None,
-                            },
-                        ),
-                    });
+    /// Get the render mode.
+    pub fn render_mode(&self) -> &super::super::RenderMode { self.borrow.mode }
+
+    pub fn shadow_pass(&mut self) -> Option<ShadowPassDrawer> {
+        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(device, "shadow_pass", &wgpu::RenderPassDescriptor {
+                    label: Some("shadow pass"),
+                    color_attachments: &[],
+                    depth_stencil_attachment: Some(
+                        wgpu::RenderPassDepthStencilAttachmentDescriptor {
+                            attachment: &shadow_renderer.directed_depth.view,
+                            depth_ops: Some(wgpu::Operations {
+                                load: wgpu::LoadOp::Clear(1.0),
+                                store: true,
+                            }),
+                            stencil_ops: None,
+                        },
+                    ),
+                });
 
-            let mut render_pass = OwningSpan::start(
-                &self.renderer.tracer,
-                render_pass,
-                spans::Id::DirectedShadows,
-            );
             render_pass.set_bind_group(0, &self.globals.bind_group, &[]);
 
             Some(ShadowPassDrawer {
                 render_pass,
-                renderer: &self.renderer,
+                borrow: &self.borrow,
                 shadow_renderer,
             })
         } else {
@@ -79,101 +99,86 @@ impl<'frame> Drawer<'frame> {
     }
 
     pub fn first_pass(&mut self) -> FirstPassDrawer {
-        let render_pass =
-            self.encoder
-                .as_mut()
-                .unwrap()
-                .begin_render_pass(&wgpu::RenderPassDescriptor {
-                    label: Some("first pass"),
-                    color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
-                        attachment: &self.renderer.tgt_color_view,
-                        resolve_target: None,
-                        ops: wgpu::Operations {
-                            load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
-                            store: true,
-                        },
-                    }],
-                    depth_stencil_attachment: Some(
-                        wgpu::RenderPassDepthStencilAttachmentDescriptor {
-                            attachment: &self.renderer.tgt_depth_view,
-                            depth_ops: Some(wgpu::Operations {
-                                load: wgpu::LoadOp::Clear(0.0),
-                                store: true,
-                            }),
-                            stencil_ops: None,
-                        },
-                    ),
-                });
-
+        let encoder = self.encoder.as_mut().unwrap();
+        let device = self.borrow.device;
         let mut render_pass =
-            OwningSpan::start(&self.renderer.tracer, render_pass, spans::Id::PassOne);
+            encoder.scoped_render_pass(device, "first_pass", &wgpu::RenderPassDescriptor {
+                label: Some("first pass"),
+                color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
+                    attachment: &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::RenderPassDepthStencilAttachmentDescriptor {
+                    attachment: &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, &self.renderer.shadow_bind.bind_group, &[]);
+        render_pass.set_bind_group(1, &self.borrow.shadow.bind.bind_group, &[]);
 
         FirstPassDrawer {
             render_pass,
-            renderer: &self.renderer,
-            figures_called: false,
+            borrow: &self.borrow,
         }
     }
 
     pub fn second_pass(&mut self) -> SecondPassDrawer {
-        let render_pass =
-            self.encoder
-                .as_mut()
-                .unwrap()
-                .begin_render_pass(&wgpu::RenderPassDescriptor {
-                    label: Some("second pass (clouds)"),
-                    color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
-                        attachment: &self.renderer.tgt_color_pp_view,
-                        resolve_target: None,
-                        ops: wgpu::Operations {
-                            load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
-                            store: true,
-                        },
-                    }],
-                    depth_stencil_attachment: None,
-                });
-
+        let encoder = self.encoder.as_mut().unwrap();
+        let device = self.borrow.device;
         let mut render_pass =
-            OwningSpan::start(&self.renderer.tracer, render_pass, spans::Id::PassTwo);
+            encoder.scoped_render_pass(device, "second_pass", &wgpu::RenderPassDescriptor {
+                label: Some("second pass (clouds)"),
+                color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
+                    attachment: &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, &[]);
-        render_pass.set_bind_group(1, &self.renderer.shadow_bind.bind_group, &[]);
+        render_pass.set_bind_group(1, &self.borrow.shadow.bind.bind_group, &[]);
 
         SecondPassDrawer {
             render_pass,
-            renderer: &self.renderer,
+            borrow: &self.borrow,
         }
     }
 
     pub fn third_pass(&mut self) -> ThirdPassDrawer {
-        let render_pass =
-            self.encoder
-                .as_mut()
-                .unwrap()
-                .begin_render_pass(&wgpu::RenderPassDescriptor {
-                    label: Some("third pass (postprocess + ui)"),
-                    color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
-                        attachment: &self.tex.view,
-                        resolve_target: None,
-                        ops: wgpu::Operations {
-                            load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
-                            store: true,
-                        },
-                    }],
-                    depth_stencil_attachment: None,
-                });
-
+        let encoder = self.encoder.as_mut().unwrap();
+        let device = self.borrow.device;
         let mut render_pass =
-            OwningSpan::start(&self.renderer.tracer, render_pass, spans::Id::PassThree);
+            encoder.scoped_render_pass(device, "third_pass", &wgpu::RenderPassDescriptor {
+                label: Some("third pass (postprocess + ui)"),
+                color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
+                    attachment: &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_bind_group(0, &self.globals.bind_group, &[]);
 
         ThirdPassDrawer {
             render_pass,
-            renderer: &self.renderer,
+            borrow: &self.borrow,
         }
     }
 
@@ -183,10 +188,13 @@ impl<'frame> Drawer<'frame> {
         chunks: impl Clone
         + Iterator<Item = (&'data Model<terrain::Vertex>, &'data terrain::BoundLocals)>,
     ) {
-        if let ShadowMap::Enabled(ref shadow_renderer) = self.renderer.shadow_map {
-            self.renderer
-                .tracer
-                .start_span(self.encoder.as_mut().unwrap(), &spans::Id::PointShadows);
+        if let ShadowMap::Enabled(ref shadow_renderer) = self.borrow.shadow.map {
+            let device = self.borrow.device;
+            let mut encoder = self
+                .encoder
+                .as_mut()
+                .unwrap()
+                .scope(device, "point shadows");
             const STRIDE: usize = std::mem::size_of::<shadow::PointLightMatrix>();
             let data = bytemuck::cast_slice(matrices);
 
@@ -209,23 +217,20 @@ impl<'frame> Drawer<'frame> {
 
                 let label = format!("point shadow face-{} pass", face);
                 let mut render_pass =
-                    self.encoder
-                        .as_mut()
-                        .unwrap()
-                        .begin_render_pass(&wgpu::RenderPassDescriptor {
-                            label: Some(&label),
-                            color_attachments: &[],
-                            depth_stencil_attachment: Some(
-                                wgpu::RenderPassDepthStencilAttachmentDescriptor {
-                                    attachment: &view,
-                                    depth_ops: Some(wgpu::Operations {
-                                        load: wgpu::LoadOp::Clear(1.0),
-                                        store: true,
-                                    }),
-                                    stencil_ops: None,
-                                },
-                            ),
-                        });
+                    encoder.scoped_render_pass(device, &label, &wgpu::RenderPassDescriptor {
+                        label: Some(&label),
+                        color_attachments: &[],
+                        depth_stencil_attachment: Some(
+                            wgpu::RenderPassDepthStencilAttachmentDescriptor {
+                                attachment: &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);
                 render_pass.set_bind_group(0, &self.globals.bind_group, &[]);
@@ -244,9 +249,6 @@ impl<'frame> Drawer<'frame> {
                     });
                 });
             }
-            self.renderer
-                .tracer
-                .end_span(self.encoder.as_mut().unwrap(), &spans::Id::PointShadows);
         }
     }
 
@@ -258,11 +260,13 @@ impl<'frame> Drawer<'frame> {
     /// requires an array of matrices that could be a pain to construct
     /// simply for clearing
     pub fn clear_shadows(&mut self) {
-        if let ShadowMap::Enabled(ref shadow_renderer) = self.renderer.shadow_map {
-            self.encoder
-                .as_mut()
-                .unwrap()
-                .begin_render_pass(&wgpu::RenderPassDescriptor {
+        if let ShadowMap::Enabled(ref shadow_renderer) = self.borrow.shadow.map {
+            let device = self.borrow.device;
+            let encoder = self.encoder.as_mut().unwrap();
+            encoder.scoped_render_pass(
+                device,
+                "clear_directed_shadow",
+                &wgpu::RenderPassDescriptor {
                     label: Some("clear directed shadow pass"),
                     color_attachments: &[],
                     depth_stencil_attachment: Some(
@@ -275,7 +279,8 @@ impl<'frame> Drawer<'frame> {
                             stencil_ops: None,
                         },
                     ),
-                });
+                },
+            );
 
             for face in 0..6 {
                 // TODO: view creation cost?
@@ -295,23 +300,20 @@ impl<'frame> Drawer<'frame> {
                         });
 
                 let label = format!("clear point shadow face-{} pass", face);
-                self.encoder
-                    .as_mut()
-                    .unwrap()
-                    .begin_render_pass(&wgpu::RenderPassDescriptor {
-                        label: Some(&label),
-                        color_attachments: &[],
-                        depth_stencil_attachment: Some(
-                            wgpu::RenderPassDepthStencilAttachmentDescriptor {
-                                attachment: &view,
-                                depth_ops: Some(wgpu::Operations {
-                                    load: wgpu::LoadOp::Clear(1.0),
-                                    store: true,
-                                }),
-                                stencil_ops: None,
-                            },
-                        ),
-                    });
+                encoder.scoped_render_pass(device, &label, &wgpu::RenderPassDescriptor {
+                    label: Some(&label),
+                    color_attachments: &[],
+                    depth_stencil_attachment: Some(
+                        wgpu::RenderPassDepthStencilAttachmentDescriptor {
+                            attachment: &view,
+                            depth_ops: Some(wgpu::Operations {
+                                load: wgpu::LoadOp::Clear(1.0),
+                                store: true,
+                            }),
+                            stencil_ops: None,
+                        },
+                    ),
+                });
             }
         }
     }
@@ -321,47 +323,38 @@ impl<'frame> Drop for Drawer<'frame> {
     fn drop(&mut self) {
         // TODO: submitting things to the queue can let the gpu start on them sooner
         // maybe we should submit each render pass to the queue as they are produced?
-        self.renderer
-            .tracer
-            .end_span(self.encoder.as_mut().unwrap(), &spans::Id::Frame);
-        self.renderer
-            .tracer
-            .resolve_timestamps(self.encoder.as_mut().unwrap());
-        self.renderer
-            .queue
-            .submit(std::iter::once(self.encoder.take().unwrap().finish()));
-        // NOTE: this introduces blocking on GPU work
-        self.renderer
-            .tracer
-            .record_timestamps(&self.renderer.device)
+        let (mut encoder, profiler) = self.encoder.take().unwrap().end_scope();
+        profiler.resolve_queries(&mut encoder);
+        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: OwningSpan<'pass, wgpu::RenderPass<'pass>>,
-    pub renderer: &'pass Renderer,
+    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 = Span::start(
-            &self.renderer.tracer,
-            &mut *self.render_pass,
-            spans::Id::DirectedFigureShadows,
-        );
+        let mut render_pass = self
+            .render_pass
+            .scope(self.borrow.device, "direcred_figure_shadows");
+
         render_pass.set_pipeline(&self.shadow_renderer.figure_directed_pipeline.pipeline);
 
         FigureShadowDrawer { render_pass }
     }
 
     pub fn draw_terrain_shadows(&mut self) -> TerrainShadowDrawer<'_, 'pass> {
-        let mut render_pass = Span::start(
-            &self.renderer.tracer,
-            &mut *self.render_pass,
-            spans::Id::DirectedTerrainShadows,
-        );
+        let mut render_pass = self
+            .render_pass
+            .scope(self.borrow.device, "direcred_terrain_shadows");
+
         render_pass.set_pipeline(&self.shadow_renderer.terrain_directed_pipeline.pipeline);
 
         TerrainShadowDrawer { render_pass }
@@ -369,7 +362,7 @@ impl<'pass> ShadowPassDrawer<'pass> {
 }
 
 pub struct FigureShadowDrawer<'pass_ref, 'pass: 'pass_ref> {
-    render_pass: Span<'pass_ref, wgpu::RenderPass<'pass>>,
+    render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
 }
 
 impl<'pass_ref, 'pass: 'pass_ref> FigureShadowDrawer<'pass_ref, 'pass> {
@@ -385,7 +378,7 @@ impl<'pass_ref, 'pass: 'pass_ref> FigureShadowDrawer<'pass_ref, 'pass> {
 }
 
 pub struct TerrainShadowDrawer<'pass_ref, 'pass: 'pass_ref> {
-    render_pass: Span<'pass_ref, wgpu::RenderPass<'pass>>,
+    render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
 }
 
 impl<'pass_ref, 'pass: 'pass_ref> TerrainShadowDrawer<'pass_ref, 'pass> {
@@ -402,73 +395,50 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainShadowDrawer<'pass_ref, 'pass> {
 
 // First pass
 pub struct FirstPassDrawer<'pass> {
-    pub(super) render_pass: OwningSpan<'pass, wgpu::RenderPass<'pass>>,
-    pub renderer: &'pass Renderer,
-    // TODO: hack
-    figures_called: bool,
+    pub(super) render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>,
+    borrow: &'pass RendererBorrow<'pass>,
 }
 
 impl<'pass> FirstPassDrawer<'pass> {
     pub fn draw_skybox<'data: 'pass>(&mut self, model: &'data Model<skybox::Vertex>) {
-        let mut render_pass = Span::start(
-            &self.renderer.tracer,
-            &mut *self.render_pass,
-            spans::Id::Skybox,
-        );
-        render_pass.set_pipeline(&self.renderer.skybox_pipeline.pipeline);
+        let mut render_pass = self.render_pass.scope(self.borrow.device, "skybox");
+
+        render_pass.set_pipeline(&self.borrow.pipelines.skybox.pipeline);
         render_pass.set_vertex_buffer(0, model.buf().slice(..));
         render_pass.draw(0..model.len() as u32, 0..1);
     }
 
     pub fn draw_lod_terrain<'data: 'pass>(&mut self, model: &'data Model<lod_terrain::Vertex>) {
-        let mut render_pass = Span::start(
-            &self.renderer.tracer,
-            &mut *self.render_pass,
-            spans::Id::Lod,
-        );
-        render_pass.set_pipeline(&self.renderer.lod_terrain_pipeline.pipeline);
+        let mut render_pass = self.render_pass.scope(self.borrow.device, "lod_terrain");
+
+        render_pass.set_pipeline(&self.borrow.pipelines.lod_terrain.pipeline);
         render_pass.set_vertex_buffer(0, model.buf().slice(..));
         render_pass.draw(0..model.len() as u32, 0..1);
     }
 
     pub fn draw_figures(&mut self) -> FigureDrawer<'_, 'pass> {
-        let mut render_pass = Span::start(
-            &self.renderer.tracer,
-            &mut *self.render_pass,
-            if !self.figures_called {
-                spans::Id::Figures1
-            } else {
-                spans::Id::Figures2
-            },
-        );
-        self.figures_called = true;
-        render_pass.set_pipeline(&self.renderer.figure_pipeline.pipeline);
+        let mut render_pass = self.render_pass.scope(self.borrow.device, "figures");
+
+        render_pass.set_pipeline(&self.borrow.pipelines.figure.pipeline);
 
         FigureDrawer { render_pass }
     }
 
     pub fn draw_terrain<'data: 'pass>(&mut self) -> TerrainDrawer<'_, 'pass> {
-        let mut render_pass = Span::start(
-            &self.renderer.tracer,
-            &mut *self.render_pass,
-            spans::Id::Terrain,
-        );
-        render_pass.set_pipeline(&self.renderer.terrain_pipeline.pipeline);
+        let mut render_pass = self.render_pass.scope(self.borrow.device, "terrain");
+
+        render_pass.set_pipeline(&self.borrow.pipelines.terrain.pipeline);
 
         TerrainDrawer {
             render_pass,
-
             col_lights: None,
         }
     }
 
     pub fn draw_particles(&mut self) -> ParticleDrawer<'_, 'pass> {
-        let mut render_pass = Span::start(
-            &self.renderer.tracer,
-            &mut *self.render_pass,
-            spans::Id::Particles,
-        );
-        render_pass.set_pipeline(&self.renderer.particle_pipeline.pipeline);
+        let mut render_pass = self.render_pass.scope(self.borrow.device, "particles");
+
+        render_pass.set_pipeline(&self.borrow.pipelines.particle.pipeline);
 
         ParticleDrawer { render_pass }
     }
@@ -477,15 +447,10 @@ impl<'pass> FirstPassDrawer<'pass> {
         &mut self,
         col_lights: &'data ColLights<sprite::Locals>,
     ) -> SpriteDrawer<'_, 'pass> {
-        let mut render_pass = Span::start(
-            &self.renderer.tracer,
-            &mut *self.render_pass,
-            spans::Id::Sprites,
-        );
-        self.render_pass
-            .set_pipeline(&self.renderer.sprite_pipeline.pipeline);
-        self.render_pass
-            .set_bind_group(4, &col_lights.bind_group, &[]);
+        let mut render_pass = self.render_pass.scope(self.borrow.device, "sprites");
+
+        render_pass.set_pipeline(&self.borrow.pipelines.sprite.pipeline);
+        render_pass.set_bind_group(4, &col_lights.bind_group, &[]);
 
         SpriteDrawer { render_pass }
     }
@@ -494,12 +459,9 @@ impl<'pass> FirstPassDrawer<'pass> {
         &mut self,
         waves: &'data fluid::BindGroup,
     ) -> FluidDrawer<'_, 'pass> {
-        let mut render_pass = Span::start(
-            &self.renderer.tracer,
-            &mut *self.render_pass,
-            spans::Id::Fluid,
-        );
-        render_pass.set_pipeline(&self.renderer.fluid_pipeline.pipeline);
+        let mut render_pass = self.render_pass.scope(self.borrow.device, "fluid");
+
+        render_pass.set_pipeline(&self.borrow.pipelines.fluid.pipeline);
         render_pass.set_bind_group(2, &waves.bind_group, &[]);
 
         FluidDrawer { render_pass }
@@ -507,7 +469,7 @@ impl<'pass> FirstPassDrawer<'pass> {
 }
 
 pub struct FigureDrawer<'pass_ref, 'pass: 'pass_ref> {
-    render_pass: Span<'pass_ref, wgpu::RenderPass<'pass>>,
+    render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
 }
 
 impl<'pass_ref, 'pass: 'pass_ref> FigureDrawer<'pass_ref, 'pass> {
@@ -527,7 +489,7 @@ impl<'pass_ref, 'pass: 'pass_ref> FigureDrawer<'pass_ref, 'pass> {
 }
 
 pub struct TerrainDrawer<'pass_ref, 'pass: 'pass_ref> {
-    render_pass: Span<'pass_ref, wgpu::RenderPass<'pass>>,
+    render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
     col_lights: Option<&'pass_ref Arc<ColLights<terrain::Locals>>>,
 }
 
@@ -559,7 +521,7 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainDrawer<'pass_ref, 'pass> {
 }
 
 pub struct ParticleDrawer<'pass_ref, 'pass: 'pass_ref> {
-    render_pass: Span<'pass_ref, wgpu::RenderPass<'pass>>,
+    render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
 }
 
 impl<'pass_ref, 'pass: 'pass_ref> ParticleDrawer<'pass_ref, 'pass> {
@@ -580,7 +542,7 @@ impl<'pass_ref, 'pass: 'pass_ref> ParticleDrawer<'pass_ref, 'pass> {
 }
 
 pub struct SpriteDrawer<'pass_ref, 'pass: 'pass_ref> {
-    render_pass: Span<'pass_ref, wgpu::RenderPass<'pass>>,
+    render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
 }
 
 impl<'pass_ref, 'pass: 'pass_ref> SpriteDrawer<'pass_ref, 'pass> {
@@ -617,7 +579,7 @@ impl<'pass_ref, 'pass: 'pass_ref> ChunkSpriteDrawer<'pass_ref, 'pass> {
 }
 
 pub struct FluidDrawer<'pass_ref, 'pass: 'pass_ref> {
-    render_pass: Span<'pass_ref, wgpu::RenderPass<'pass>>,
+    render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
 }
 
 impl<'pass_ref, 'pass: 'pass_ref> FluidDrawer<'pass_ref, 'pass> {
@@ -634,49 +596,44 @@ impl<'pass_ref, 'pass: 'pass_ref> FluidDrawer<'pass_ref, 'pass> {
 
 // Second pass: clouds
 pub struct SecondPassDrawer<'pass> {
-    render_pass: OwningSpan<'pass, wgpu::RenderPass<'pass>>,
-    renderer: &'pass Renderer,
+    render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>,
+    borrow: &'pass RendererBorrow<'pass>,
 }
 
 impl<'pass> SecondPassDrawer<'pass> {
     pub fn draw_clouds(&mut self) {
         self.render_pass
-            .set_pipeline(&self.renderer.clouds_pipeline.pipeline);
+            .set_pipeline(&self.borrow.pipelines.clouds.pipeline);
         self.render_pass
-            .set_bind_group(2, &self.renderer.locals.clouds_bind.bind_group, &[]);
+            .set_bind_group(2, &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: OwningSpan<'pass, wgpu::RenderPass<'pass>>,
-    renderer: &'pass Renderer,
+    render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>,
+    borrow: &'pass RendererBorrow<'pass>,
 }
 
 impl<'pass> ThirdPassDrawer<'pass> {
     pub fn draw_post_process(&mut self) {
-        let mut render_pass = Span::start(
-            &self.renderer.tracer,
-            &mut *self.render_pass,
-            spans::Id::Postprocess,
-        );
-        render_pass.set_pipeline(&self.renderer.postprocess_pipeline.pipeline);
-        render_pass.set_bind_group(1, &self.renderer.locals.postprocess_bind.bind_group, &[]);
+        let mut render_pass = self.render_pass.scope(self.borrow.device, "postprocess");
+        render_pass.set_pipeline(&self.borrow.pipelines.postprocess.pipeline);
+        render_pass.set_bind_group(1, &self.borrow.locals.postprocess_bind.bind_group, &[]);
         render_pass.draw(0..3, 0..1);
     }
 
     pub fn draw_ui(&mut self) -> UiDrawer<'_, 'pass> {
-        let mut render_pass =
-            Span::start(&self.renderer.tracer, &mut *self.render_pass, spans::Id::Ui);
-        render_pass.set_pipeline(&self.renderer.ui_pipeline.pipeline);
+        let mut render_pass = self.render_pass.scope(self.borrow.device, "ui");
+        render_pass.set_pipeline(&self.borrow.pipelines.ui.pipeline);
 
         UiDrawer { render_pass }
     }
 }
 
 pub struct UiDrawer<'pass_ref, 'pass: 'pass_ref> {
-    render_pass: Span<'pass_ref, wgpu::RenderPass<'pass>>,
+    render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
 }
 
 pub struct PreparedUiDrawer<'pass_ref, 'pass: 'pass_ref> {
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<clouds::Locals>,
+    pub clouds_bind: clouds::BindGroup,
+
+    pub postprocess: Consts<postprocess::Locals>,
+    pub postprocess_bind: postprocess::BindGroup,
+}
+
+impl Locals {
+    pub(super) fn new(
+        device: &wgpu::Device,
+        layouts: &Layouts,
+        clouds_locals: Consts<clouds::Locals>,
+        postprocess_locals: Consts<postprocess::Locals>,
+        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/shaders.rs b/voxygen/src/render/renderer/shaders.rs
new file mode 100644
index 0000000000..3373dbac83
--- /dev/null
+++ b/voxygen/src/render/renderer/shaders.rs
@@ -0,0 +1,91 @@
+use common::assets::{self, AssetExt, AssetHandle};
+use hashbrown::HashMap;
+
+/// Load from a GLSL file.
+pub struct Glsl(pub String);
+
+impl From<String> for Glsl {
+    fn from(s: String) -> Glsl { Glsl(s) }
+}
+
+impl assets::Asset for Glsl {
+    type Loader = assets::LoadFrom<String, assets::StringLoader>;
+
+    const EXTENSION: &'static str = "glsl";
+}
+
+pub struct Shaders {
+    shaders: HashMap<String, AssetHandle<Glsl>>,
+}
+
+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<S: assets::source::Source>(
+        _: &assets::AssetCache<S>,
+        _: &str,
+    ) -> Result<Shaders, assets::Error> {
+        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",
+            "light-shadows-directed-frag",
+            "point-light-shadows-vert",
+            "skybox-vert",
+            "skybox-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",
+            "player-shadow-frag",
+            "light-shadows-geom",
+            "light-shadows-frag",
+        ];
+
+        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::<Result<HashMap<_, _>, assets::Error>>()?;
+
+        Ok(Self { shaders })
+    }
+}
+
+impl Shaders {
+    pub fn get(&self, shader: &str) -> Option<impl core::ops::Deref<Target = Glsl>> {
+        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..6ba75961a2
--- /dev/null
+++ b/voxygen/src/render/renderer/shadow_map.rs
@@ -0,0 +1,38 @@
+use super::super::{pipelines::shadow, texture::Texture};
+
+/// 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<gfx_backend::Resources, gfx_backend::CommandBuffer>,
+    // point_encoder: gfx::Encoder<gfx_backend::Resources, gfx_backend::CommandBuffer>,
+    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 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/scope.rs b/voxygen/src/render/scope.rs
new file mode 100644
index 0000000000..67abe277ec
--- /dev/null
+++ b/voxygen/src/render/scope.rs
@@ -0,0 +1,165 @@
+use wgpu_profiler::{GpuProfiler, ProfilerCommandRecorder};
+
+pub fn required_features() -> wgpu::Features { wgpu::Features::TIMESTAMP_QUERY }
+
+pub struct Scope<'a, W: ProfilerCommandRecorder> {
+    profiler: &'a mut GpuProfiler,
+    wgpu_thing: &'a mut W,
+}
+
+pub struct OwningScope<'a, W: ProfilerCommandRecorder> {
+    profiler: &'a mut GpuProfiler,
+    wgpu_thing: W,
+}
+
+// Separate type since we can't destructure types that impl Drop :/
+pub struct ManualOwningScope<'a, W: ProfilerCommandRecorder> {
+    profiler: &'a mut GpuProfiler,
+    wgpu_thing: W,
+}
+
+impl<'a, W: ProfilerCommandRecorder> Scope<'a, W> {
+    pub fn start(
+        profiler: &'a mut GpuProfiler,
+        wgpu_thing: &'a mut W,
+        device: &wgpu::Device,
+        label: &str,
+    ) -> Self {
+        profiler.begin_scope(label, wgpu_thing, device);
+        Self {
+            profiler,
+            wgpu_thing,
+        }
+    }
+
+    /// Starts a scope nested within this one
+    pub fn scope(&mut self, device: &wgpu::Device, label: &str) -> Scope<'_, W> {
+        Scope::start(self.profiler, self.wgpu_thing, device, label)
+    }
+}
+
+impl<'a, W: ProfilerCommandRecorder> OwningScope<'a, W> {
+    pub fn start(
+        profiler: &'a mut GpuProfiler,
+        mut wgpu_thing: W,
+        device: &wgpu::Device,
+        label: &str,
+    ) -> Self {
+        profiler.begin_scope(label, &mut wgpu_thing, device);
+        Self {
+            profiler,
+            wgpu_thing,
+        }
+    }
+
+    /// Starts a scope nested within this one
+    pub fn scope(&mut self, device: &wgpu::Device, label: &str) -> Scope<'_, W> {
+        Scope::start(self.profiler, &mut self.wgpu_thing, device, label)
+    }
+}
+
+impl<'a, W: ProfilerCommandRecorder> ManualOwningScope<'a, W> {
+    pub fn start(
+        profiler: &'a mut GpuProfiler,
+        mut wgpu_thing: W,
+        device: &wgpu::Device,
+        label: &str,
+    ) -> Self {
+        profiler.begin_scope(label, &mut wgpu_thing, device);
+        Self {
+            profiler,
+            wgpu_thing,
+        }
+    }
+
+    /// Starts a scope nested within this one
+    pub fn scope(&mut self, device: &wgpu::Device, label: &str) -> Scope<'_, W> {
+        Scope::start(self.profiler, &mut self.wgpu_thing, device, label)
+    }
+
+    /// Ends the scope allowing the extraction of owned the wgpu thing
+    /// and the mutable reference to the GpuProfiler
+    pub fn end_scope(mut self) -> (W, &'a mut GpuProfiler) {
+        self.profiler.end_scope(&mut self.wgpu_thing);
+        (self.wgpu_thing, self.profiler)
+    }
+}
+impl<'a> Scope<'a, wgpu::CommandEncoder> {
+    /// Start a render pass wrapped in an OwnedScope
+    pub fn scoped_render_pass<'b>(
+        &'b mut self,
+        device: &wgpu::Device,
+        label: &str,
+        pass_descriptor: &wgpu::RenderPassDescriptor<'b, '_>,
+    ) -> OwningScope<'b, wgpu::RenderPass> {
+        let render_pass = self.wgpu_thing.begin_render_pass(pass_descriptor);
+        OwningScope::start(self.profiler, render_pass, device, label)
+    }
+}
+
+impl<'a> OwningScope<'a, wgpu::CommandEncoder> {
+    /// Start a render pass wrapped in an OwnedScope
+    pub fn scoped_render_pass<'b>(
+        &'b mut self,
+        device: &wgpu::Device,
+        label: &str,
+        pass_descriptor: &wgpu::RenderPassDescriptor<'b, '_>,
+    ) -> OwningScope<'b, wgpu::RenderPass> {
+        let render_pass = self.wgpu_thing.begin_render_pass(pass_descriptor);
+        OwningScope::start(self.profiler, render_pass, device, label)
+    }
+}
+
+impl<'a> ManualOwningScope<'a, wgpu::CommandEncoder> {
+    /// Start a render pass wrapped in an OwnedScope
+    pub fn scoped_render_pass<'b>(
+        &'b mut self,
+        device: &wgpu::Device,
+        label: &str,
+        pass_descriptor: &wgpu::RenderPassDescriptor<'b, '_>,
+    ) -> OwningScope<'b, wgpu::RenderPass> {
+        let render_pass = self.wgpu_thing.begin_render_pass(pass_descriptor);
+        OwningScope::start(self.profiler, render_pass, device, label)
+    }
+}
+
+// Scope
+impl<'a, W: ProfilerCommandRecorder> std::ops::Deref for Scope<'a, W> {
+    type Target = W;
+
+    fn deref(&self) -> &Self::Target { self.wgpu_thing }
+}
+
+impl<'a, W: ProfilerCommandRecorder> std::ops::DerefMut for Scope<'a, W> {
+    fn deref_mut(&mut self) -> &mut Self::Target { self.wgpu_thing }
+}
+
+impl<'a, W: ProfilerCommandRecorder> Drop for Scope<'a, W> {
+    fn drop(&mut self) { self.profiler.end_scope(self.wgpu_thing); }
+}
+
+// OwningScope
+impl<'a, W: ProfilerCommandRecorder> std::ops::Deref for OwningScope<'a, W> {
+    type Target = W;
+
+    fn deref(&self) -> &Self::Target { &self.wgpu_thing }
+}
+
+impl<'a, W: ProfilerCommandRecorder> std::ops::DerefMut for OwningScope<'a, W> {
+    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.wgpu_thing }
+}
+
+impl<'a, W: ProfilerCommandRecorder> Drop for OwningScope<'a, W> {
+    fn drop(&mut self) { self.profiler.end_scope(&mut self.wgpu_thing); }
+}
+
+// ManualOwningScope
+impl<'a, W: ProfilerCommandRecorder> std::ops::Deref for ManualOwningScope<'a, W> {
+    type Target = W;
+
+    fn deref(&self) -> &Self::Target { &self.wgpu_thing }
+}
+
+impl<'a, W: ProfilerCommandRecorder> std::ops::DerefMut for ManualOwningScope<'a, W> {
+    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.wgpu_thing }
+}
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 6337aa391d..d7719dc9b2 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -1048,9 +1048,7 @@ impl Scene {
         let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
 
         // would instead have this as an extension.
-        if drawer.renderer.render_mode().shadow.is_map()
-            && (is_daylight || !self.light_data.is_empty())
-        {
+        if drawer.render_mode().shadow.is_map() && (is_daylight || !self.light_data.is_empty()) {
             if is_daylight {
                 if let Some(mut shadow_pass) = drawer.shadow_pass() {
                     // Render terrain directed shadows.
diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index eec1fda1ef..7f75e01fb4 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -516,7 +516,7 @@ impl SpriteRenderContext {
                             .into_iter()
                             .map(
                                 |SpriteDataResponse {
-                                     locals,
+                                     locals: locals_buffer,
                                      model,
                                      offset,
                                  }| {
diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs
index 470897178e..fa6d809c3a 100644
--- a/voxygen/src/window.rs
+++ b/voxygen/src/window.rs
@@ -10,7 +10,6 @@ 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, warn};
 use vek::*;
@@ -941,6 +940,9 @@ impl Window {
                 let winit::dpi::PhysicalSize { width, height } = physical;
                 self.events
                     .push(Event::Resize(Vec2::new(width as u32, height as u32)));
+                // TODO: can get invalid scissor rect
+                // panic with this + resize several times
+                // std::thread::sleep_ms(500);
             },
             WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
                 // TODO: is window resized event emitted? or do we need to handle that here?
@@ -1343,7 +1345,9 @@ impl Window {
     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() {
+        let _ = self.renderer.create_screenshot();
+        // TODO
+        /*match self.renderer.create_screenshot() {
             Ok(img) => {
                 let mut path = settings.screenshots_path.clone();
                 let sender = self.message_sender.clone();
@@ -1378,7 +1382,7 @@ impl Window {
                     .unwrap();
             },
             Err(e) => error!(?e, "Couldn't create screenshot due to renderer error"),
-        }
+        }*/
     }
 
     fn is_pressed(