diff --git a/Cargo.lock b/Cargo.lock index 9452c797..674fd1b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.82" @@ -96,7 +105,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -146,6 +155,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -259,7 +279,7 @@ checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -368,6 +388,21 @@ dependencies = [ "rand 0.4.6", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "cocoa" version = "0.25.0" @@ -552,9 +587,14 @@ dependencies = [ "gltf", "image", "log", + "noise", + "num", "once_cell", "rand 0.8.5", "raw-window-handle", + "serde", + "structopt", + "toml 0.8.19", "uuid", "winit", ] @@ -658,7 +698,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -833,7 +873,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -860,9 +900,18 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "heck" @@ -870,6 +919,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -898,7 +956,7 @@ dependencies = [ "exr", "gif", "image-webp", - "num-traits 0.2.18", + "num-traits 0.2.19", "png", "qoi", "ravif", @@ -927,9 +985,9 @@ checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" [[package]] name = "indexmap" -version = "2.1.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -949,7 +1007,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -1203,6 +1261,17 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "noise" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6da45c8333f2e152fc665d78a380be060eb84fad8ca4c9f7ac8ca29216cff0cc" +dependencies = [ + "num-traits 0.2.19", + "rand 0.8.5", + "rand_xorshift", +] + [[package]] name = "nom" version = "7.1.3" @@ -1219,15 +1288,37 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits 0.2.19", +] + [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", - "num-traits 0.2.18", + "num-traits 0.2.19", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits 0.2.19", ] [[package]] @@ -1243,24 +1334,33 @@ dependencies = [ [[package]] name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits 0.2.19", +] + +[[package]] +name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", - "num-traits 0.2.18", + "num-integer", + "num-traits 0.2.19", ] [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", - "num-traits 0.2.18", + "num-traits 0.2.19", ] [[package]] @@ -1269,14 +1369,14 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" dependencies = [ - "num-traits 0.2.18", + "num-traits 0.2.19", ] [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1299,7 +1399,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -1567,7 +1667,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -1609,7 +1709,7 @@ checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi", + "hermit-abi 0.3.9", "pin-project-lite", "rustix", "tracing", @@ -1632,11 +1732,35 @@ dependencies = [ "toml_edit 0.19.15", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.101", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1667,9 +1791,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1732,6 +1856,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.3", +] + [[package]] name = "rav1e" version = "0.6.6" @@ -1754,7 +1887,7 @@ dependencies = [ "new_debug_unreachable", "noop_proc_macro", "num-derive", - "num-traits 0.2.18", + "num-traits 0.2.19", "once_cell", "paste", "rand 0.8.5", @@ -1943,22 +2076,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -1974,9 +2107,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -2060,6 +2193,36 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.101", +] + [[package]] name = "syn" version = "1.0.101" @@ -2073,9 +2236,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -2089,9 +2252,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", - "heck", + "heck 0.5.0", "pkg-config", - "toml 0.8.12", + "toml 0.8.19", "version-compare", ] @@ -2101,6 +2264,15 @@ version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -2118,7 +2290,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] @@ -2183,21 +2355,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.12", + "toml_edit 0.22.22", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -2215,15 +2387,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.12" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.7", + "winnow 0.6.20", ] [[package]] @@ -2275,6 +2447,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "url" version = "2.4.1" @@ -2308,10 +2486,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" dependencies = [ "aligned-vec", - "num-traits 0.2.18", + "num-traits 0.2.19", "wasm-bindgen", ] +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version-compare" version = "0.2.0" @@ -2361,7 +2545,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -2395,7 +2579,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2841,9 +3025,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.7" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -2925,7 +3109,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9ee4156a..b7fac3e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,9 @@ name = "dotrix" version = "0.6.0" authors = [ - "Elias Kartašov ", - "Štěpán Wünsch ", - "Nikita Zemtsov ", + "Elias Kartašov ", + "Štěpán Wünsch ", + "Nikita Zemtsov ", ] edition = "2021" description = "3D Engine" @@ -18,27 +18,45 @@ path = "src/lib.rs" name = "demo" path = "demo/main.rs" +[[bin]] +name = "dotrix-terrain" +path = "utils/terrain/main.rs" +required-features = ["terrain"] + [features] -default = [] +default = ["terrain"] +terrain = [ + "dep:noise", + "dep:rand", + "dep:num", + "dep:structopt", + "dep:toml", + "dep:serde", +] [dependencies] -rand = "0.8" bytemuck = { version = "1.4", features = ["derive"] } image = "0.25" uuid = { version = "1.1", features = ["v4"] } log = "0.4.20" once_cell = "1.18.0" -futures = {version = "0.3", default-features = false, features = ["std", "executor"]} -raw-window-handle = {version = "0.6.1"} -winit = {version = "0.30.5", features = ["serde", "rwh_06"]} +futures = { version = "0.3", default-features = false, features = [ + "std", + "executor", +] } +raw-window-handle = { version = "0.6.1" } +winit = { version = "0.30.5", features = ["serde", "rwh_06"] } bitflags = "2.4.1" ash = "0.38.0" ash-window = "0.13.0" gltf = "1.4.1" base64 = "0.22.0" glam = { version = "0.27.0", features = ["bytemuck"] } -genmesh = "0.6.2" - -[dev-dependencies] -#noise = { version = "0.8" } -#bytemuck = { version = "1.4", features = ["derive"] } +genmesh = "0.6.2" # TODO: make optional +# optional +noise = { version = "0.9.0", optional = true } +num = { version = "0.4", optional = true } +rand = { version = "0.8.4", features = ["small_rng"], optional = true } +structopt = { version = "0.3", optional = true } +toml = { version = "0.8", optional = true } +serde = { version = "1.0", optional = true } diff --git a/README.md b/README.md index c8196195..6d184a88 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ glslc -fshader-stage=vertex src/models/shaders/only_mesh.vert -o src/models/shad glslc -fshader-stage=fragment src/models/shaders/only_mesh.frag -o src/models/shaders/only_mesh.frag.spv glslc -fshader-stage=vertex src/models/shaders/skin_mesh.vert -o src/models/shaders/skin_mesh.vert.spv glslc -fshader-stage=fragment src/models/shaders/skin_mesh.frag -o src/models/shaders/skin_mesh.frag.spv +glslc -fshader-stage=vertex src/features/terrain/shaders/terrain.vert -o src/features/terrain/shaders/terrain.vert.spv +glslc -fshader-stage=fragment src/features/terrain/shaders/terrain.frag -o src/features/terrain/shaders/terrain.frag.spv ``` ## Sponsors diff --git a/configs/terrain.toml b/configs/terrain.toml new file mode 100644 index 00000000..4af5a393 --- /dev/null +++ b/configs/terrain.toml @@ -0,0 +1,14 @@ +name = "terrain" +size = 1024 + +[noise] +octaves = 4 +persistence = 0.5 # 0..1 +lacunarity = 2.0 # > 1 +scale = 250.0 +offset = [0.0, 0.0] +seed = 0 + +[falloff] +power = 2.6 +factor = 2.4 diff --git a/features/vulkan/Cargo.toml b/features/vulkan/Cargo.toml deleted file mode 100644 index cc74bf0a..00000000 --- a/features/vulkan/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "vulkan" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/features/vulkan/src/lib.rs b/features/vulkan/src/lib.rs deleted file mode 100644 index 7d12d9af..00000000 --- a/features/vulkan/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/src/features.rs b/src/features.rs new file mode 100644 index 00000000..0a6c1c5b --- /dev/null +++ b/src/features.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "terrain")] +pub mod terrain; diff --git a/src/features/terrain.rs b/src/features/terrain.rs new file mode 100644 index 00000000..501341a1 --- /dev/null +++ b/src/features/terrain.rs @@ -0,0 +1,311 @@ +mod colormap; +mod generators; +mod heightmap; +mod renderer; + +use std::collections::HashMap; + +use crate::{Any, Assets, Camera, Entity, Id, Mesh, Mut, Ref, Task, VertexAttribute, World}; +pub use colormap::ColorMap; +pub use generators::{Generator, LowPolyTerrain, SimpleTerrain, TileSetup}; +pub use heightmap::{FalloffConfig, HeightMap, NoiseConfig}; +pub use renderer::{LodSetup, RenderTerrain, RenderTerrainSetup}; + +pub const TILE_MIN_SIZE: u32 = 2; +pub const TILE_MAX_SIZE: u32 = 254; +pub const DEFAULT_TILE_SIZE: u32 = 120; +pub const DEFAULT_TILES_IN_VIEW_RANGE: u32 = 4; +pub const DEFAULT_MAX_LODS: u32 = 3; +pub const DEFAULT_HEIGHT_AMPLIFIER: f32 = 100.0; + +pub struct Moisture { + pub value: f32, +} + +impl VertexAttribute for Moisture { + type Raw = f32; + fn name() -> &'static str { + "Moisture" + } + fn pack(&self) -> Self::Raw { + self.value + } + fn format() -> crate::Format { + crate::Format::Float32 + } +} + +/// Terrain Level of Details +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)] +pub struct LoD { + value: u32, +} + +impl LoD { + pub fn new(value: u32) -> Self { + LoD { value } + } + pub fn factor(&self) -> u32 { + if self.value != 0 { + self.value * 2 + } else { + 1 + } + } + pub fn value(&self) -> u32 { + self.value + } +} + +/// Terrain component +pub struct Terrain { + /// Terrain mesh + pub mesh: Mesh, + /// Terrain X world position + pub x: f32, + /// Terrain Z world position + pub z: f32, + /// Terrain Level Of Details (0..) + pub lod: LoD, +} + +pub struct SpawnTerrain { + /// number of sections (each section represents a LOD) + tiles_in_view_range: u32, + /// maximal number of LODs + max_lods: u32, + /// spawned entities index + index: HashMap>, + /// Height Map asset name + heightmap: String, + /// Moisture Map asset name + moisturemap: String, + /// Color Map asset name + colormap: String, + /// generator + generator: Box, +} + +impl Default for SpawnTerrain { + fn default() -> Self { + let total_visible_tiles = 4 * DEFAULT_TILES_IN_VIEW_RANGE * DEFAULT_TILES_IN_VIEW_RANGE; + Self { + tiles_in_view_range: DEFAULT_TILES_IN_VIEW_RANGE, + max_lods: DEFAULT_MAX_LODS, + index: HashMap::with_capacity(total_visible_tiles as usize), + heightmap: String::from("terrain::heightmap"), + moisturemap: String::from("terrain::moisturemap"), + colormap: String::from("terrain::colormap"), + generator: Box::new(SimpleTerrain::new( + DEFAULT_TILE_SIZE, + DEFAULT_HEIGHT_AMPLIFIER, + )), + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Tile { + xi: i32, + zi: i32, + lod: LoD, +} + +impl SpawnTerrain { + pub fn with_tiles_in_view_range(mut self, tiles_in_view_range: u32) -> Self { + self.tiles_in_view_range = tiles_in_view_range; + self + } + + pub fn with_max_lods(mut self, max_lods: u32) -> Self { + self.max_lods = max_lods; + self + } + + pub fn with_heightmap(mut self, heightmap: impl Into) -> Self { + self.heightmap = heightmap.into(); + self + } + + pub fn with_moisturemap(mut self, moisturemap: impl Into) -> Self { + self.moisturemap = moisturemap.into(); + self + } + + pub fn with_colormap(mut self, colormap: impl Into) -> Self { + self.colormap = colormap.into(); + self + } + + pub fn with_generator(mut self, generator: Box) -> Self { + self.generator = generator; + self + } + + fn get_tile_lod(&self, xi: i32, zi: i32) -> LoD { + for i in 0..self.max_lods { + if self.tiles_in_view_range < i { + break; + } + + let boundary = (self.tiles_in_view_range - i) as i32; + if xi == -boundary || xi == boundary - 1 || zi == -boundary || zi == boundary - 1 { + return LoD::new(self.max_lods - i - 1); + } + } + LoD::default() + } +} + +#[derive(Default, Debug)] +pub struct SpawnTerrainOutput { + pub tiles_to_exile: Vec>, // must have + pub tiles_to_spawn: Vec>, // not very usefull + pub scene: Vec>, // could be usefull +} + +impl Task for SpawnTerrain { + type Output = SpawnTerrainOutput; + type Context = (Any, Mut, Ref); + + fn run(&mut self, (camera, mut world, assets): Self::Context) -> Self::Output { + // log::debug!("SpawnTerrain::run()"); + let mut index = HashMap::with_capacity(self.index.capacity()); + let mut tiles_to_spawn: Vec> = Vec::with_capacity(4); + let pos_x = camera.target.x; + let pos_z = camera.target.z; + //log::debug!("SpawnTerrain::run(@{};{})", pos_x, pos_z); + let tile_size = self.generator.tile_size(); + + let terrain_xi = (pos_x / (tile_size as f32)).floor() as i32; + let terrain_zi = (pos_z / (tile_size as f32)).floor() as i32; + + let tiles_in_view_range = self.tiles_in_view_range as i32; + + let heightmap = match assets + .find::(&self.heightmap) + .and_then(|id| assets.get(id)) + { + Some(heightmap) => heightmap, + None => { + log::warn!("Terrain `{}` asset is not ready", self.heightmap); + return SpawnTerrainOutput::default(); + } + }; + + let moisturemap = match assets + .find::(&self.moisturemap) + .and_then(|id| assets.get(id)) + { + Some(moisturemap) => moisturemap, + None => { + log::warn!("Terrain `{}` asset is not ready", self.moisturemap); + return SpawnTerrainOutput::default(); + } + }; + + let colormap = match assets + .find::(&self.colormap) + .and_then(|id| assets.get(id)) + { + Some(colormap) => colormap, + None => { + log::warn!("Terrain `{}` asset is not ready", self.colormap); + return SpawnTerrainOutput::default(); + } + }; + + for offset_z in -tiles_in_view_range..tiles_in_view_range { + for offset_x in -tiles_in_view_range..tiles_in_view_range { + let zi = terrain_zi + offset_z; + let xi = terrain_xi + offset_x; + let lod = self.get_tile_lod(offset_x, offset_z); + // log::debug!("tile LOD: {};{} -> {}", xi, zi, lod.value()); + let tile = Tile { xi, zi, lod }; + + if let Some(entity_id) = self.index.remove(&tile) { + index.insert(tile, entity_id); + } else { + let map_offset = heightmap.size() as i32 / 2; + let setup = TileSetup { + lod, + position_x: xi * tile_size as i32, + position_z: zi * tile_size as i32, + map_offset_x: -map_offset, + map_offset_z: -map_offset, + heightmap, + moisturemap, + colormap, + }; + + let terrain = self.generator.generate(&setup); + + let entity_id = world + .spawn(Some(Entity::empty().with(terrain))) + .next() + .expect("Terrain entity id was not returned after spawning"); + index.insert(tile, entity_id); + // log::debug!("spawn -> ({};{} -> {})", xi, zi, lod.value()); + tiles_to_spawn.push(entity_id); + } + } + } + + // for (tile, entity) in index.iter() { + // log::debug!("terrain setup: [{:?}]: {:?}", entity, tile); + // } + + let mut tiles_to_exile = Vec::with_capacity(self.index.len()); + // Clear terrain out of view range + for entity_id in self.index.values() { + /* TODO: exile is broken on ECS level + if let Some(entity) = world.exile(entity_id) { + if let Some(terrain) = entity.get::() { + log::debug!("exile -> ({})", terrain.lod.value()); + } else { + log::error!("exile -> entity is not a terrain: {:?}", entity_id); + } + } else { + log::error!("exile -> failed {:?}", entity_id); + } + */ + tiles_to_exile.push(*entity_id); + } + let scene = index.values().copied().collect::>(); + self.index = index; + + // log::debug!("tiles: to_exile - {}", tiles_to_exile.len()); + // log::debug!("tiles: to_spawn - {}", tiles_to_spawn.len()); + // log::debug!("tiles: total - {}", self.index.len()); + + SpawnTerrainOutput { + tiles_to_exile, + tiles_to_spawn, + scene, + } + } +} + +#[cfg(test)] +mod tests { + + use super::SpawnTerrain; + + #[test] + fn detect_tile_lod_by_axis() { + let spawn_terrain = SpawnTerrain::default(); + let tiles_in_view_range = spawn_terrain.tiles_in_view_range as i32; + + assert_eq!(spawn_terrain.get_tile_lod(0, 0).value(), 0); + assert_eq!( + spawn_terrain.get_tile_lod(-tiles_in_view_range, 2).value(), + 2 + ); + assert_eq!( + spawn_terrain + .get_tile_lod(-tiles_in_view_range + 1, 1) + .value(), + 1 + ); + } +} diff --git a/src/features/terrain/colormap.rs b/src/features/terrain/colormap.rs new file mode 100644 index 00000000..b1b28042 --- /dev/null +++ b/src/features/terrain/colormap.rs @@ -0,0 +1,38 @@ +use crate::Asset; + +pub struct ColorMap { + name: String, + colors: Vec<[u8; 3]>, + moisture_blend_factor: f32, +} + +impl ColorMap { + pub fn new(name: impl Into, colors: Vec<[u8; 3]>, moisture_blend_factor: f32) -> Self { + Self { + name: name.into(), + colors, + moisture_blend_factor, + } + } + + pub fn color(&self, height: f32, moisture: f32) -> [u8; 3] { + let color_count = self.colors.len() as f32; + let color_step = 1.0 / color_count; + let moisture_blend = + moisture * self.moisture_blend_factor - (self.moisture_blend_factor / 2.0); + let color_height = (height + moisture_blend).clamp(0.0, 1.0); + let color_index = (color_height / color_step).floor() as usize; + + self.colors[if color_index < self.colors.len() { + color_index + } else { + self.colors.len() - 1 + }] + } +} + +impl Asset for ColorMap { + fn name(&self) -> &str { + &self.name + } +} diff --git a/src/features/terrain/generators.rs b/src/features/terrain/generators.rs new file mode 100644 index 00000000..fcd8c3d2 --- /dev/null +++ b/src/features/terrain/generators.rs @@ -0,0 +1,406 @@ +use super::{ColorMap, HeightMap, LoD, Moisture, Terrain}; +use crate::math::Vec3; +use crate::{Color, Mesh, VertexNormal, VertexPosition}; + +pub struct TileSetup<'a> { + pub lod: LoD, + pub map_offset_x: i32, + pub map_offset_z: i32, + pub position_x: i32, + pub position_z: i32, + pub heightmap: &'a HeightMap, + pub moisturemap: &'a HeightMap, + pub colormap: &'a ColorMap, +} + +pub trait Generator: Send + Sync { + fn tile_size(&self) -> u32; + fn amplify_height(&self, heightmap_value: f32) -> f32; + fn vertices_count(&self, lod: LoD) -> u32; + fn indices_count(&self, lod: LoD) -> u32; + fn indices(&self, lod: LoD) -> Vec; + fn generate(&self, setup: &TileSetup) -> Terrain; +} + +pub struct SimpleTerrain { + pub tile_size: u32, + pub height_factor: f32, +} + +impl SimpleTerrain { + pub fn new(tile_size: u32, height_factor: f32) -> Self { + Self { + tile_size, + height_factor, + } + } +} + +impl Generator for SimpleTerrain { + fn tile_size(&self) -> u32 { + self.tile_size + } + fn amplify_height(&self, heightmap_value: f32) -> f32 { + self.height_factor * heightmap_value.powi(4) + } + fn vertices_count(&self, lod: LoD) -> u32 { + let tile_size = self.tile_size / lod.factor(); + let vertices_per_side = tile_size + 1; + vertices_per_side * vertices_per_side + } + fn indices_count(&self, lod: LoD) -> u32 { + let tile_size = self.tile_size / lod.factor(); + tile_size * tile_size * 2 * 3 + } + fn indices(&self, lod: LoD) -> Vec { + let indices_count = self.indices_count(lod); + let tile_size = self.tile_size / lod.factor(); + + let mut indices = Vec::with_capacity(indices_count as usize); + let vertices_per_side = tile_size + 1; + for xi in 0..tile_size { + let offset = xi * vertices_per_side; + for zi in 0..tile_size { + /* + A *---* B + | \ | + D *---* C + */ + let index_a = offset + zi; + let index_b = index_a + 1; + let index_c = index_b + vertices_per_side; + let index_d = index_a + vertices_per_side; + + indices.extend([ + index_a, index_c, index_b, // face ACB + index_c, index_a, index_d, // face CAD + ]); + } + } + + // log::debug!(" terrain[{}]: indices=\n{:?}", tile_size, indices); + indices + } + fn generate(&self, setup: &TileSetup) -> Terrain { + let lod_factor = setup.lod.factor(); + let sqares_per_side = self.tile_size / lod_factor; + let vertices_per_side = sqares_per_side + 1; + let vertices_count = vertices_per_side * vertices_per_side; + let mut vertices = Vec::with_capacity(vertices_count as usize); + let mut colors = Vec::with_capacity(vertices_count as usize); + let mut normals = vec![[0.0, 0.0, 0.0]; vertices_count as usize]; + let mut moistures = vec![0.0; vertices_count as usize]; + + // log::debug!( + // "Generate terrain: lod={}, vertices_count={}", + // self.lod.value(), + // vertices_count + // ); + + let map_offset = (setup.heightmap.size() as i32) / 2; + + for zi in 0..vertices_per_side { + for xi in 0..vertices_per_side { + let world_x = setup.position_x + (xi * lod_factor) as i32; + let world_z = setup.position_z + (zi * lod_factor) as i32; + let map_x = (map_offset + world_x).clamp(0, setup.heightmap.size() as i32) as u32; + let map_z = (map_offset + world_z).clamp(0, setup.heightmap.size() as i32) as u32; + let height = setup.heightmap.value(map_x, map_z); + let world_y = self.amplify_height(height); + + /* + log::debug!( + "world:({};{}), map:({}:{}; size:{}, offset: {}), height:({}->{})", + world_x, + world_z, + map_x, + map_z, + heightmap.size(), + map_offset, + height, + world_y + );*/ + + vertices.push([world_x as f32, world_y, world_z as f32]); + + let moisture = setup.moisturemap.value(map_x, map_z); + moistures.push(moisture); + + let color: Color = + (&Color::::from(setup.colormap.color(height, moisture))).into(); + /* + let color = (color[0] as u32) << 24 + | (color[1] as u32) << 16 + | (color[2] as u32) << 8 + | 255; + */ + // let color = match self.lod.value() { + // 0 => Color::rgb(1.0, 0.0, 0.0), + // 1 => Color::rgb(0.0, 1.0, 0.0), + // 2 => Color::rgb(0.0, 0.0, 1.0), + // _ => Color::rgb(1.0, 1.0, 1.0), + // }; + colors.push((&color).into()); + } + } + + for zi in 0..sqares_per_side { + let offset = zi * vertices_per_side; + for xi in 0..sqares_per_side { + /* + A *---* B + | \ | + D *---* C + */ + let index_a = (offset + xi) as usize; + let index_b = index_a + 1; + let index_c = index_b + vertices_per_side as usize; + let index_d = index_a + vertices_per_side as usize; + + let vertex_a = Vec3::from(vertices[index_a]); + let vertex_b = Vec3::from(vertices[index_b]); + let vertex_c = Vec3::from(vertices[index_c]); + let vertex_d = Vec3::from(vertices[index_d]); + + // face ACB + normals[index_b] = (vertex_b - vertex_a) + .cross(vertex_c - vertex_a) + .normalize() + .into(); + // face CAD + normals[index_d] = (vertex_d - vertex_c) + .cross(vertex_a - vertex_c) + .normalize() + .into(); + } + } + + // log::debug!(" terrain: lod={}", self.lod.value(),); + // log::debug!(" terrain: vertices=\n{:?}", vertices); + // log::debug!(" terrain: colors=\n{:?}", colors,); + + let mut mesh = Mesh::new("terrain"); + mesh.set_vertices::(vertices); + mesh.set_vertices::(normals); + mesh.set_vertices::>(colors); + mesh.set_vertices::(moistures); + + Terrain { + mesh, + lod: setup.lod, + x: setup.position_x as f32, + z: setup.position_z as f32, + } + } +} + +pub struct LowPolyTerrain { + pub tile_size: u32, + pub height_factor: f32, +} + +impl LowPolyTerrain { + pub fn new(tile_size: u32, height_factor: f32) -> Self { + Self { + tile_size, + height_factor, + } + } + + fn calculate_duplicated_vertex_index(index: u32, vertices_per_side: u32, row: u32) -> u32 { + vertices_per_side * vertices_per_side + (index - vertices_per_side - (row - 1) * 2 - 1) + } + fn generate_sqaure_indices(square_x: u32, square_z: u32, vertices_per_side: u32) -> [u32; 6] { + let offset = square_z * vertices_per_side; + let top_left = offset + square_x; + let top_right = top_left + 1; + let bottom_left = top_left + vertices_per_side; + let bottom_right = top_right + vertices_per_side; + + let last_square = vertices_per_side - 2; + + if square_x % 2 == 0 { + // duplicate vertices + let real_top_left = if square_x == 0 || square_z == 0 { + top_left + } else { + Self::calculate_duplicated_vertex_index(top_left, vertices_per_side, square_z) + }; + let real_top_right = if square_x == last_square || square_z == 0 { + top_right + } else { + Self::calculate_duplicated_vertex_index(top_right, vertices_per_side, square_z) + }; + if square_z % 2 == 0 { + [ + real_top_left, + bottom_left, + bottom_right, + real_top_right, + top_left, + bottom_right, + ] + } else { + [ + real_top_left, + bottom_left, + top_right, + real_top_right, + bottom_left, + bottom_right, + ] + } + } else if square_z % 2 == 0 { + [ + bottom_left, + top_right, + top_left, + bottom_right, + top_right, + bottom_left, + ] + } else { + [ + bottom_left, + bottom_right, + top_left, + bottom_right, + top_right, + top_left, + ] + } + } +} + +impl Generator for LowPolyTerrain { + fn tile_size(&self) -> u32 { + self.tile_size + } + fn amplify_height(&self, heightmap_value: f32) -> f32 { + self.height_factor * heightmap_value.powi(4) + } + fn vertices_count(&self, lod: LoD) -> u32 { + let tile_size = self.tile_size / lod.factor(); + let vertices_per_side = tile_size + 1; + let duplicated_per_side = tile_size - 1; + vertices_per_side * vertices_per_side + duplicated_per_side * duplicated_per_side + } + fn indices_count(&self, lod: LoD) -> u32 { + let tile_size = self.tile_size / lod.factor(); + tile_size * tile_size * 2 * 3 + } + fn indices(&self, lod: LoD) -> Vec { + let indices_count = self.indices_count(lod); + let tile_size = self.tile_size / lod.factor(); + let squares_per_side = tile_size; + let vertices_per_side = tile_size + 1; + let mut indices = Vec::with_capacity(indices_count as usize); + + for square_z in 0..squares_per_side { + for square_x in 0..squares_per_side { + let square_indices = + Self::generate_sqaure_indices(square_x, square_z, vertices_per_side); + indices.extend(square_indices.into_iter()); + } + } + // log::debug!(" terrain[{}]: indices=\n{:?}", tile_size, indices); + indices + } + fn generate(&self, setup: &TileSetup) -> Terrain { + let lod_factor = setup.lod.factor(); + let squares_per_side = self.tile_size / lod_factor; + let vertices_per_side = squares_per_side + 1; + let duplicates_per_side = squares_per_side - 1; + let unique_vertices_count = vertices_per_side * vertices_per_side; + let vertices_count = unique_vertices_count + duplicates_per_side * duplicates_per_side; + let mut vertices = vec![[0.0, 0.0, 0.0]; vertices_count as usize]; + let mut colors = vec![[0.0, 0.0, 0.0, 0.0]; vertices_count as usize]; + let mut normals = vec![[0.0, 0.0, 0.0]; vertices_count as usize]; + let mut moistures = vec![0.0; vertices_count as usize]; + + // log::debug!( + // "Generate terrain: lod={}, vertices_count={}", + // self.lod.value(), + // vertices_count + // ); + + let map_offset = (setup.heightmap.size() as i32) / 2; + let mut vertex_cursor = 0; + let mut duplicates_cursor = unique_vertices_count as usize; + + for zi in 0..vertices_per_side { + for xi in 0..vertices_per_side { + let world_x = setup.position_x + (xi * lod_factor) as i32; + let world_z = setup.position_z + (zi * lod_factor) as i32; + let map_x = (map_offset + world_x).clamp(0, setup.heightmap.size() as i32) as u32; + let map_z = (map_offset + world_z).clamp(0, setup.heightmap.size() as i32) as u32; + let height = setup.heightmap.value(map_x, map_z); + let world_y = self.amplify_height(height); + + /* + log::debug!( + "world:({};{}), map:({}:{}; size:{}, offset: {}), height:({}->{})", + world_x, + world_z, + map_x, + map_z, + heightmap.size(), + map_offset, + height, + world_y + );*/ + + vertices[vertex_cursor] = [world_x as f32, world_y, world_z as f32]; + let moisture = setup.moisturemap.value(map_x, map_z); + let color: Color = + (&Color::::from(setup.colormap.color(height, moisture))).into(); + // let color: Color = Color::grey(); + colors[vertex_cursor] = (&color).into(); + moistures[vertex_cursor] = moisture; + if xi != 0 && xi != squares_per_side && zi != 0 && zi != squares_per_side { + // duplicate vertex + vertices[duplicates_cursor] = [world_x as f32, world_y, world_z as f32]; + colors[duplicates_cursor] = (&color).into(); + duplicates_cursor += 1; + } + vertex_cursor += 1; + } + } + + for square_z in 0..squares_per_side { + for square_x in 0..squares_per_side { + let indices = Self::generate_sqaure_indices(square_x, square_z, vertices_per_side); + + // log::debug!("square: ({}, {}) -> {:?}", square_x, square_z, indices)); + + for triangle in 0..2 { + let base_index = triangle * 3; + let index0 = indices[base_index] as usize; + let index1 = indices[base_index + 1] as usize; + let index2 = indices[base_index + 2] as usize; + normals[index0] = (Vec3::from(vertices[index1]) - Vec3::from(vertices[index0])) + .cross(Vec3::from(vertices[index2]) - Vec3::from(vertices[index0])) + .normalize() + .into(); + } + } + } + + // log::debug!(" terrain: lod={}", self.lod.value(),); + // log::debug!(" terrain: vertices=\n{:?}", vertices); + // log::debug!(" terrain: colors=\n{:?}", colors,); + + let mut mesh = Mesh::new("terrain"); + mesh.set_vertices::(vertices); + mesh.set_vertices::(normals); + mesh.set_vertices::>(colors); + mesh.set_vertices::(moistures); + + Terrain { + mesh, + lod: setup.lod, + x: setup.position_x as f32, + z: setup.position_z as f32, + } + } +} diff --git a/src/features/terrain/heightmap.rs b/src/features/terrain/heightmap.rs new file mode 100644 index 00000000..8903f996 --- /dev/null +++ b/src/features/terrain/heightmap.rs @@ -0,0 +1,254 @@ +use noise::{NoiseFn, Perlin}; +use serde::{Deserialize, Serialize}; + +use rand::rngs::SmallRng; +use rand::{RngCore, SeedableRng}; + +use crate::Asset; + +pub struct HeightMap { + name: String, + values: Vec, + size: u32, +} + +impl HeightMap { + pub fn new(name: impl Into, size: u32) -> Self { + Self { + name: name.into(), + values: vec![0.0; (size * size) as usize], + size, + } + } + + pub fn new_from_noise(name: impl Into, size: u32, noise_config: &NoiseConfig) -> Self { + let mut noise_values = vec![0.0; (size * size) as usize]; + let noise = Perlin::new(noise_config.seed); + + let mut pseudo_rng = SmallRng::seed_from_u64(noise_config.seed as u64); + + let octaves_offsets = (0..noise_config.octaves) + .map(|_| { + [ + Self::randomize_offset(noise_config.offset[0], &mut pseudo_rng), + Self::randomize_offset(noise_config.offset[1], &mut pseudo_rng), + ] + }) + .collect::>(); + + let mut min_noise_value: f32 = f32::MAX; + let mut max_noise_value: f32 = f32::MIN; + + let half_size = (size / 2) as f32; // to apply scale to the center + for z in 0..size { + let offset = z * size; + for x in 0..size { + let index = (offset + x) as usize; + let mut noise_value = 0.0; + + let mut amplitude = 1.0; + let mut frequency = 1.0; + + for octave_offset in octaves_offsets.iter() { + let xf = + (x as f32 - half_size) / noise_config.scale * frequency + octave_offset[0]; + let zf = + (z as f32 - half_size) / noise_config.scale * frequency + octave_offset[1]; + + let noise_octave_value = 2.0 * (noise.get([xf as f64, zf as f64]) as f32) - 1.0; + noise_value += noise_octave_value * amplitude; + + amplitude *= noise_config.persistence; + frequency *= noise_config.lacunarity; + } + + if noise_value < min_noise_value { + min_noise_value = noise_value; + } + + if noise_value > max_noise_value { + max_noise_value = noise_value; + } + + noise_values[index] = noise_value; + } + } + + // normalize values + let delta = max_noise_value - min_noise_value; + let values = noise_values + .into_iter() + .map(|value| (((value - min_noise_value) / delta).clamp(0.0, 1.0))) + .collect::>(); + + Self { + name: name.into(), + values, + size, + } + } + + /// Returns falloff map + pub fn new_from_falloff( + name: impl Into, + size: u32, + falloff_config: &FalloffConfig, + ) -> Self { + let mut values = vec![0.0; (size * size) as usize]; + for z in 0..size { + let offset = z * size; + for x in 0..size { + let index = (offset + x) as usize; + let value_x = (x as f32 / size as f32 * 2.0 - 1.0).abs(); + let value_z = (z as f32 / size as f32 * 2.0 - 1.0).abs(); + let value = if value_x > value_z { value_x } else { value_z }; + // soften the curve + let power_of_value = value.powf(falloff_config.power); + let value = power_of_value + / (power_of_value + + (falloff_config.factor - falloff_config.factor * value) + .powf(falloff_config.power)); + + values[index] = value.clamp(0.0, 1.0); + } + } + Self { + name: name.into(), + size, + values, + } + } + + pub fn new_from_bytes(name: impl Into, size: u32, bytes: &[u8]) -> Self { + let pixels_count = (size * size) as usize; + let bytes_per_pixel = bytes.len() / pixels_count; + let mut values = vec![0.0; pixels_count]; + + let mut max_value: u32 = 0; + for _ in 0..bytes_per_pixel { + max_value = (max_value << 8) | 0xFF; + } + + //log::debug!("max_value: {}", max_value); + + for i in 0..pixels_count { + let mut value_uint: u32 = 0; + for byte in (0..bytes_per_pixel).rev() { + // value_uint = + // value_uint | ((bytes[i * bytes_per_pixel + byte] as u32) << byte_shift); + value_uint = (value_uint << 8) | (bytes[i * bytes_per_pixel + byte] as u32); + } + // log::debug!("value_uint: 0x{:.04x}", value_uint); + values[i] = (value_uint as f64 / max_value as f64) as f32; + } + // panic!("brea"); + Self { + name: name.into(), + values, + size, + } + } + + pub fn size(&self) -> u32 { + self.size + } + + pub fn value(&self, x: u32, z: u32) -> f32 { + let i = (z * self.size + x) as usize; + self.values[i] + } + + pub fn subtract(&mut self, map: &Self) { + if self.size != map.size { + panic!( + "Could not subtract maps of different sizes: {} != {}", + self.size, map.size, + ); + } + + for z in 0..self.size { + let offset = z * self.size; + for x in 0..self.size { + let i = (offset + x) as usize; + self.values[i] = (self.values[i] - map.values[i]).clamp(0.0, 1.0); + } + } + } + + fn randomize_offset(value: f32, pseudo_rng: &mut SmallRng) -> f32 { + value + (pseudo_rng.next_u32() & 0xFFFF) as f32 - 32768.0 + } + + pub fn write_to_file( + &self, + path: &std::path::Path, + format: image::ImageFormat, + ) -> Result<(), image::ImageError> { + let size = self.size; + let buffer = (0..size) + .flat_map(|z| (0..size).map(move |x| ((self.value(x, z) * 65535.0).round() as u16))) + .collect::>(); + + let image_buffer: image::ImageBuffer, Vec> = + image::ImageBuffer::from_vec(size, size, buffer) + .expect("Could not generate heightmap pixels buffers"); + + image_buffer + //image::GrayImage::from_raw(size, size, pixels) + .save_with_format(path, format) + } +} + +impl Asset for HeightMap { + fn name(&self) -> &str { + &self.name + } +} + +/// Noise configuration +#[derive(Debug, Clone, Copy, Deserialize, Serialize)] +pub struct NoiseConfig { + /// Number of octaves + pub octaves: u32, + /// Noise persistence + pub persistence: f32, + /// Noise Lacunarity + pub lacunarity: f32, + /// Noise scale + pub scale: f32, + /// Offset of the noise sampling + pub offset: [f32; 2], + /// Noise seed + pub seed: u32, +} + +impl Default for NoiseConfig { + fn default() -> Self { + Self { + octaves: 4, + persistence: 0.5, + lacunarity: 2.0, + scale: 250.0, + offset: [0.0, 0.0], + seed: 0, + } + } +} + +/// Falloff settings +#[derive(Debug, Clone, Copy, Deserialize, Serialize)] +pub struct FalloffConfig { + /// How rough should fall off + pub power: f32, + /// How far should fall off + pub factor: f32, +} + +impl Default for FalloffConfig { + fn default() -> Self { + Self { + power: 2.6, + factor: 2.4, + } + } +} diff --git a/src/features/terrain/renderer.rs b/src/features/terrain/renderer.rs new file mode 100644 index 00000000..514da303 --- /dev/null +++ b/src/features/terrain/renderer.rs @@ -0,0 +1,882 @@ +use std::collections::HashMap; +use std::io::Cursor; + +use ash::vk::Handle; + +use crate::graphics::vk; +use crate::graphics::{Buffer, CommandRecorder, Display, Extent2D, Gpu, RenderSubmit}; +use crate::models::{Transform3D, VertexBufferLayout, VertexNormal, VertexPosition}; +use crate::{Any, Camera, Entity, Frame, Id, Ref, Task, World}; + +use super::{ + LoD, Moisture, SpawnTerrainOutput, Terrain, DEFAULT_TILES_IN_VIEW_RANGE, DEFAULT_TILE_SIZE, +}; + +pub type MeshLayout = (VertexPosition, VertexNormal, Moisture); + +/// Terrain rendering task +pub struct RenderTerrain { + /// Wait for render submits from following + wait_for: Vec, + /// GPU instance + gpu: Gpu, + /// Version of surface to track changes and update framebuffers and fender pass + surface_version: u64, + /// globals buffer + globals_buffer: Buffer, + /// transform buffer + instance_buffer: Buffer, + /// indirect buffer + indirect_buffer: Buffer, + /// Indices buffer + index_buffer: Buffer, + /// Vertex buffer + vertex_buffer: Buffer, + /// Used bytes in vertex buffer + vertex_buffer_usage: u64, + /// descriptor sets + descriptor_sets: Vec, + /// descriptor pool + _descriptor_pool: vk::DescriptorPool, + /// descriptor set layouts + _desc_set_layouts: [vk::DescriptorSetLayout; 1], + /// Pipeline layout to render models + pipeline_layout_render: vk::PipelineLayout, + /// Graphics pipeline to render models + pipeline_render: vk::Pipeline, + /// Vertex shader module + shader_vertex: vk::ShaderModule, + /// Fragment shader module + shader_fragment: vk::ShaderModule, + /// Terrain tiles index + tiles_index: HashMap, Slot>, + /// Lod info index + lods_index: HashMap, + /// Instance buffer data + instance_buffer_data: Vec, + /// Indirect buffer data + indirect_buffer_data: Vec, +} + +#[derive(Clone, Copy, Debug, Default)] +struct Slot { + /// Terrain tile offset in the buffer + offset: u64, + /// Size + size: u64, +} + +#[derive(Clone, Debug, Default)] +pub struct LodSetup { + pub lod: LoD, + pub vertices_count: u32, + pub indices: Vec, +} + +#[derive(Clone, Copy, Debug, Default)] +struct LodInfo { + /// Number of vertices + vertices_count: u32, + /// Number of indices + indices_count: u32, + /// Base index of the LoD in buffer + first_index: u32, + /// Number of tiles + tiles_count: u32, +} + +impl Drop for RenderTerrain { + fn drop(&mut self) { + unsafe { + self.gpu.device_wait_idle().unwrap(); + + // pipelines + if self.pipeline_render != vk::Pipeline::null() { + self.gpu.destroy_pipeline(self.pipeline_render); + } + + // pipelines layouts + self.gpu + .destroy_pipeline_layout(self.pipeline_layout_render); + + // shaders + self.gpu.destroy_shader_module(self.shader_vertex); + self.gpu.destroy_shader_module(self.shader_fragment); + + // buffers + self.globals_buffer.free_memory_and_destroy(&self.gpu); + self.index_buffer.free_memory_and_destroy(&self.gpu); + self.vertex_buffer.free_memory_and_destroy(&self.gpu); + self.indirect_buffer.free_memory_and_destroy(&self.gpu); + self.instance_buffer.free_memory_and_destroy(&self.gpu); + + // descriptors + for &descriptor_set_layout in self._desc_set_layouts.iter() { + self.gpu + .destroy_descriptor_set_layout(descriptor_set_layout); + } + self.gpu.destroy_descriptor_pool(self._descriptor_pool); + }; + } +} + +impl Task for RenderTerrain { + type Output = RenderSubmit; + type Context = ( + Any, + Any, + Any, + Ref, + Ref, + ); + + fn run( + &mut self, + (spawn_terrain_output, camera, frame, display, world): Self::Context, + ) -> Self::Output { + let globals_uniform = [self.globals_uniform(&camera)]; + unsafe { + self.globals_buffer + .map_and_write_to_device_memory(&self.gpu, 0, &globals_uniform); + } + // verify pipeline + if let Some(surface_version) = display.surface_changed(self.surface_version) { + unsafe { + log::debug!("resize: Surface changed"); + self.gpu.device_wait_idle().unwrap(); + + // rebuild pipelines + if self.pipeline_render.is_null() { + log::debug!("resize: create_graphics_pipelines"); + self.pipeline_render = self.create_graphics_pipelines( + display.render_pass(), + display.surface_resolution(), + )[0]; + } + }; + self.surface_version = surface_version; + } + + // clear for new cycle + self.instance_buffer_data.clear(); + self.indirect_buffer_data.clear(); + + // TODO: to calculate UVs for terrain color samplier we will need to have global offset for + // X and Z to subtract from tile coordinates + + // 1. Mark slots occupied by exiled terrain as free + let mut free_slots = spawn_terrain_output + .tiles_to_exile + .iter() + .map(|i| { + self.tiles_index + .remove(i) + .expect("Terrain index is corrupted") + }) + .collect::>(); + + // 2. Iterate over terrain in the world and store in buffer missing meshes + let vertex_size = MeshLayout::vertex_size(); + + let mut lods_counters = HashMap::::new(); + + for (id, terrain) in world.query::<(&Id, &Terrain)>() { + if !spawn_terrain_output.scene.contains(id) { + log::error!("Garbage terrain from ECS: {:?}", id); + continue; + } + let lod_info = match self.lods_index.get(&terrain.lod) { + Some(lod_info) => lod_info, + None => panic!("No LoD info for {:?}", terrain.lod), + }; + let instance = InstanceUniform { + transform: Transform3D::default().matrix().to_cols_array_2d(), + }; + self.instance_buffer_data.push(instance); + + if !self.tiles_index.contains_key(id) { + let mesh_buffer_size = (vertex_size as u64) * (lod_info.vertices_count as u64); + let slot = self.find_free_slot(&mut free_slots, mesh_buffer_size); + let reuse_slot = slot.is_some(); + let slot = slot.unwrap_or(Slot { + offset: self.vertex_buffer_usage, + size: mesh_buffer_size, + }); + + if !reuse_slot && self.vertex_buffer_usage >= self.vertex_buffer.size { + log::error!( + "tiles: Vertex buffer will overflow by {} bytes of mesh (lod={})!", + mesh_buffer_size, + terrain.lod.value() + ); + } + self.tiles_index.insert(*id, slot); + // write to vertex buffer + let mesh_data = terrain + .mesh + .buffer::() + .expect("Terrain mesh must have required attributes"); + + let counter = lods_counters.entry(terrain.lod).or_insert(0); + + *counter += 1; + let vertex_buffer_usage = unsafe { + self.vertex_buffer.map_and_write_to_device_memory( + &self.gpu, + slot.offset, + mesh_data.as_slice(), + ) + }; + + if !reuse_slot { + self.vertex_buffer_usage += vertex_buffer_usage; + } + } + + let slot = match self.tiles_index.get(id) { + Some(slot) => slot, + None => panic!("Terrain slot information was not found for {id:?}"), + }; + + let instance_number = self.indirect_buffer_data.len() as u32; + self.indirect_buffer_data + .push(vk::DrawIndexedIndirectCommand { + instance_count: 1, + first_instance: instance_number, + first_index: lod_info.first_index, + index_count: lod_info.indices_count, + vertex_offset: (slot.offset / (vertex_size as u64)) as i32, + }); + } + + unsafe { + self.instance_buffer.map_and_write_to_device_memory( + &self.gpu, + 0, + self.instance_buffer_data.as_slice(), + ) + }; + + unsafe { + self.indirect_buffer.map_and_write_to_device_memory( + &self.gpu, + 0, + self.indirect_buffer_data.as_slice(), + ); + }; + // 3. Prepare and execute render pass + + let command_recorder = Recorder { + resolution: frame.resolution, + draw_count: self.indirect_buffer_data.len() as u32, + vertex_buffer: self.vertex_buffer.handle, + index_buffer: self.index_buffer.handle, + indirect_buffer: self.indirect_buffer.handle, + descriptor_sets: self.descriptor_sets.clone(), + pipeline_layout: self.pipeline_layout_render, + pipeline: self.pipeline_render, + }; + + RenderSubmit::new::(Box::new(command_recorder), &self.wait_for) + + /* + unsafe { + self.record_draw_commands(&frame, self.indirect_buffer_data.len() as u32); + } + + CommandBufferSubmitInfo { + wait_semaphores: self.wait_semaphores.clone(), + wait_dst_stage_mask: self + .wait_semaphores + .iter() + .map(|_| vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) + .collect::>(), + command_buffers: vec![self.command_buffer_draw], + signal_semaphores: vec![self.signal_semaphore], + } + */ + } +} + +impl RenderTerrain { + pub fn setup() -> RenderTerrainSetup { + RenderTerrainSetup::default() + } + + pub fn new(display: &mut Display, setup: RenderTerrainSetup) -> Self { + let terrain_tiles_capacity = 4 * setup.tiles_in_view_range * setup.tiles_in_view_range; + + let gpu = display.gpu(); + + let globals_buffer = unsafe { + Self::create_globals_uniform_buffer(&gpu) + .expect("Could not allocate globals uniform buffer") + }; + + let instance_buffer = unsafe { + Self::create_storage_buffer( + &gpu, + (terrain_tiles_capacity as u64) * (std::mem::size_of::() as u64), + ) + .expect("Could not allocate instance storage buffer") + }; + + let indirect_buffer = unsafe { + Self::create_indirect_buffer( + &gpu, + (terrain_tiles_capacity as u64) + * (std::mem::size_of::() as u64), + ) + .expect("Could not allocate indirect buffer") + }; + + let mut index_buffer_data = Vec::new(); + let lods_index = Self::generate_lods_index( + setup.tiles_in_view_range, + setup.lods, + &mut index_buffer_data, + ); + + let vertex_buffer_size = lods_index + .values() + .map(|lod_info| { + (lod_info.vertices_count as u64) + * (lod_info.tiles_count as u64) + * (MeshLayout::vertex_size() as u64) + }) + .sum(); + + let index_buffer = unsafe { + Self::create_index_buffer( + &gpu, + (index_buffer_data.len() * std::mem::size_of::()) as u64, + ) + .expect("Could not allocate index buffer") + }; + // write indices to index buffer + unsafe { + index_buffer.map_and_write_to_device_memory(&gpu, 0, index_buffer_data.as_slice()); + }; + + let vertex_buffer = unsafe { + Self::create_vertex_buffer(&gpu, vertex_buffer_size) + .expect("Could not allocate vertex buffer (non-rigged)") + }; + + // bindings layout + let descriptor_sizes = [ + // Globals + vk::DescriptorPoolSize { + ty: vk::DescriptorType::UNIFORM_BUFFER, + descriptor_count: 1, + }, + // Instances + vk::DescriptorPoolSize { + ty: vk::DescriptorType::STORAGE_BUFFER, + descriptor_count: 1, + }, + ]; + let descriptor_pool_info = vk::DescriptorPoolCreateInfo::default() + .pool_sizes(&descriptor_sizes) + .max_sets(1); + let descriptor_pool = unsafe { gpu.create_descriptor_pool(&descriptor_pool_info).unwrap() }; + + let desc_layout_bindings = [ + // Globals + vk::DescriptorSetLayoutBinding { + binding: 0, + descriptor_type: vk::DescriptorType::UNIFORM_BUFFER, + descriptor_count: 1, + stage_flags: vk::ShaderStageFlags::VERTEX, + ..Default::default() + }, + // Instances + vk::DescriptorSetLayoutBinding { + binding: 1, + descriptor_type: vk::DescriptorType::STORAGE_BUFFER, + descriptor_count: 1, + stage_flags: vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT, + ..Default::default() + }, + ]; + let descriptor_info = + vk::DescriptorSetLayoutCreateInfo::default().bindings(&desc_layout_bindings); + + let desc_set_layouts = + unsafe { [gpu.create_descriptor_set_layout(&descriptor_info).unwrap()] }; + + let desc_alloc_info = vk::DescriptorSetAllocateInfo::default() + .descriptor_pool(descriptor_pool) + .set_layouts(&desc_set_layouts); + let descriptor_sets = unsafe { gpu.allocate_descriptor_sets(&desc_alloc_info).unwrap() }; + + let globals_uniform_buffer_descriptor = vk::DescriptorBufferInfo { + buffer: globals_buffer.handle, + offset: 0, + range: globals_buffer.size, + }; + + let instance_storage_buffer_descriptor = vk::DescriptorBufferInfo { + buffer: instance_buffer.handle, + offset: 0, + range: instance_buffer.size, + }; + + let write_desc_sets = [ + vk::WriteDescriptorSet { + dst_binding: 0, + dst_set: descriptor_sets[0], + descriptor_count: 1, + descriptor_type: vk::DescriptorType::UNIFORM_BUFFER, + p_buffer_info: &globals_uniform_buffer_descriptor, + ..Default::default() + }, + vk::WriteDescriptorSet { + dst_binding: 1, + dst_set: descriptor_sets[0], + descriptor_count: 1, + descriptor_type: vk::DescriptorType::STORAGE_BUFFER, + p_buffer_info: &instance_storage_buffer_descriptor, + ..Default::default() + }, + ]; + + unsafe { + gpu.update_descriptor_sets(&write_desc_sets, &[]); + }; + + // pipeline layout + let pipeline_layout_create_info = + vk::PipelineLayoutCreateInfo::default().set_layouts(&desc_set_layouts); + let pipeline_layout_render = unsafe { + gpu.create_pipeline_layout(&pipeline_layout_create_info) + .expect("Failed to create non-rigged pipeline layout") + }; + + let shader_vertex = unsafe { + Self::load_shader_module(&gpu, include_bytes!("shaders/terrain.vert.spv")) + .expect("Failed to load terrain vertex shader module") + }; + let shader_fragment = unsafe { + Self::load_shader_module(&gpu, include_bytes!("shaders/terrain.frag.spv")) + .expect("Failed to load terrain fragment shader module") + }; + + Self { + wait_for: vec![], + gpu, + surface_version: 0, + globals_buffer, + instance_buffer, + indirect_buffer, + index_buffer, + vertex_buffer, + vertex_buffer_usage: 0, + + descriptor_sets, + _descriptor_pool: descriptor_pool, + _desc_set_layouts: desc_set_layouts, + pipeline_layout_render, + pipeline_render: vk::Pipeline::null(), + shader_vertex, + shader_fragment, + + tiles_index: HashMap::with_capacity(16), + lods_index, + + instance_buffer_data: Vec::with_capacity(terrain_tiles_capacity as usize), + indirect_buffer_data: Vec::with_capacity(terrain_tiles_capacity as usize), + } + } + + fn find_free_slot(&self, slots: &mut Vec, size: u64) -> Option { + if let Some(slot_index) = + slots + .iter() + .enumerate() + .find_map(|(index, slot)| if slot.size == size { Some(index) } else { None }) + { + let slot = slots.remove(slot_index); + return Some(slot); + } + None + } + + fn generate_lods_index( + tiles_in_view_range: u32, + lods: Vec, + index_buffer_data: &mut Vec, + ) -> HashMap { + let mut result = HashMap::with_capacity(lods.len()); + let mut index_buffer_offset = 0; + let mut tiles_per_side_in_lod = 2 * tiles_in_view_range; + for lod_setup in lods.into_iter() { + let max_tiles_of_lod = tiles_per_side_in_lod * tiles_per_side_in_lod; + let tiles_count = if lod_setup.lod.value() != 0 { + let tiles_per_side_in_higher_lod = tiles_per_side_in_lod - 2; + tiles_per_side_in_lod -= 2; + max_tiles_of_lod - tiles_per_side_in_higher_lod * tiles_per_side_in_higher_lod + } else { + max_tiles_of_lod + }; + + let lod_info = LodInfo { + vertices_count: lod_setup.vertices_count, + indices_count: lod_setup.indices.len() as u32, + first_index: (index_buffer_offset / (std::mem::size_of::() as u64)) as u32, + tiles_count, + }; + + index_buffer_data.extend(lod_setup.indices.into_iter()); + index_buffer_offset = + (index_buffer_data.len() as u64) * (std::mem::size_of::() as u64); + result.insert(lod_setup.lod, lod_info); + } + + result + } + + /// Returns Buffer, binded memory and allocated size + unsafe fn create_globals_uniform_buffer(gpu: &Gpu) -> Result { + let buffer_create_info = vk::BufferCreateInfo { + size: std::mem::size_of::() as u64, + usage: vk::BufferUsageFlags::UNIFORM_BUFFER, + sharing_mode: vk::SharingMode::EXCLUSIVE, + ..Default::default() + }; + + Buffer::create_and_allocate(gpu, &buffer_create_info) + } + + unsafe fn create_vertex_buffer(gpu: &Gpu, size: u64) -> Result { + let buffer_create_info = vk::BufferCreateInfo { + size, + usage: vk::BufferUsageFlags::VERTEX_BUFFER, + sharing_mode: vk::SharingMode::EXCLUSIVE, + ..Default::default() + }; + + Buffer::create_and_allocate(gpu, &buffer_create_info) + } + + unsafe fn create_index_buffer(gpu: &Gpu, size: u64) -> Result { + let buffer_create_info = vk::BufferCreateInfo { + size, + usage: vk::BufferUsageFlags::INDEX_BUFFER, + sharing_mode: vk::SharingMode::EXCLUSIVE, + ..Default::default() + }; + + Buffer::create_and_allocate(gpu, &buffer_create_info) + } + + unsafe fn create_storage_buffer(gpu: &Gpu, size: u64) -> Result { + let buffer_create_info = vk::BufferCreateInfo { + size, + usage: vk::BufferUsageFlags::STORAGE_BUFFER, + sharing_mode: vk::SharingMode::EXCLUSIVE, + ..Default::default() + }; + + Buffer::create_and_allocate(gpu, &buffer_create_info) + } + + unsafe fn create_indirect_buffer(gpu: &Gpu, size: u64) -> Result { + let buffer_create_info = vk::BufferCreateInfo { + size, + usage: vk::BufferUsageFlags::INDIRECT_BUFFER, + sharing_mode: vk::SharingMode::EXCLUSIVE, + ..Default::default() + }; + + Buffer::create_and_allocate(gpu, &buffer_create_info) + } + + unsafe fn load_shader_module(gpu: &Gpu, bytes: &[u8]) -> Result { + // let bytes = include_bytes!("shaders/non-rigged.frag.spv"); + let mut cursor = Cursor::new(bytes); + let shader_code = ash::util::read_spv(&mut cursor).expect("Failed to read shader SPV code"); + let shader_module_create_info = vk::ShaderModuleCreateInfo::default().code(&shader_code); + + gpu.create_shader_module(&shader_module_create_info) + } + + unsafe fn create_graphics_pipelines( + &self, + render_pass: vk::RenderPass, + surface_resolution: Extent2D, + ) -> Vec { + let shader_entry_point = c"main"; + + let shader_stages = [ + vk::PipelineShaderStageCreateInfo { + module: self.shader_vertex, + p_name: shader_entry_point.as_ptr(), + stage: vk::ShaderStageFlags::VERTEX, + ..Default::default() + }, + vk::PipelineShaderStageCreateInfo { + s_type: vk::StructureType::PIPELINE_SHADER_STAGE_CREATE_INFO, + module: self.shader_fragment, + p_name: shader_entry_point.as_ptr(), + stage: vk::ShaderStageFlags::FRAGMENT, + ..Default::default() + }, + ]; + + let vertex_input_binding_descriptions = [vk::VertexInputBindingDescription { + binding: 0, + stride: std::mem::size_of::() as u32, + input_rate: vk::VertexInputRate::VERTEX, + }]; + let vertex_input_attribute_descriptions = [ + // position + vk::VertexInputAttributeDescription { + location: 0, + binding: 0, + format: vk::Format::R32G32B32_SFLOAT, + offset: 0, + }, + // normal + vk::VertexInputAttributeDescription { + location: 1, + binding: 0, + format: vk::Format::R32G32B32_SFLOAT, + offset: std::mem::size_of::() as u32, + }, + // moisture + vk::VertexInputAttributeDescription { + location: 2, + binding: 0, + // format: vk::Format::R32G32B32A32_SFLOAT, + format: vk::Format::R32_SFLOAT, + offset: std::mem::size_of::() as u32 + + std::mem::size_of::() as u32, + }, + ]; + + let vertex_input_state_info = vk::PipelineVertexInputStateCreateInfo::default() + .vertex_attribute_descriptions(&vertex_input_attribute_descriptions) + .vertex_binding_descriptions(&vertex_input_binding_descriptions); + let vertex_input_assembly_state_info = vk::PipelineInputAssemblyStateCreateInfo { + topology: vk::PrimitiveTopology::TRIANGLE_LIST, + ..Default::default() + }; + + let viewports = [vk::Viewport { + x: 0.0, + y: 0.0, + width: surface_resolution.width as f32, + height: surface_resolution.height as f32, + min_depth: 0.0, + max_depth: 1.0, + }]; + let scissors = [(vk::Extent2D { + width: surface_resolution.width, + height: surface_resolution.height, + }) + .into()]; + let viewport_state_info = vk::PipelineViewportStateCreateInfo::default() + .scissors(&scissors) + .viewports(&viewports); + + let rasterization_info = vk::PipelineRasterizationStateCreateInfo { + front_face: vk::FrontFace::COUNTER_CLOCKWISE, + line_width: 1.0, + // polygon_mode: vk::PolygonMode::LINE, + polygon_mode: vk::PolygonMode::FILL, + cull_mode: vk::CullModeFlags::FRONT, + ..Default::default() + }; + let multisample_state_info = vk::PipelineMultisampleStateCreateInfo { + rasterization_samples: vk::SampleCountFlags::TYPE_1, + ..Default::default() + }; + let noop_stencil_state = vk::StencilOpState { + // fail_op: vk::StencilOp::KEEP, + // pass_op: vk::StencilOp::KEEP, + // depth_fail_op: vk::StencilOp::KEEP, + // compare_op: vk::CompareOp::ALWAYS, + ..Default::default() + }; + let depth_state_info = vk::PipelineDepthStencilStateCreateInfo { + depth_test_enable: 1, + depth_write_enable: 1, + depth_compare_op: vk::CompareOp::LESS_OR_EQUAL, + front: noop_stencil_state, + back: noop_stencil_state, + max_depth_bounds: 1.0, + ..Default::default() + }; + let color_blend_attachment_states = [vk::PipelineColorBlendAttachmentState { + blend_enable: 0, + src_color_blend_factor: vk::BlendFactor::SRC_COLOR, + dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_DST_COLOR, + color_blend_op: vk::BlendOp::ADD, + src_alpha_blend_factor: vk::BlendFactor::ZERO, + dst_alpha_blend_factor: vk::BlendFactor::ZERO, + alpha_blend_op: vk::BlendOp::ADD, + color_write_mask: vk::ColorComponentFlags::RGBA, + }]; + let color_blend_state = vk::PipelineColorBlendStateCreateInfo::default() + .logic_op(vk::LogicOp::CLEAR) + .attachments(&color_blend_attachment_states); + + let dynamic_state = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]; + let dynamic_state_info = + vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_state); + + let graphic_pipeline_info = vk::GraphicsPipelineCreateInfo::default() + .stages(&shader_stages) + .vertex_input_state(&vertex_input_state_info) + .input_assembly_state(&vertex_input_assembly_state_info) + .viewport_state(&viewport_state_info) + .rasterization_state(&rasterization_info) + .multisample_state(&multisample_state_info) + .depth_stencil_state(&depth_state_info) + .color_blend_state(&color_blend_state) + .dynamic_state(&dynamic_state_info) + .layout(self.pipeline_layout_render) + .render_pass(render_pass); + + self.gpu + .create_graphics_pipelines(vk::PipelineCache::null(), &[graphic_pipeline_info]) + .expect("Failed to create graphics pipelines") + } + + pub fn globals_uniform(&self, camera: &Camera) -> GlobalsUniform { + // let proj = Mat4::perspective_rh(std::f32::consts::FRAC_PI_4, 800.0 / 600.0, 1.0, 10.0); + // let view = Mat4::look_at_rh(Vec3::new(1.5f32, -5.0, 3.0), Vec3::ZERO, Vec3::Z); + GlobalsUniform { + proj: camera.proj.to_cols_array_2d(), + view: camera.view.to_cols_array_2d(), + } + } +} + +pub struct RenderTerrainSetup { + wait_semaphores: Vec, + surface_format: vk::Format, + tile_size: u32, + tiles_in_view_range: u32, + lods: Vec, +} + +impl Default for RenderTerrainSetup { + fn default() -> Self { + Self { + wait_semaphores: Vec::new(), + surface_format: vk::Format::default(), + tile_size: DEFAULT_TILE_SIZE, + tiles_in_view_range: DEFAULT_TILES_IN_VIEW_RANGE, + lods: vec![], + } + } +} + +impl RenderTerrainSetup { + pub fn tile_size(mut self, value: u32) -> Self { + self.tile_size = value; + self + } + + pub fn tiles_in_view_range(mut self, value: u32) -> Self { + self.tiles_in_view_range = value; + self + } + + pub fn wait_semaphores(mut self, semaphores: impl IntoIterator) -> Self { + self.wait_semaphores.extend(semaphores); + self + } + + pub fn surface_format(mut self, surface_format: vk::Format) -> Self { + self.surface_format = surface_format; + self + } + + pub fn lods(mut self, lods: Vec) -> Self { + self.lods = lods; + self + } + + pub fn create(self, display: &mut Display) -> RenderTerrain { + RenderTerrain::new(display, self) + } +} + +#[repr(C)] +#[derive(Clone, Debug, Copy, Default)] +pub struct GlobalsUniform { + /// Projection matrix + pub proj: [[f32; 4]; 4], + /// View matrix + pub view: [[f32; 4]; 4], +} + +#[repr(C)] +#[derive(Clone, Debug, Copy, Default)] +pub struct InstanceUniform { + /// Terrain instance local transform matrix + pub transform: [[f32; 4]; 4], +} + +pub struct Recorder { + resolution: Extent2D, + draw_count: u32, + vertex_buffer: vk::Buffer, + index_buffer: vk::Buffer, + indirect_buffer: vk::Buffer, + pipeline_layout: vk::PipelineLayout, + pipeline: vk::Pipeline, + descriptor_sets: Vec, +} + +impl CommandRecorder for Recorder { + unsafe fn record(&self, gpu: &Gpu, command_buffer: vk::CommandBuffer) { + let viewports = [vk::Viewport { + x: 0.0, + y: 0.0, + width: self.resolution.width as f32, + height: self.resolution.height as f32, + min_depth: 0.0, + max_depth: 1.0, + }]; + let scissors = [(vk::Extent2D { + width: self.resolution.width, + height: self.resolution.height, + }) + .into()]; + + gpu.cmd_bind_descriptor_sets( + command_buffer, + vk::PipelineBindPoint::GRAPHICS, + self.pipeline_layout, + 0, + &self.descriptor_sets[..], + &[], + ); + + // ONLY MESH + if self.draw_count != 0 { + gpu.cmd_bind_pipeline( + command_buffer, + vk::PipelineBindPoint::GRAPHICS, + self.pipeline, + ); + gpu.cmd_set_viewport(command_buffer, 0, &viewports); + gpu.cmd_set_scissor(command_buffer, 0, &scissors); + gpu.cmd_bind_vertex_buffers(command_buffer, 0, &[self.vertex_buffer], &[0]); + + gpu.cmd_bind_index_buffer(command_buffer, self.index_buffer, 0, vk::IndexType::UINT32); + + gpu.cmd_draw_indexed_indirect( + command_buffer, + self.indirect_buffer, + 0, + self.draw_count, + std::mem::size_of::() as u32, + ); + } + } +} diff --git a/src/features/terrain/shaders/terrain.frag b/src/features/terrain/shaders/terrain.frag new file mode 100644 index 00000000..44f599a7 --- /dev/null +++ b/src/features/terrain/shaders/terrain.frag @@ -0,0 +1,71 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout(location = 0) in vec3 world_position; +layout(location = 1) flat in vec3 world_normal; +layout(location = 2) in float vertex_moisture; +layout(location = 0) out vec4 o_frag_color; + +float inverse_lerp(float value, float min_value, float max_value) { + float result = (value - min_value) / (max_value - min_value); + return clamp(result, 0.0, 1.0); +} + +void main() { + vec3 ambient_light = vec3(0.5, 0.5, 0.5); + vec3 light_color = vec3(1.0, 1.0, 1.0); + vec3 light_position = vec3(0.0, 10000.0, 0.0); + + vec3 normal = normalize(world_normal); + vec3 light_direction = normalize(light_position - world_position); + //vec3 light_direction = (normalize(light_position)); + + vec3 diffuse = max(dot(normal, light_direction), 0.0) * light_color; + float min_height = 0.0; + float max_height = 100.0; + const int max_layers = 5; + vec3 colors[max_layers] = vec3[]( + vec3(0.059, 0.533, 0.737), // water + vec3(0.729, 0.714, 0.667), // sand + vec3(0.11, 0.286, 0.118), // grass + vec3(0.275, 0.263, 0.224), // rock + vec3(0.902, 0.937, 0.996) // snow + ); + float limits[max_layers] = float[]( + 0.0, + 0.01, + 0.05, + 0.4, + 0.8 + ); + float blends[max_layers] = float[]( + 0.0, + 0.0, + 0.4, + 0.2, + 0.1 + ); + float moisture_intensity[max_layers] = float[]( + 0.0, + 0.0, + 0.00, + 0.00, + 0.008 + ); + vec3 color = colors[0]; + float height = inverse_lerp(world_position.y, min_height, max_height); + for (int i = 0; i < max_layers; i++) { + if (height < limits[i]) { + break; + } + color = colors[i]; + } + for (int i = 0; i < max_layers; i++) { + float moisture_blend = vertex_moisture * moisture_intensity[i]; + float intensity = inverse_lerp(height - moisture_blend - limits[i], -blends[i] / 2.0 - 0.000001, blends[i] / 2.0); + color = color * (1.0 - intensity) + colors[i] * intensity; + } + o_frag_color = vec4((ambient_light + diffuse), 1.0) * vec4(color, 1.0); + // o_frag_color = vec4(color, 1.0); +} diff --git a/src/features/terrain/shaders/terrain.frag.spv b/src/features/terrain/shaders/terrain.frag.spv new file mode 100644 index 00000000..fed47bfe Binary files /dev/null and b/src/features/terrain/shaders/terrain.frag.spv differ diff --git a/src/features/terrain/shaders/terrain.vert b/src/features/terrain/shaders/terrain.vert new file mode 100644 index 00000000..04581bc2 --- /dev/null +++ b/src/features/terrain/shaders/terrain.vert @@ -0,0 +1,47 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout(location = 0) in vec3 pos; +layout(location = 1) in vec3 normal; +// layout(location = 2) in vec4 color; +layout(location = 2) in float moisture; + +layout(binding = 0) uniform DtxGlobals { + mat4 proj; + mat4 view; +} dtx_globals; + +struct DtxInstance { + mat4 transform; +}; + +layout(std430, binding = 1) buffer DtxInstanceLayout +{ + DtxInstance dtx_instance[]; +}; + +layout(location = 0) out vec3 o_world_position; +layout(location = 1) flat out vec3 o_world_normal; +// layout(location = 2) out vec4 o_color; +layout(location = 2) out float o_moisture; +void main() { + mat4 model_transform = dtx_instance[gl_InstanceIndex].transform; + + mat4 proj_view = dtx_globals.proj * dtx_globals.view; + o_world_position = pos; //vec3(model_transform * vec4(pos, 1.0)); + // o_world_normal = vec3(model_transform * vec4(normalize(normal), 1.0)); + o_world_normal = normalize(normal); + o_moisture = moisture; + + //vec4( + // float((color >> 24) & 0xFF) / 255.0, + // float((color >> 16) & 0xFF) / 255.0, + // float((color >> 8) & 0xFF) / 255.0, + // float(color & 0xFF) / 255.0 + // ); + + // o_color = vec4(0.0, 1.0, 0.0, 1.0); + + gl_Position = proj_view * vec4(o_world_position, 1.0); +} diff --git a/src/features/terrain/shaders/terrain.vert.spv b/src/features/terrain/shaders/terrain.vert.spv new file mode 100644 index 00000000..50e80242 Binary files /dev/null and b/src/features/terrain/shaders/terrain.vert.spv differ diff --git a/src/graphics.rs b/src/graphics.rs index 88fd73d9..3240283a 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -7,8 +7,11 @@ pub use ash::vk; use crate::window; pub use formats::Extent2D; -pub use frame::{CreateFrame, Frame, RenderPass, SubmitFrame}; -pub use vulkan::{Buffer, CommandBufferIter, Display, FramePresenter, Gpu, Semaphore, Surface}; +pub use frame::{CreateFrame, Frame, SubmitFrame}; +pub use vulkan::{ + Buffer, CommandBufferIter, CommandRecorder, Display, FramePresenter, Gpu, RenderSubmit, + Semaphore, Surface, +}; /// GPU device type #[derive(Debug, Clone, Copy, Eq, PartialEq)] diff --git a/src/graphics/frame.rs b/src/graphics/frame.rs index 9f89505e..7f77a23a 100644 --- a/src/graphics/frame.rs +++ b/src/graphics/frame.rs @@ -1,6 +1,8 @@ -use super::{Display, Extent2D, FramePresenter}; +use ash::vk; + +use super::{Display, Extent2D, FramePresenter, Gpu, RenderSubmit}; use crate::log; -use crate::tasks::{All, Any, Mut, OutputChannel, Ref, Task}; +use crate::tasks::{All, Any, Mut, OutputChannel, Ref, Take, Task}; use std::collections::VecDeque; use std::time::{Duration, Instant}; @@ -134,22 +136,309 @@ impl Task for CreateFrame { } /// Task, responsible for frame submition to be presented -#[derive(Default)] -pub struct SubmitFrame {} +pub struct SubmitFrame { + gpu: Gpu, + surface_version: u64, + command_pool: vk::CommandPool, + command_buffer_render: vk::CommandBuffer, + command_buffer_setup: vk::CommandBuffer, + setup_fence: vk::Fence, + framebuffers: Vec, +} + +impl SubmitFrame { + pub fn new(display: &Display) -> Self { + let gpu = display.gpu(); + let pool_create_info = vk::CommandPoolCreateInfo::default() + .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER) + .queue_family_index(gpu.queue_family_index()); + let command_pool = unsafe { + gpu.create_command_pool(&pool_create_info) + .expect("Failed to create a command pool") + }; + + let fence_create_info = + vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); + + let setup_fence = unsafe { gpu.create_fence(&fence_create_info) }; + + let command_buffer_allocate_info = vk::CommandBufferAllocateInfo::default() + .command_buffer_count(2) + .command_pool(command_pool) + .level(vk::CommandBufferLevel::PRIMARY); + + let (command_buffer_render, command_buffer_setup) = unsafe { + gpu.allocate_command_buffers(&command_buffer_allocate_info) + .into() + }; + + Self { + gpu, + command_pool, + command_buffer_render, + command_buffer_setup, + setup_fence, + framebuffers: vec![], + surface_version: 0, + } + } + + unsafe fn create_framebuffers(&mut self, display: &Display) { + let resolution = display.surface_resolution(); + self.framebuffers = display + .swapchain_image_views() + .map(|&present_image_view| { + let framebuffer_attachments = [present_image_view, display.depth_image_view()]; + let frame_buffer_create_info = vk::FramebufferCreateInfo::default() + .render_pass(display.render_pass()) + .attachments(&framebuffer_attachments) + .width(resolution.width) + .height(resolution.height) + .layers(1); + + self.gpu + .create_framebuffer(&frame_buffer_create_info) + .expect("Could not create a framebuffer") + }) + .collect::>() + } + + unsafe fn destroy_framebuffers(&mut self) { + for framebuffer in self.framebuffers.drain(..) { + self.gpu.destroy_framebuffer(framebuffer); + } + } + + unsafe fn setup_depth_image(&self, display: &Display) { + let depth_image = display.depth_image(); + + // begin: prepare + + self.gpu + .wait_for_fences(&[self.setup_fence], true, u64::MAX) + .expect("Wait for fence failed."); + + self.gpu + .reset_fences(&[self.setup_fence]) + .expect("Reset fences failed."); + + self.gpu + .reset_command_buffer( + self.command_buffer_setup, + vk::CommandBufferResetFlags::RELEASE_RESOURCES, + ) + .expect("Reset command buffer failed."); + + let command_buffer_begin_info = vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); + + self.gpu + .begin_command_buffer(self.command_buffer_setup, &command_buffer_begin_info) + .expect("Begin commandbuffer"); + + // end: prepare + + let layout_transition_barriers = vk::ImageMemoryBarrier::default() + .image(depth_image) + .dst_access_mask( + vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ + | vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE, + ) + .new_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL) + .old_layout(vk::ImageLayout::UNDEFINED) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::DEPTH) + .layer_count(1) + .level_count(1), + ); + + self.gpu.cmd_pipeline_barrier( + self.command_buffer_setup, + vk::PipelineStageFlags::BOTTOM_OF_PIPE, + vk::PipelineStageFlags::LATE_FRAGMENT_TESTS, + vk::DependencyFlags::empty(), + &[], + &[], + &[layout_transition_barriers], + ); + + // submit + self.gpu + .end_command_buffer(self.command_buffer_setup) + .expect("End commandbuffer"); + + let command_buffers = [self.command_buffer_setup]; + let wait_mask = []; + let wait_semaphores = []; + let signal_semaphores = []; + + let submits = [vk::SubmitInfo::default() + .wait_semaphores(&wait_semaphores) + .wait_dst_stage_mask(&wait_mask) + .command_buffers(&command_buffers) + .signal_semaphores(&signal_semaphores)]; + + self.gpu + .submit_queue(&submits, self.setup_fence) + .expect("queue submit failed.") + } +} + +impl Drop for SubmitFrame { + fn drop(&mut self) { + unsafe { + self.gpu.device_wait_idle().unwrap(); + // framebuffers + self.destroy_framebuffers(); + // command buffers + self.gpu.destroy_command_pool(self.command_pool); + // destory fence + self.gpu.destroy_fence(self.setup_fence) + }; + } +} impl Task for SubmitFrame { - type Context = (Ref, Any, All); + type Context = (Ref, Any, Take>); type Output = FramePresenter; fn output_channel(&self) -> OutputChannel { OutputChannel::Scheduler } - fn run(&mut self, (display, frame, _): Self::Context) -> Self::Output { + fn run(&mut self, (display, frame, submits): Self::Context) -> Self::Output { log::info!("get presenter"); + + if let Some(surface_version) = display.surface_changed(self.surface_version) { + unsafe { + log::debug!("resize: Surface changed"); + // self.gpu.device_wait_idle().unwrap(); + + // rebuild framebuffers + log::debug!("resize: destroy_framebuffers"); + self.destroy_framebuffers(); + + log::debug!("resize: create_framebuffers"); + self.create_framebuffers(&display); + + log::debug!("resize: setup_depth_image"); + self.setup_depth_image(&display); + } + self.surface_version = surface_version; + } + + let mut submits = submits.take(); + + submits.sort_by(|a, b| { + let a_deps = a.wait_for(); + let b_deps = b.wait_for(); + let a_depends_on_b = a_deps.iter().any(|i| *i == b.id()); + let b_depends_on_a = b_deps.iter().any(|i| *i == a.id()); + + if a_depends_on_b && b_depends_on_a { + panic!("Circular rendering dependencies"); + } + + if a_depends_on_b && !b_depends_on_a { + return std::cmp::Ordering::Greater; + } + + if !a_depends_on_b && b_depends_on_a { + return std::cmp::Ordering::Less; + } + + a_deps.len().cmp(&b_deps.len()) + }); + /* + .iter() + .map(|i| { + vk::SubmitInfo::default() + .wait_semaphores(i.wait_semaphores.as_slice()) + .wait_dst_stage_mask(i.wait_dst_stage_mask.as_slice()) + .command_buffers(i.command_buffers.as_slice()) + .signal_semaphores(i.signal_semaphores.as_slice()) + }) + .collect::>(); + */ + + let draw_fence = display.draw_fence(); + + let command_buffer_begin_info = vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); + + let clear_values = [ + vk::ClearValue { + color: vk::ClearColorValue { + float32: [0.0, 0.0, 0.1, 0.0], + }, + }, + vk::ClearValue { + depth_stencil: vk::ClearDepthStencilValue { + depth: 1.0, + stencil: 0, + }, + }, + ]; + + let render_pass = unsafe { display.render_pass() }; + let render_pass_begin_info = vk::RenderPassBeginInfo::default() + .render_pass(render_pass) + .framebuffer(self.framebuffers[frame.swapchain_index as usize]) + .render_area(vk::Rect2D { + offset: vk::Offset2D { x: 0, y: 0 }, + extent: vk::Extent2D { + width: frame.resolution.width, + height: frame.resolution.height, + }, + }) + .clear_values(&clear_values); + + unsafe { + self.gpu + .reset_command_buffer( + self.command_buffer_render, + vk::CommandBufferResetFlags::RELEASE_RESOURCES, + ) + .expect("Failed to reset Vulkan command buffer"); + + self.gpu + .begin_command_buffer(self.command_buffer_render, &command_buffer_begin_info) + .expect("Failed to begin draw command buffer"); + + self.gpu.cmd_begin_render_pass( + self.command_buffer_render, + &render_pass_begin_info, + vk::SubpassContents::INLINE, + ); + + for submit in submits.into_iter() { + submit.record_command_buffer(&self.gpu, self.command_buffer_render); + } + + self.gpu.cmd_end_render_pass(self.command_buffer_render); + + self.gpu + .end_command_buffer(self.command_buffer_render) + .expect("End commandbuffer"); + + let present_complete_semaphore = display.present_complete_semaphore(); + let render_complete_semaphore = display.render_complete_semaphore(); + let wait_semaphores = [present_complete_semaphore]; + let wait_mask = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]; + let command_buffers = [self.command_buffer_render]; + let signal_semaphores = [render_complete_semaphore]; + + let submit_info = [vk::SubmitInfo::default() + .wait_semaphores(&wait_semaphores) + .wait_dst_stage_mask(&wait_mask) + .command_buffers(&command_buffers) + .signal_semaphores(&signal_semaphores)]; + + self.gpu + .submit_queue(&submit_info, draw_fence) + .expect("Failed to submit draw buffer to queue"); + } display.presenter(frame.swapchain_index) } } - -/// Render Pass Output -pub struct RenderPass {} diff --git a/src/graphics/vulkan.rs b/src/graphics/vulkan.rs index 8df31b91..7aba1179 100644 --- a/src/graphics/vulkan.rs +++ b/src/graphics/vulkan.rs @@ -3,7 +3,6 @@ use std::ffi::{c_char, CStr, CString}; use std::sync::Arc; pub use ash::vk; -use ash::vk::DescriptorPool; use crate::log; use crate::window; @@ -787,7 +786,7 @@ impl Gpu { pub unsafe fn create_descriptor_pool( &self, descriptor_pool_info: &vk::DescriptorPoolCreateInfo, - ) -> Result { + ) -> Result { self.device .vk_device .create_descriptor_pool(descriptor_pool_info, None) @@ -1151,6 +1150,7 @@ impl From for (vk::CommandBuffer, vk::CommandBuffer, vk::Comm /// Display abstraction layer pub struct Display { gpu: Gpu, + draw_fence: vk::Fence, swapchain: Arc, surface: Surface, window: window::Instance, @@ -1159,6 +1159,7 @@ pub struct Display { depth_image: vk::Image, depth_image_view: vk::ImageView, depth_image_memory: vk::DeviceMemory, + render_pass: vk::RenderPass, } impl Drop for Display { @@ -1174,10 +1175,16 @@ impl Drop for Display { self.depth_image_memory, ); self.gpu.destroy_semaphore(self.present_complete_semaphore); + self.gpu.destroy_semaphore(self.render_complete_semaphore); // NOTE: render_complete_semaphore is not owned + self.gpu.destroy_fence(self.draw_fence); + Swapchain::destroy(&self.swapchain, &self.gpu); + // render pass + self.gpu.destroy_render_pass(self.render_pass); + self.surface .loader .destroy_surface(self.surface.vk_surface, None); @@ -1229,6 +1236,7 @@ impl Display { let features = vk::PhysicalDeviceFeatures { shader_clip_distance: 1, vertex_pipeline_stores_and_atomics: 1, + multi_draw_indirect: 1, ..Default::default() }; let priorities = [1.0]; @@ -1278,19 +1286,81 @@ impl Display { .expect("Failed to create completion semaphore") }; + let render_complete_sempahore_create_info = vk::SemaphoreCreateInfo::default(); + let render_complete_semaphore = unsafe { + gpu.create_semaphore(&render_complete_sempahore_create_info) + .expect("Failed to create completion semaphore") + }; + let (depth_image, depth_image_view, depth_image_memory) = unsafe { Self::create_depth_image(&gpu, surface.vk_surface_resolution) }; + let fence_create_info = + vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); + let draw_fence = unsafe { gpu.create_fence(&fence_create_info) }; + + let renderpass_attachments = [ + vk::AttachmentDescription { + format: surface.vk_surface_format.format, + samples: vk::SampleCountFlags::TYPE_1, + load_op: vk::AttachmentLoadOp::CLEAR, + store_op: vk::AttachmentStoreOp::STORE, + final_layout: vk::ImageLayout::PRESENT_SRC_KHR, + ..Default::default() + }, + vk::AttachmentDescription { + format: vk::Format::D16_UNORM, + samples: vk::SampleCountFlags::TYPE_1, + load_op: vk::AttachmentLoadOp::CLEAR, + initial_layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + final_layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + ..Default::default() + }, + ]; + let color_attachment_refs = [vk::AttachmentReference { + attachment: 0, + layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + }]; + let depth_attachment_ref = vk::AttachmentReference { + attachment: 1, + layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }; + let dependencies = [vk::SubpassDependency { + src_subpass: vk::SUBPASS_EXTERNAL, + src_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, + dst_access_mask: vk::AccessFlags::COLOR_ATTACHMENT_READ + | vk::AccessFlags::COLOR_ATTACHMENT_WRITE, + dst_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, + ..Default::default() + }]; + + let subpass = vk::SubpassDescription::default() + .color_attachments(&color_attachment_refs) + .depth_stencil_attachment(&depth_attachment_ref) + .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS); + + let renderpass_create_info = vk::RenderPassCreateInfo::default() + .attachments(&renderpass_attachments) + .subpasses(std::slice::from_ref(&subpass)) + .dependencies(&dependencies); + + let render_pass = unsafe { + gpu.create_render_pass(&renderpass_create_info) + .expect("Failed to create a render pass") + }; + Self { gpu, surface, window, swapchain: Arc::new(swapchain), present_complete_semaphore, - render_complete_semaphore: vk::Semaphore::null(), + render_complete_semaphore, + draw_fence, depth_image, depth_image_view, depth_image_memory, + render_pass, } } @@ -1398,11 +1468,32 @@ impl Display { self.depth_image } + /// # Safety + /// + /// This function requires valid Vulkan entities + pub unsafe fn render_pass(&self) -> vk::RenderPass { + self.render_pass + } + pub fn gpu(&self) -> Gpu { self.gpu.clone() } + pub fn draw_fence(&self) -> vk::Fence { + self.draw_fence + } + pub fn next_frame(&self) -> u32 { + unsafe { + self.gpu + .wait_for_fences(&[self.draw_fence], true, u64::MAX) + .expect("Failed to wait for the draw fence"); + + self.gpu + .reset_fences(&[self.draw_fence]) + .expect("Failed to reset draw fences"); + }; + let (present_index, is_suboptimal) = unsafe { log::debug!("Begin acquire image"); self.swapchain @@ -1493,15 +1584,11 @@ impl Display { FramePresenter { swapchain: Arc::clone(&self.swapchain), device: Arc::clone(&self.gpu.device), - render_complete_semaphore: self.render_complete_semaphore, swapchain_index, + wait_semaphore: self.render_complete_semaphore, } } - pub fn set_render_complete_semaphore(&mut self, semaphore: vk::Semaphore) { - self.render_complete_semaphore = semaphore; - } - /// # Safety /// /// This function requires valid Vulkan entities @@ -1509,6 +1596,13 @@ impl Display { self.present_complete_semaphore } + /// # Safety + /// + /// This function requires valid Vulkan entities + pub unsafe fn render_complete_semaphore(&self) -> vk::Semaphore { + self.render_complete_semaphore + } + /// # Safety /// /// This function requires valid Vulkan entities @@ -1633,187 +1727,80 @@ impl Display { }) .expect("Could not find a device that fulfill requirements") } +} - /* - pub fn present(&self) { - let present_index = self.next_frame(); - log::info!("present_index {}", present_index); - let clear_values = vec![ - vk::ClearValue { - color: vk::ClearColorValue { - float32: [0.1, 1.0, 1.0, 0.0], - }, - }, - //vk::ClearValue { - // depth_stencil: vk::ClearDepthStencilValue { - // depth: 1.0, - // stencil: 0, - // }, - //}, - ]; +impl<'a> From<&'a Display> for &'a Device { + fn from(display: &Display) -> &Device { + &display.gpu.device + } +} - // these array must live long enough or vulkan will sigsegv - let wait_semaphores = [self.vk_present_complete_semaphore]; - let signal_semaphores = [self.vk_render_complete_semaphore]; - let command_buffers = [self.vk_draw_command_buffer]; - - log::info!("render_pass_begin_info"); - let render_pass_begin_info = vk::RenderPassBeginInfo::builder() - .render_pass(self.renderpass) - .framebuffer(self.framebuffers[present_index as usize]) - .render_area(self.surface.vk_resolution.into()) - .clear_values(&clear_values) - .build(); - - log::info!("begin submit"); - // begin submit - unsafe { - self.vk_device - .wait_for_fences(&[self.vk_draw_commands_reuse_fence], true, u64::MAX) - .expect("Wait for fence failed."); - - log::info!("wait_for_fences"); - self.vk_device - .reset_fences(&[self.vk_draw_commands_reuse_fence]) - .expect("Reset fences failed."); - - log::info!("reset_fences"); - self.vk_device - .reset_command_buffer( - self.vk_draw_command_buffer, - vk::CommandBufferResetFlags::RELEASE_RESOURCES, - ) - .expect("Reset command buffer failed."); - - log::info!("reset_command_buffer"); - let command_buffer_begin_info = vk::CommandBufferBeginInfo::builder() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT) - .build(); - - log::info!("command_buffer_begin_info"); - self.vk_device - .begin_command_buffer(self.vk_draw_command_buffer, &command_buffer_begin_info) - .expect("Begin commandbuffer"); - log::info!("begin_command_buffer"); - } - // ------------ - unsafe { - self.vk_device.cmd_begin_render_pass( - self.vk_draw_command_buffer, - &render_pass_begin_info, - vk::SubpassContents::INLINE, - ); - log::info!("cmd_begin_render_pass"); - self.vk_device - .cmd_end_render_pass(self.vk_draw_command_buffer); - } - log::info!("cmd_end_render_pass"); - // ------------ - unsafe { - self.vk_device - .end_command_buffer(self.vk_draw_command_buffer) - .expect("End commandbuffer"); - log::info!("end_command_buffer"); - - let submit_info = vk::SubmitInfo::builder() - .wait_semaphores(&wait_semaphores) - .wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]) - .command_buffers(&command_buffers) - .signal_semaphores(&signal_semaphores) - .build(); - log::info!("submit_info {:?}", self.vk_queue); - - self.vk_device - .queue_submit( - self.vk_queue, - &[submit_info], - self.vk_draw_commands_reuse_fence, - ) - .expect("queue submit failed."); - log::info!("queue_submit"); - } - // end submit +pub trait CommandRecorder: Send + Sync { + /// # Safety + /// + /// Requires valid Vulkan entities + unsafe fn record(&self, gpu: &Gpu, command_buffer: vk::CommandBuffer); +} - let wait_semaphores = [self.vk_render_complete_semaphore]; - let swapchains = [self.swapchain.vk_swapchain]; - let image_indices = [present_index]; - let present_info = vk::PresentInfoKHR::builder() - .wait_semaphores(&[self.vk_render_complete_semaphore]) - .swapchains(&swapchains) - .image_indices(&image_indices) - .build(); - log::info!("present_info"); +pub struct RenderSubmit { + /// Id of submitting task + id: std::any::TypeId, + /// dependencied of other tasks + dependencies: Vec, + /// command recorder + command_recorder: Box, +} - unsafe { - self.swapchain - .loader - .queue_present(self.vk_queue, &present_info) - .unwrap(); +impl RenderSubmit { + pub fn new( + command_recorder: Box, + dependencies: &[std::any::TypeId], + ) -> Self { + Self { + id: std::any::TypeId::of::(), + dependencies: dependencies.into(), + command_recorder, } - log::info!("queue_present"); } - */ - - /* - unsafe fn create_framebuffers( - device: &ash::Device, - surface: &Surface, - swapchain: &Swapchain, - renderpass: &vk::RenderPass, - ) -> Vec { - swapchain - .vk_present_image_views - .iter() - .map(|&present_image_view| { - let framebuffer_attachments = [present_image_view /*, base.depth_image_view*/]; - let frame_buffer_create_info = vk::FramebufferCreateInfo::builder() - .render_pass(*renderpass) - .attachments(&framebuffer_attachments) - .width(surface.vk_resolution.width) - .height(surface.vk_resolution.height) - .layers(1) - .build(); - device - .create_framebuffer(&frame_buffer_create_info, None) - .expect("Could not create a framebuffer") - }) - .collect::>() + pub fn id(&self) -> std::any::TypeId { + self.id } - unsafe fn destroy_framebuffers(&mut self) { - for framebuffer in self.framebuffers.iter() { - self.vk_device.destroy_framebuffer(*framebuffer, None); - } - self.framebuffers.clear(); + pub fn wait_for(&self) -> &[std::any::TypeId] { + self.dependencies.as_slice() } - */ -} -impl<'a> From<&'a Display> for &'a Device { - fn from(display: &Display) -> &Device { - &display.gpu.device + /// # Safety + /// + /// Requires valid Vulkan entities + pub unsafe fn record_command_buffer(&self, gpu: &Gpu, command_buffer: vk::CommandBuffer) { + self.command_recorder.record(gpu, command_buffer); } } pub struct FramePresenter { swapchain: Arc, device: Arc, - render_complete_semaphore: vk::Semaphore, + wait_semaphore: vk::Semaphore, swapchain_index: u32, } impl FramePresenter { pub fn present(self) { - let wait_semaphores = [self.render_complete_semaphore]; let swapchains = [self.swapchain.as_ref().vk_swapchain]; let image_indices = [self.swapchain_index]; + let wait_semaphores = [self.wait_semaphore]; let present_info = vk::PresentInfoKHR::default() .wait_semaphores(&wait_semaphores) .swapchains(&swapchains) .image_indices(&image_indices); - log::debug!("Begin present: {}", self.swapchain_index); + log::debug!( + "Begin present: {} ({} wait semaphores)", + self.swapchain_index, + wait_semaphores.len() + ); unsafe { let r = self @@ -1826,90 +1813,6 @@ impl FramePresenter { } } -/* -pub struct CommandRecorder<'a> { - gpu: &'a Gpu, - command_buffer: vk::CommandBuffer, - one_time_submit: bool, -} - -impl<'a> Drop for CommandRecorder<'a> { - fn drop(&mut self) { - if self.one_time_submit { - unsafe { - self.gpu.end_command_buffer(self.command_buffer); - } - } - } -} - -impl<'a> CommandRecorder<'a> { - pub fn setup() -> CommandRecorderSetup { - CommandRecorderSetup { - one_time_submit: true, - ..Default::default() - } - } - - -} - -#[derive(Default)] -pub struct CommandRecorderSetup { - pub command_buffer: vk::CommandBuffer, - pub reuse_fence: Option, - pub one_time_submit: bool, -} - -impl CommandRecorderSetup { - #[inline(always)] - pub fn command_buffer(mut self, command_buffer: vk::CommandBuffer) -> Self { - self.command_buffer = command_buffer; - self - } - #[inline(always)] - pub fn reuse_fence(mut self, reuse_fence: Option) -> Self { - self.reuse_fence = reuse_fence; - self - } - #[inline(always)] - pub fn one_time_submit(mut self, one_time_submit: bool) -> Self { - self.one_time_submit = one_time_submit; - self - } - - pub fn create<'a>(self, gpu: &'a Gpu) -> CommandRecorder<'a> { - unsafe { - if let Some(reuse_fence) = self.reuse_fence { - gpu.wait_for_fences(&[reuse_fence], true, u64::MAX) - .expect("Failed to wait for Vulkan fences"); - gpu.reset_fences(&[reuse_fence]) - .expect("Failed to reset Vulkan fences"); - } - - if self.one_time_submit { - let command_buffer_begin_info = vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); - - gpu.reset_command_buffer( - self.command_buffer, - vk::CommandBufferResetFlags::RELEASE_RESOURCES, - ) - .expect("Failed to reset Vulkan command buffer"); - - gpu.begin_command_buffer(self.command_buffer, &command_buffer_begin_info); - } - } - - CommandRecorder { - gpu, - command_buffer: self.command_buffer, - one_time_submit: self.one_time_submit, - } - } -} -*/ - pub struct Buffer { pub handle: vk::Buffer, pub device_memory: vk::DeviceMemory, @@ -1962,7 +1865,7 @@ impl Buffer { data: &[T], ) -> u64 { let align = std::mem::align_of::() as u64; - let size = (data.len() * std::mem::size_of::()) as u64; + let size = std::mem::size_of_val(data) as u64; log::debug!("map buffer: align({:?}), size({})", align, size); @@ -1991,83 +1894,6 @@ impl Buffer { } } -/* - -pub struct PipelineLayout { - vk_pipeline_layout: vk::PipelineLayout, - device: Arc, -} - -#[derive(Default)] -pub struct PipelineLayoutSetup<'a> { - vk_layout_create_info: vk::PipelineLayoutCreateInfo<'a>, -} - -impl PipelineLayout { - pub fn setup<'a>() -> PipelineLayoutSetup<'a> { - PipelineLayoutSetup::default() - } -} - -impl Drop for PipelineLayout { - fn drop(&mut self) { - unsafe { - self.device - .vk_device - .destroy_pipeline_layout(self.vk_pipeline_layout, None); - }; - } -} - -pub struct GraphicsPipeline { - vk_pipeline: vk::Pipeline, - device: Arc, -} - -impl GraphicsPipeline { - pub fn setup<'a>() -> GraphicsPipelineSetup<'a> { - GraphicsPipelineSetup::default() - } -} - -#[derive(Default)] -pub struct GraphicsPipelineSetup<'a> { - vk_graphics_pipline_create_info: vk::GraphicsPipelineCreateInfo<'a>, -} - -impl<'a> GraphicsPipelineSetup<'a> { - pub fn create(self, gpu: &Gpu) -> GraphicsPipeline { - gpu.create_graphics_pipeline(self.vk_graphics_pipline_create_info) - } -} - -impl Drop for GraphicsPipeline { - fn drop(&mut self) { - unsafe { - self.device - .vk_device - .destroy_pipeline(self.vk_pipeline, None); - }; - } -} - -pub struct ShaderModule { - device: Arc, - vk_shader_module: vk::ShaderModule, -} - -impl Drop for ShaderModule { - fn drop(&mut self) { - unsafe { - self.device - .vk_device - .destroy_shader_module(self.vk_shader_module, None); - }; - } -} - - */ - /// # Safety /// /// This function requires valid Vulkan entities diff --git a/src/lib.rs b/src/lib.rs index 910c6659..f9cccf27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,9 @@ pub use crate::log::Log; /// Math module pub mod math; +mod features; +pub use features::*; + /// Models abstractions pub mod models; pub use models::{ @@ -83,9 +86,6 @@ pub use window::{Input, ReadInput, Window}; // #[cfg(feature = "pbr")] // pub use dotrix_pbr as pbr; -// #[cfg(feature = "ui")] -// pub use dotrix_ui as ui; - /// Dotrix Settings pub trait Application { /// Startup diff --git a/src/loaders/gltf_loader.rs b/src/loaders/gltf_loader.rs index 1d5fb433..587c59e3 100644 --- a/src/loaders/gltf_loader.rs +++ b/src/loaders/gltf_loader.rs @@ -45,7 +45,10 @@ pub struct GltfLoader; impl ResourceLoader for GltfLoader { fn read(&self, path: &Path, targets: &HashSet) -> ResourceBundle { - let mut file = File::open(path).expect("Could not open GLTF resource file"); + let mut file = match File::open(path) { + Ok(file) => file, + Err(err) => panic!("Could not open GLTF resource file ({path:?}): {err:?}",), + }; let metadata = std::fs::metadata(path).expect("Could not read GLTF file metadata"); let mut data = vec![0; metadata.len() as usize]; file.read_exact(&mut data) @@ -75,7 +78,7 @@ impl ResourceLoader for GltfLoader { } } } - Err(e) => log::error!("Could not read GLTF file `{:?}`: {:?}", path, e), + Err(err) => log::error!("Could not read GLTF file `{path:?}`: {err:?}"), }; let mut bundle = targets @@ -121,16 +124,16 @@ impl GltfLoader { if let Some(stripped) = uri.strip_prefix(URI_BASE64) { match base64_decode(stripped) { Ok(buffer) => buffers.push(buffer), - Err(e) => { - log::error!("Could not decode Base64 buffer: {:?}", e); + Err(err) => { + log::error!("Could not decode Base64 buffer: {err:?}"); return None; } }; } else { match std::fs::read(path.parent().unwrap().join(uri)) { Ok(buffer) => buffers.push(buffer), - Err(e) => { - log::error!("Could not read GLTF buffer from file: {:?}", e); + Err(err) => { + log::error!("Could not read GLTF buffer from file: {err:?}"); return None; } }; @@ -271,7 +274,7 @@ impl GltfLoader { let mode = primitive.mode(); if mode != gltf::mesh::Mode::Triangles { - log::error!("Unsupported topology: {:?}", mode); + log::error!("Unsupported topology: {mode:?}"); return; }; @@ -391,8 +394,8 @@ impl GltfLoader { match base64_decode(&uri[URI_IMAGE_PNG.len()..]) { Ok(data) => (data, ImageFormat::Png), - Err(e) => { - log::error!("Could not decode texture data: {:?}", e); + Err(err) => { + log::error!("Could not decode texture data: {err:?}"); return Id::default(); } } @@ -400,7 +403,7 @@ impl GltfLoader { gltf::image::Source::View { view, mime_type } => { if mime_type != "image/png" { - log::warn!("Unsupported mime: {}", mime_type); + log::warn!("Unsupported mime: {mime_type}"); return Id::default(); } @@ -433,9 +436,9 @@ impl GltfLoader { let asset_name = animation .name() .map(|animation_name| [name, animation_name].join("::")) - .unwrap_or_else(|| format!("{}::animation[{}]", name, animation.index())); + .unwrap_or_else(|| format!("{name}::animation[{}]", animation.index())); - log::info!("importing animation as `{}`", asset_name); + log::info!("importing animation as `{asset_name}`"); let mut asset = Animation::new(asset_name); @@ -478,9 +481,8 @@ impl GltfLoader { }; } else { log::warn!( - "Animation {} refers target joint ({}), that does not exist", - asset.name(), - index + "Animation {} refers target joint ({index}), that does not exist", + asset.name() ); } } diff --git a/src/loaders/image_loader.rs b/src/loaders/image_loader.rs index 36c8cd59..8d24c431 100644 --- a/src/loaders/image_loader.rs +++ b/src/loaders/image_loader.rs @@ -23,7 +23,10 @@ impl ResourceLoader for ImageLoader { .and_then(|n| n.to_str()) .expect("Could not get file name from its path"); - let mut file = File::open(path).expect("Could not open Image resource file"); + let mut file = match File::open(path) { + Ok(file) => file, + Err(err) => panic!("Could not open image resource file ({path:?}): {err:?}"), + }; let metadata = std::fs::metadata(path).expect("Could not read Image file metadata"); let mut data = vec![0; metadata.len() as usize]; file.read_exact(&mut data) @@ -60,10 +63,11 @@ impl ImageLoader { ) -> Option { match image::load_from_memory_with_format(data, format) { Ok(img) => { - let img = img.into_rgba8(); - let (width, height) = img.dimensions(); - let resolution = Extent2D { width, height }; - Some(Image::new(name.into(), resolution, img.into_vec())) + let resolution = Extent2D { + width: img.width(), + height: img.height(), + }; + Some(Image::new(name.into(), resolution, img.into_bytes())) } Err(e) => { log::error!("Could not read image from buffer: {:?}", e); diff --git a/src/models/animations.rs b/src/models/animations.rs index 59b55965..fbf7d5ab 100644 --- a/src/models/animations.rs +++ b/src/models/animations.rs @@ -339,7 +339,7 @@ impl AnimationPlayer { } AnimationState::Loop(current) => { let mut new_duration = current + delta.mul_f32(self.speed); - if !(new_duration < duration) { + if new_duration >= duration { new_duration = Duration::from_secs_f32( new_duration.as_secs_f32() % duration.as_secs_f32(), ); diff --git a/src/models/colors.rs b/src/models/colors.rs index 5fd11019..609f8422 100644 --- a/src/models/colors.rs +++ b/src/models/colors.rs @@ -141,6 +141,17 @@ impl From<&Color> for [f32; 3] { } } +impl From<&Color> for Color { + fn from(color: &Color) -> Self { + Color { + r: (color.r as f32) / 255.0, + g: (color.g as f32) / 255.0, + b: (color.b as f32) / 255.0, + a: (color.a as f32) / 255.0, + } + } +} + impl VertexAttribute for Color { type Raw = u32; fn name() -> &'static str { @@ -170,3 +181,14 @@ impl From for Color { } } } + +impl From<[u8; 3]> for Color { + fn from(color: [u8; 3]) -> Self { + Self { + r: color[0], + g: color[1], + b: color[2], + a: 0xFF, + } + } +} diff --git a/src/models/renderer.rs b/src/models/renderer.rs index 19b09e61..022883e8 100644 --- a/src/models/renderer.rs +++ b/src/models/renderer.rs @@ -1,9 +1,8 @@ use std::collections::HashMap; -use std::ffi::CStr; use std::io::Cursor; -use crate::graphics::vk; -use crate::graphics::{Buffer, RenderPass}; +use crate::graphics::{vk, CommandRecorder}; +use crate::graphics::{Buffer, RenderSubmit}; use crate::loaders::Assets; use crate::models::materials::MAX_MATERIAL_IMAGES; use crate::utils::Id; @@ -19,8 +18,8 @@ use super::{ #[derive(Clone, Copy)] pub struct LayoutInBuffer { - /// Offset inside of the buffer in bytes - pub offset: u64, + // /// offset in bytes + // pub offset: u32, /// Offset of the first item (vertex or index) pub base: u32, /// Number of items (vertices or indices) @@ -46,24 +45,12 @@ pub struct DrawCount { pub struct RenderModels { /// GPU instance gpu: Gpu, - /// Wait for these semaphores before executing command buffers - wait_semaphores: Vec, - /// Signal these semaphores after rendering is done - signal_semaphore: vk::Semaphore, /// Command Pool command_pool: vk::CommandPool, /// Setup command buffer command_buffer_setup: vk::CommandBuffer, /// Setup command buffer reuse fence command_buffer_setup_reuse_fence: vk::Fence, - /// Draw command buffer - command_buffer_draw: vk::CommandBuffer, - /// Draw command buffer reuse fence - command_buffer_draw_reuse_fence: vk::Fence, - /// Framebuffers - framebuffers: Vec, - /// Render pass - render_pass: vk::RenderPass, /// Version of surface to track changes and update framebuffers and fender pass surface_version: u64, /// Index of instances by mesh (just mesh, indexed) @@ -115,8 +102,6 @@ pub struct RenderModels { material_layer_size: Extent2D, /// Material layer index in the material_image material_layer_index: HashMap, usize>, - /// Usage of layers in material image buffer - material_layer_usage: u32, /// Mesh layouts of non-rigged models mesh_registry: HashMap, MeshLayout>, /// descriptor sets @@ -201,22 +186,12 @@ impl Drop for RenderModels { } self.gpu.destroy_descriptor_pool(self.descriptor_pool); - // framebuffers - self.destroy_framebuffers(); - - // render pass - self.gpu.destroy_render_pass(self.render_pass); - // command buffers self.gpu.destroy_command_pool(self.command_pool); // fences self.gpu .destroy_fence(self.command_buffer_setup_reuse_fence); - self.gpu.destroy_fence(self.command_buffer_draw_reuse_fence); - - // semaphores - self.gpu.destroy_semaphore(self.signal_semaphore); } } } @@ -229,31 +204,24 @@ impl Task for RenderModels { Ref, Ref, ); - type Output = RenderPass; + type Output = RenderSubmit; fn run(&mut self, (frame, camera, assets, display, world): Self::Context) -> Self::Output { - log::debug!("pbr: begin"); - if let Some(surface_version) = display.surface_changed(self.surface_version) { unsafe { log::debug!("resize: Surface changed"); self.gpu.device_wait_idle().unwrap(); - // rebuild framebuffers - - log::debug!("resize: destroy_framebuffers"); - self.destroy_framebuffers(); - - log::debug!("resize: create_framebuffers"); - self.create_framebuffers(&display, self.render_pass); - // rebuild pipelines if self.pipeline_render_only_mesh == vk::Pipeline::null() { log::debug!("resize: destroy_graphics_pipelines"); + // NOTE: WHAT ARE WE DESTROYING HERE??? self.destroy_graphics_pipelines(); log::debug!("resize: create_graphics_pipelines"); - let graphic_pipelines = - self.create_graphics_pipelines(display.surface_resolution()); + let graphic_pipelines = self.create_graphics_pipelines( + display.render_pass(), + display.surface_resolution(), + ); self.pipeline_render_only_mesh = graphic_pipelines[0]; self.pipeline_render_skin_mesh = graphic_pipelines[1]; @@ -261,8 +229,6 @@ impl Task for RenderModels { log::debug!("resize: setup_depth_image"); self.setup_depth_image(&display); } - - log::debug!("resize: complete -> {}", surface_version); }; self.surface_version = surface_version; } @@ -271,13 +237,20 @@ impl Task for RenderModels { log::debug!("draw count: {:?}", draw_count); - unsafe { - self.execute_render_pass(&frame, draw_count); - self.submit_draw_commands(); - } + let command_recorder = Recorder { + resolution: frame.resolution, + draw_count, + pipeline_layout: self.pipeline_layout_render, + descriptor_sets: self.descriptor_sets.clone(), + pipeline_render_only_mesh: self.pipeline_render_only_mesh, + pipeline_render_skin_mesh: self.pipeline_render_skin_mesh, + indirect_buffer: self.indirect_buffer.handle, + index_buffer: self.index_buffer.handle, + vertex_buffer_only_mesh: self.vertex_buffer_only_mesh.handle, + vertex_buffer_skin_mesh: self.vertex_buffer_skin_mesh.handle, + }; - log::debug!("pbr: submit_command_buffer"); - RenderPass {} + RenderSubmit::new::(Box::new(command_recorder), &[]) } } @@ -297,11 +270,11 @@ impl RenderModels { }; let command_buffer_allocate_info = vk::CommandBufferAllocateInfo::default() - .command_buffer_count(2) + .command_buffer_count(1) .command_pool(command_pool) .level(vk::CommandBufferLevel::PRIMARY); - let (command_buffer_setup, command_buffer_draw) = unsafe { + let command_buffer_setup = unsafe { gpu.allocate_command_buffers(&command_buffer_allocate_info) .into() }; @@ -310,70 +283,6 @@ impl RenderModels { vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); let command_buffer_setup_reuse_fence = unsafe { gpu.create_fence(&fence_create_info) }; - let command_buffer_draw_reuse_fence = unsafe { gpu.create_fence(&fence_create_info) }; - - let signal_semaphore_create_info = vk::SemaphoreCreateInfo::default(); - let signal_semaphore = unsafe { - gpu.create_semaphore(&signal_semaphore_create_info) - .expect("Failed to create a signal semaphore") - }; - let mut wait_semaphores = setup.wait_semaphores; - unsafe { - wait_semaphores.push(display.present_complete_semaphore()); - }; - - // TODO: this works only until we have one - display.set_render_complete_semaphore(signal_semaphore); - - let renderpass_attachments = [ - vk::AttachmentDescription { - format: setup.surface_format, - samples: vk::SampleCountFlags::TYPE_1, - load_op: vk::AttachmentLoadOp::CLEAR, - store_op: vk::AttachmentStoreOp::STORE, - final_layout: vk::ImageLayout::PRESENT_SRC_KHR, - ..Default::default() - }, - vk::AttachmentDescription { - format: vk::Format::D16_UNORM, - samples: vk::SampleCountFlags::TYPE_1, - load_op: vk::AttachmentLoadOp::CLEAR, - initial_layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - final_layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - ..Default::default() - }, - ]; - let color_attachment_refs = [vk::AttachmentReference { - attachment: 0, - layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, - }]; - let depth_attachment_ref = vk::AttachmentReference { - attachment: 1, - layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - }; - let dependencies = [vk::SubpassDependency { - src_subpass: vk::SUBPASS_EXTERNAL, - src_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, - dst_access_mask: vk::AccessFlags::COLOR_ATTACHMENT_READ - | vk::AccessFlags::COLOR_ATTACHMENT_WRITE, - dst_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, - ..Default::default() - }]; - - let subpass = vk::SubpassDescription::default() - .color_attachments(&color_attachment_refs) - .depth_stencil_attachment(&depth_attachment_ref) - .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS); - - let renderpass_create_info = vk::RenderPassCreateInfo::default() - .attachments(&renderpass_attachments) - .subpasses(std::slice::from_ref(&subpass)) - .dependencies(&dependencies); - - let render_pass = unsafe { - gpu.create_render_pass(&renderpass_create_info) - .expect("Failed to create a render pass") - }; let globals_buffer = unsafe { Self::create_globals_uniform_buffer(&gpu) @@ -694,14 +603,8 @@ impl RenderModels { Self { gpu, command_pool, - wait_semaphores, - signal_semaphore, command_buffer_setup, - command_buffer_draw, command_buffer_setup_reuse_fence, - command_buffer_draw_reuse_fence, - render_pass, - framebuffers: Vec::new(), surface_version: 0, index_buffer, index_buffer_usage: 0, @@ -727,7 +630,6 @@ impl RenderModels { material_image_memory, material_staging_buffer, material_layer_count, - material_layer_usage: 0, material_layer_index: HashMap::new(), material_layer_size: setup.material_image_size, mesh_registry: HashMap::new(), @@ -753,13 +655,6 @@ impl RenderModels { } } - /// # Safety - /// - /// Leaks signal semaphore that should never be destroyed - pub unsafe fn signal_semaphore(&self) -> vk::Semaphore { - self.signal_semaphore - } - fn update_buffers(&mut self, camera: &Camera, assets: &Assets, world: &World) -> DrawCount { self.instances_skin_mesh_indexed.clear(); self.instances_only_mesh_indexed.clear(); @@ -775,14 +670,13 @@ impl RenderModels { .map_and_write_to_device_memory(&self.gpu, 0, &globals_uniform); } - for (entity_id, mesh_id, material_id, _armature_id, transform) in world.query::<( + for (_entity_id, mesh_id, material_id, _armature_id, transform) in world.query::<( &Id, &Id, &Id, &Id, &Transform, )>() { - log::debug!("Update buffers: {:?}", entity_id); let material_index = self.register_material(*material_id, assets); if material_index.is_none() { continue; @@ -806,7 +700,7 @@ impl RenderModels { transform: transform.model.matrix().to_cols_array_2d(), }); let mut joint_index = 0; // NOTE: real joint can never have a 0 index - if mesh_layout.has_skin && transform.armature.len() != 0 { + if mesh_layout.has_skin && !transform.armature.is_empty() { joint_index = self.transform_buffer_data.len() as u32; self.transform_buffer_data .extend(transform.armature.iter().map(|i| TransformUniform { @@ -823,31 +717,23 @@ impl RenderModels { ..Default::default() }); - log::debug!("\n\n material index: {} ({:?})\n transform_index: {}\n joint_index: {}\n\n", material_index, material_id, transform_index, joint_index); - if mesh_layout.has_skin {} } } unsafe { - let material_bytes = self.material_buffer.map_and_write_to_device_memory( + self.material_buffer.map_and_write_to_device_memory( &self.gpu, 0, self.material_buffer_data.as_slice(), ); - log::debug!( - "writing materials buffer ({:?}): {:?}", - material_bytes, - self.material_buffer_data.as_slice() - ); }; unsafe { - let transform_bytes = self.transform_buffer.map_and_write_to_device_memory( + self.transform_buffer.map_and_write_to_device_memory( &self.gpu, 0, self.transform_buffer_data.as_slice(), ); - log::debug!("writing materials buffer ({:?})", transform_bytes); }; let mut instances_total = 0; @@ -883,17 +769,6 @@ impl RenderModels { }) .collect::>(); - log::debug!( - "only mesh: instance buffer data (offest: {}): {:?}", - instance_buffer_offset, - instance_buffer_data - ); - log::debug!( - "only mesh: indirect buffer data (offest: {}): {:?}", - indirect_buffer_offset, - indirect_buffer_data - ); - instances_total += instances_count; unsafe { @@ -943,17 +818,6 @@ impl RenderModels { }) .collect::>(); - log::debug!( - "only mesh(indexed): instance buffer data (offest: {}): {:?}", - instance_buffer_offset, - instance_buffer_data - ); - log::debug!( - "only mesh(indexed): indirect buffer data (offest: {}): {:?}", - indirect_buffer_offset, - indirect_buffer_data - ); - instances_total += instances_count; unsafe { @@ -977,10 +841,7 @@ impl RenderModels { let skin_mesh_draws_count = if !self.instances_skin_mesh.is_empty() { indirect_buffer_offset += indirect_buffer_offset % std::mem::size_of::() as u64; - log::debug!( - "skin mesh: indirect buffer data (offest: {})", - indirect_buffer_offset, - ); + let instances_count = self .instances_skin_mesh .values() @@ -1029,10 +890,7 @@ impl RenderModels { let skin_mesh_indexed_draws_count = if !self.instances_skin_mesh_indexed.is_empty() { indirect_buffer_offset += indirect_buffer_offset % std::mem::size_of::() as u64; - log::debug!( - "skin mesh(indexed): indirect buffer data (offest: {})", - indirect_buffer_offset, - ); + let instances_count = self .instances_skin_mesh_indexed .values() @@ -1063,12 +921,12 @@ impl RenderModels { // instances_total += instances_count; unsafe { - indirect_buffer_offset += self.indirect_buffer.map_and_write_to_device_memory( + self.indirect_buffer.map_and_write_to_device_memory( &self.gpu, indirect_buffer_offset, indirect_buffer_data.as_slice(), ); - instance_buffer_offset += self.instance_buffer.map_and_write_to_device_memory( + self.instance_buffer.map_and_write_to_device_memory( &self.gpu, instance_buffer_offset, instance_buffer_data.as_slice(), @@ -1124,12 +982,12 @@ impl RenderModels { let mesh_layout = MeshLayout { vertices: LayoutInBuffer { - offset: vertex_offset, + // offset: vertex_offset, base: (vertex_offset / vertex_size) as u32, count: mesh.count_vertices() as u32, }, indices: index_data.map(|data| LayoutInBuffer { - offset: self.index_buffer_usage, + // offset: self.index_buffer_usage, base: (self.index_buffer_usage / (std::mem::size_of::() as u64)) as u32, count: data.len() as u32, @@ -1137,22 +995,10 @@ impl RenderModels { has_skin, }; - log::debug!( - "VB offset: {}, IB offset: {:?}, has skin: {:?}", - mesh_layout.vertices.offset, - mesh_layout.indices.as_ref().map(|i| i.offset), - has_skin - ); - self.mesh_registry.insert(mesh_id, mesh_layout); unsafe { if has_skin { - log::debug!( - "write {:?} bytes to VB(skin) at offset {:?}", - vertex_data.len(), - vertex_offset - ); self.vertex_buffer_skin_mesh_usage += self.vertex_buffer_skin_mesh.map_and_write_to_device_memory( &self.gpu, @@ -1160,11 +1006,6 @@ impl RenderModels { vertex_data.as_slice(), ); } else { - log::debug!( - "write {:?} bytes to VB(no skin) at offset {:?}", - vertex_data.len(), - vertex_offset - ); self.vertex_buffer_only_mesh_usage += self.vertex_buffer_only_mesh.map_and_write_to_device_memory( &self.gpu, @@ -1174,13 +1015,7 @@ impl RenderModels { } }; - // log::debug!("Indices @{}: {:?}", self.index_buffer_usage, index_data); if let Some(data) = index_data.as_ref() { - log::debug!( - "write {:?} bytes to IB at offset {:?}", - data.len() * std::mem::size_of::(), - self.index_buffer_usage - ); unsafe { self.index_buffer_usage += self.index_buffer.map_and_write_to_device_memory( @@ -1189,7 +1024,6 @@ impl RenderModels { data, ); } - log::debug!("self.index_buffer_usage -> {}", self.index_buffer_usage); } return Some(mesh_layout); @@ -1207,7 +1041,9 @@ impl RenderModels { .get(material.albedo_map) .map(|image| { let base_array_layer = self.material_layer_index.len(); - if !self.material_layer_index.contains_key(&material.albedo_map) { + if let std::collections::hash_map::Entry::Vacant(e) = + self.material_layer_index.entry(material.albedo_map) + { // write to buffer // TODO: verify material extent unsafe { @@ -1221,8 +1057,7 @@ impl RenderModels { image.data(), ); }; - self.material_layer_index - .insert(material.albedo_map, base_array_layer); + e.insert(base_array_layer); staging_layer_count += 1; } let albedo_map_index = self @@ -1239,13 +1074,8 @@ impl RenderModels { self.flush_material_staging_buffer(staging_layer_count, base_array_layer as u32); }; let mut material_uniform: MaterialUniform = material.into(); - material_uniform.maps_1 = [ - albedo_map_index, - std::u32::MAX, - std::u32::MAX, - std::u32::MAX, - ]; - material_uniform.maps_2 = [std::u32::MAX; 4]; + material_uniform.maps_1 = [albedo_map_index, u32::MAX, u32::MAX, u32::MAX]; + material_uniform.maps_2 = [u32::MAX; 4]; material_uniform } else { return None; @@ -1255,9 +1085,8 @@ impl RenderModels { self.material_buffer_index .get(&material_id) .cloned() - .map(|material_index| { + .inspect(|&material_index| { self.material_buffer_data[material_index as usize] = material_uniform; - material_index }) .or_else(|| { let material_index = self.material_buffer_index.len() as u32; @@ -1276,7 +1105,6 @@ impl RenderModels { if staging_layer_count == 0 { return; } - log::debug!("copy material to vk:Image ({} layers)", staging_layer_count); // begin self.gpu .wait_for_fences(&[self.command_buffer_setup_reuse_fence], true, u64::MAX) @@ -1300,24 +1128,6 @@ impl RenderModels { .begin_command_buffer(self.command_buffer_setup, &command_buffer_begin_info) .expect("Begin commandbuffer"); - // Command buffer - // let buffer_copy_regions = (0..staging_layer_count) - // .into_iter() - // .map(|_| { - // vk::BufferImageCopy::default() - // .image_subresource( - // vk::ImageSubresourceLayers::default() - // .aspect_mask(vk::ImageAspectFlags::COLOR) - // .base_array_layer(base_array_layer) - // .layer_count(1), - // ) - // .image_extent(vk::Extent3D { - // width: self.material_layer_size.width, - // height: self.material_layer_size.height, - // depth: 1, - // }) - // }) - // .collect::>(); let buffer_copy_regions = [vk::BufferImageCopy::default() .image_subresource( vk::ImageSubresourceLayers::default() @@ -1397,16 +1207,14 @@ impl RenderModels { .command_buffers(&command_buffers) .signal_semaphores(&signal_semaphores); - log::debug!("submit image copy"); self.gpu .submit_queue(&[submit_info], self.command_buffer_setup_reuse_fence) .expect("queue submit failed."); // wait let fences = [self.command_buffer_setup_reuse_fence]; self.gpu - .wait_for_fences(&fences, true, std::u64::MAX) + .wait_for_fences(&fences, true, u64::MAX) .expect("Failed to wait until end of textures bufering"); - log::debug!("wait_for_fences: done"); } /// Returns Buffer, binded memory and allocated size @@ -1485,34 +1293,12 @@ impl RenderModels { gpu.create_shader_module(&shader_module_create_info) } - unsafe fn create_framebuffers(&mut self, display: &Display, render_pass: vk::RenderPass) { - let resolution = display.surface_resolution(); - self.framebuffers = display - .swapchain_image_views() - .map(|&present_image_view| { - let framebuffer_attachments = [present_image_view, display.depth_image_view()]; - let frame_buffer_create_info = vk::FramebufferCreateInfo::default() - .render_pass(render_pass) - .attachments(&framebuffer_attachments) - .width(resolution.width) - .height(resolution.height) - .layers(1); - - self.gpu - .create_framebuffer(&frame_buffer_create_info) - .expect("Could not create a framebuffer") - }) - .collect::>() - } - - unsafe fn destroy_framebuffers(&mut self) { - for framebuffer in self.framebuffers.drain(..) { - self.gpu.destroy_framebuffer(framebuffer); - } - } - - unsafe fn create_graphics_pipelines(&self, surface_resolution: Extent2D) -> Vec { - let shader_entry_point = unsafe { CStr::from_bytes_with_nul_unchecked(b"main\0") }; + unsafe fn create_graphics_pipelines( + &self, + render_pass: vk::RenderPass, + surface_resolution: Extent2D, + ) -> Vec { + let shader_entry_point = c"main"; // ONLY MESH let only_mesh_shader_stages = [ vk::PipelineShaderStageCreateInfo { @@ -1662,12 +1448,23 @@ impl RenderModels { let rasterization_info = vk::PipelineRasterizationStateCreateInfo { front_face: vk::FrontFace::COUNTER_CLOCKWISE, + depth_clamp_enable: vk::FALSE, + rasterizer_discard_enable: vk::FALSE, + depth_bias_enable: vk::FALSE, + depth_bias_constant_factor: 0.0, + depth_bias_clamp: 0.0, + depth_bias_slope_factor: 0.0, line_width: 1.0, polygon_mode: vk::PolygonMode::FILL, ..Default::default() }; let multisample_state_info = vk::PipelineMultisampleStateCreateInfo { rasterization_samples: vk::SampleCountFlags::TYPE_1, + sample_shading_enable: vk::FALSE, + //multisampling defaulted to no multisampling (1 sample per pixel) + min_sample_shading: 1.0, + alpha_to_coverage_enable: vk::FALSE, + alpha_to_one_enable: vk::FALSE, ..Default::default() }; let noop_stencil_state = vk::StencilOpState { @@ -1715,7 +1512,7 @@ impl RenderModels { .color_blend_state(&color_blend_state) .dynamic_state(&dynamic_state_info) .layout(self.pipeline_layout_render) - .render_pass(self.render_pass); + .render_pass(render_pass); let skin_mesh_graphic_pipeline_info = vk::GraphicsPipelineCreateInfo::default() .stages(&skin_mesh_shader_stages) @@ -1728,7 +1525,7 @@ impl RenderModels { .color_blend_state(&color_blend_state) .dynamic_state(&dynamic_state_info) .layout(self.pipeline_layout_render) - .render_pass(self.render_pass); + .render_pass(render_pass); self.gpu .create_graphics_pipelines( @@ -1746,223 +1543,6 @@ impl RenderModels { self.gpu.destroy_pipeline(self.pipeline_render_skin_mesh); } - unsafe fn execute_render_pass(&self, frame: &Frame, draw_count: DrawCount) { - self.gpu - .wait_for_fences(&[self.command_buffer_draw_reuse_fence], true, u64::MAX) - .expect("Failed to wait for draw buffer fences"); - - self.gpu - .reset_fences(&[self.command_buffer_draw_reuse_fence]) - .expect("Failed to reset Vulkan fences"); - - let command_buffer_begin_info = vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); - - self.gpu - .reset_command_buffer( - self.command_buffer_draw, - vk::CommandBufferResetFlags::RELEASE_RESOURCES, - ) - .expect("Failed to reset Vulkan command buffer"); - - self.gpu - .begin_command_buffer(self.command_buffer_draw, &command_buffer_begin_info) - .expect("Failed to begin draw command buffer"); - - let clear_values = [ - vk::ClearValue { - color: vk::ClearColorValue { - float32: [0.0, 0.0, 0.1, 0.0], - }, - }, - vk::ClearValue { - depth_stencil: vk::ClearDepthStencilValue { - depth: 1.0, - stencil: 0, - }, - }, - ]; - - let viewports = [vk::Viewport { - x: 0.0, - y: 0.0, - width: frame.resolution.width as f32, - height: frame.resolution.height as f32, - min_depth: 0.0, - max_depth: 1.0, - }]; - let scissors = [(vk::Extent2D { - width: frame.resolution.width, - height: frame.resolution.height, - }) - .into()]; - - let render_pass_begin_info = vk::RenderPassBeginInfo::default() - .render_pass(self.render_pass) - .framebuffer(self.framebuffers[frame.swapchain_index as usize]) - .render_area(vk::Rect2D { - offset: vk::Offset2D { x: 0, y: 0 }, - extent: vk::Extent2D { - width: frame.resolution.width, - height: frame.resolution.height, - }, - }) - .clear_values(&clear_values); - - self.gpu.cmd_begin_render_pass( - self.command_buffer_draw, - &render_pass_begin_info, - vk::SubpassContents::INLINE, - ); - - self.gpu.cmd_bind_descriptor_sets( - self.command_buffer_draw, - vk::PipelineBindPoint::GRAPHICS, - self.pipeline_layout_render, - 0, - &self.descriptor_sets[..], - &[], - ); - - let mut offset: u64 = 0; - - // ONLY MESH - if draw_count.only_mesh != 0 || draw_count.only_mesh_indexed != 0 { - self.gpu.cmd_bind_pipeline( - self.command_buffer_draw, - vk::PipelineBindPoint::GRAPHICS, - self.pipeline_render_only_mesh, - ); - self.gpu - .cmd_set_viewport(self.command_buffer_draw, 0, &viewports); - self.gpu - .cmd_set_scissor(self.command_buffer_draw, 0, &scissors); - self.gpu.cmd_bind_vertex_buffers( - self.command_buffer_draw, - 0, - &[self.vertex_buffer_only_mesh.handle], - &[0], - ); - - if draw_count.only_mesh != 0 { - log::debug!( - "cmd_draw_indirect(offset: {}, draw_count: {}, stride: {})", - offset, - draw_count.only_mesh, - std::mem::size_of::() as u32 - ); - self.gpu.cmd_draw_indirect( - self.command_buffer_draw, - self.indirect_buffer.handle, - offset, - draw_count.only_mesh, - std::mem::size_of::() as u32, - ); - offset += draw_count.only_mesh as u64 - * std::mem::size_of::() as u64; - } - - if draw_count.only_mesh_indexed != 0 { - // padding - offset += offset % std::mem::size_of::() as u64; - log::debug!( - "cmd_draw_indexed_indirect(offset: {}, draw_count: {}, stride: {})", - offset, - draw_count.only_mesh_indexed, - std::mem::size_of::() as u32 - ); - self.gpu.cmd_bind_index_buffer( - self.command_buffer_draw, - self.index_buffer.handle, - 0, - vk::IndexType::UINT32, - ); - - self.gpu.cmd_draw_indexed_indirect( - self.command_buffer_draw, - self.indirect_buffer.handle, - offset, - draw_count.only_mesh_indexed, - std::mem::size_of::() as u32, - ); - - offset += draw_count.only_mesh_indexed as u64 - * std::mem::size_of::() as u64; - } - } - - // MESH WITH SKIN - if draw_count.skin_mesh != 0 || draw_count.skin_mesh_indexed != 0 { - self.gpu.cmd_bind_pipeline( - self.command_buffer_draw, - vk::PipelineBindPoint::GRAPHICS, - self.pipeline_render_skin_mesh, - ); - self.gpu - .cmd_set_viewport(self.command_buffer_draw, 0, &viewports); - self.gpu - .cmd_set_scissor(self.command_buffer_draw, 0, &scissors); - self.gpu.cmd_bind_vertex_buffers( - self.command_buffer_draw, - 0, - &[self.vertex_buffer_skin_mesh.handle], - &[0], - ); - - if draw_count.skin_mesh != 0 { - // padding - offset += offset % std::mem::size_of::() as u64; - log::debug!( - "skin - cmd_draw_indirect(offset: {}, draw_count: {}, stride: {})", - offset, - draw_count.skin_mesh, - std::mem::size_of::() as u32 - ); - self.gpu.cmd_draw_indirect( - self.command_buffer_draw, - self.indirect_buffer.handle, - offset, - draw_count.skin_mesh, - std::mem::size_of::() as u32, - ); - offset += draw_count.skin_mesh as u64 - * std::mem::size_of::() as u64; - } - - if draw_count.skin_mesh_indexed != 0 { - offset += offset % std::mem::size_of::() as u64; - log::debug!( - "skin - cmd_draw_indexed_indirect(offset: {}, draw_count: {}, stride: {})", - offset, - draw_count.skin_mesh_indexed, - std::mem::size_of::() as u32 - ); - self.gpu.cmd_bind_index_buffer( - self.command_buffer_draw, - self.index_buffer.handle, - 0, - vk::IndexType::UINT32, - ); - - self.gpu.cmd_draw_indexed_indirect( - self.command_buffer_draw, - self.indirect_buffer.handle, - offset, - draw_count.skin_mesh_indexed, - std::mem::size_of::() as u32, - ); - } - } - - self.gpu.cmd_end_render_pass(self.command_buffer_draw); - - self.gpu - .end_command_buffer(self.command_buffer_draw) - .expect("End commandbuffer"); - - // panic!("--------------------------- BREAKPOINT ---------------------------"); - } - unsafe fn setup_depth_image(&self, display: &Display) { let depth_image = display.depth_image(); @@ -2037,33 +1617,6 @@ impl RenderModels { .submit_queue(&submits, self.command_buffer_setup_reuse_fence) .expect("queue submit failed.") } - - unsafe fn submit_draw_commands(&self) { - // panic!("----------------------- BREAKPOINT -----------------------"); - let (wait_semaphores, wait_dst_stage_mask): (Vec<_>, Vec<_>) = self - .wait_semaphores - .iter() - .map(|s| (s, vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)) - .unzip(); - let signal_semaphores = [self.signal_semaphore]; - let command_buffers = [self.command_buffer_draw]; - let submits = [vk::SubmitInfo::default() - .wait_semaphores(wait_semaphores.as_slice()) - .wait_dst_stage_mask(wait_dst_stage_mask.as_slice()) - .command_buffers(&command_buffers) - .signal_semaphores(&signal_semaphores)]; - - log::debug!( - "buffers: {}, wait: {}, signal: {}, deps: {}", - command_buffers.len(), - wait_semaphores.len(), - signal_semaphores.len(), - wait_dst_stage_mask.len(), - ); - self.gpu - .submit_queue(&submits, self.command_buffer_draw_reuse_fence) - .expect("Failed to submit draw buffer to queue"); - } } pub struct RenderModelsSetup { @@ -2146,3 +1699,135 @@ pub struct TransformUniform { /// Model/Joint transform matrix pub transform: [[f32; 4]; 4], } + +pub struct Recorder { + resolution: Extent2D, + draw_count: DrawCount, + pipeline_layout: vk::PipelineLayout, + descriptor_sets: Vec, + pipeline_render_only_mesh: vk::Pipeline, + pipeline_render_skin_mesh: vk::Pipeline, + indirect_buffer: vk::Buffer, + index_buffer: vk::Buffer, + vertex_buffer_only_mesh: vk::Buffer, + vertex_buffer_skin_mesh: vk::Buffer, +} + +impl CommandRecorder for Recorder { + unsafe fn record(&self, gpu: &Gpu, command_buffer: vk::CommandBuffer) { + let viewports = [vk::Viewport { + x: 0.0, + y: 0.0, + width: self.resolution.width as f32, + height: self.resolution.height as f32, + min_depth: 0.0, + max_depth: 1.0, + }]; + let scissors = [(vk::Extent2D { + width: self.resolution.width, + height: self.resolution.height, + }) + .into()]; + + gpu.cmd_bind_descriptor_sets( + command_buffer, + vk::PipelineBindPoint::GRAPHICS, + self.pipeline_layout, + 0, + &self.descriptor_sets[..], + &[], + ); + + let mut offset: u64 = 0; + + gpu.cmd_set_viewport(command_buffer, 0, &viewports); + gpu.cmd_set_scissor(command_buffer, 0, &scissors); + // ONLY MESH + if self.draw_count.only_mesh != 0 || self.draw_count.only_mesh_indexed != 0 { + gpu.cmd_bind_pipeline( + command_buffer, + vk::PipelineBindPoint::GRAPHICS, + self.pipeline_render_only_mesh, + ); + gpu.cmd_bind_vertex_buffers(command_buffer, 0, &[self.vertex_buffer_only_mesh], &[0]); + + if self.draw_count.only_mesh != 0 { + gpu.cmd_draw_indirect( + command_buffer, + self.indirect_buffer, + offset, + self.draw_count.only_mesh, + std::mem::size_of::() as u32, + ); + offset += self.draw_count.only_mesh as u64 + * std::mem::size_of::() as u64; + } + + if self.draw_count.only_mesh_indexed != 0 { + // padding + offset += offset % std::mem::size_of::() as u64; + gpu.cmd_bind_index_buffer( + command_buffer, + self.index_buffer, + 0, + vk::IndexType::UINT32, + ); + + gpu.cmd_draw_indexed_indirect( + command_buffer, + self.indirect_buffer, + offset, + self.draw_count.only_mesh_indexed, + std::mem::size_of::() as u32, + ); + + offset += self.draw_count.only_mesh_indexed as u64 + * std::mem::size_of::() as u64; + } + } + + // MESH WITH SKIN + if self.draw_count.skin_mesh != 0 || self.draw_count.skin_mesh_indexed != 0 { + gpu.cmd_bind_pipeline( + command_buffer, + vk::PipelineBindPoint::GRAPHICS, + self.pipeline_render_skin_mesh, + ); + gpu.cmd_bind_vertex_buffers(command_buffer, 0, &[self.vertex_buffer_skin_mesh], &[0]); + + if self.draw_count.skin_mesh != 0 { + // padding + offset += offset % std::mem::size_of::() as u64; + gpu.cmd_draw_indirect( + command_buffer, + self.indirect_buffer, + offset, + self.draw_count.skin_mesh, + std::mem::size_of::() as u32, + ); + offset += self.draw_count.skin_mesh as u64 + * std::mem::size_of::() as u64; + } + + if self.draw_count.skin_mesh_indexed != 0 { + offset += offset % std::mem::size_of::() as u64; + gpu.cmd_bind_index_buffer( + command_buffer, + self.index_buffer, + 0, + vk::IndexType::UINT32, + ); + + gpu.cmd_draw_indexed_indirect( + command_buffer, + self.indirect_buffer, + offset, + self.draw_count.skin_mesh_indexed, + std::mem::size_of::() as u32, + ); + } + } + + // panic!("--------------------------- BREAKPOINT ---------------------------"); + } +} diff --git a/src/models/shaders/only_mesh.vert b/src/models/shaders/only_mesh.vert index c32baf30..9efc33b9 100644 --- a/src/models/shaders/only_mesh.vert +++ b/src/models/shaders/only_mesh.vert @@ -2,11 +2,11 @@ #extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_shading_language_420pack : enable -layout (location = 0) in vec3 pos; -layout (location = 1) in vec3 normal; -layout (location = 2) in vec2 texture; +layout(location = 0) in vec3 pos; +layout(location = 1) in vec3 normal; +layout(location = 2) in vec2 texture; -layout (binding = 0) uniform DtxGlobals { +layout(binding = 0) uniform DtxGlobals { mat4 proj; mat4 view; } dtx_globals; @@ -40,19 +40,22 @@ layout(std430, binding = 3) buffer DtxTransformsLayout mat4 dtx_transform[]; }; -layout (location = 0) out vec3 o_world_position; -layout (location = 1) out vec3 o_world_normal; -layout (location = 2) out vec4 o_color; +layout(location = 0) out vec3 o_world_position; +layout(location = 1) out vec3 o_world_normal; +layout(location = 2) out vec4 o_color; +layout(location = 3) out vec3 o_texture; void main() { uint transform_index = dtx_instance[gl_InstanceIndex].transform_index; uint material_index = dtx_instance[gl_InstanceIndex].material_index; mat4 model_transform = dtx_transform[transform_index]; vec4 material_color = dtx_material[material_index].color; + float albedo_layer = float(dtx_material[material_index].maps_1.x); mat4 proj_view = dtx_globals.proj * dtx_globals.view; o_world_position = vec3(model_transform * vec4(pos, 1.0)); o_world_normal = vec3(model_transform * vec4(normal, 1.0)); o_color = vec4(material_color); + o_texture = vec3(texture, albedo_layer); gl_Position = proj_view * vec4(o_world_position, 1.0); } diff --git a/src/models/shaders/only_mesh.vert.spv b/src/models/shaders/only_mesh.vert.spv index f0c1f645..0d505f2e 100644 Binary files a/src/models/shaders/only_mesh.vert.spv and b/src/models/shaders/only_mesh.vert.spv differ diff --git a/src/models/vertices.rs b/src/models/vertices.rs index ca517df6..c65a31fa 100644 --- a/src/models/vertices.rs +++ b/src/models/vertices.rs @@ -13,6 +13,14 @@ pub struct VertexPosition { pub value: Vec3, } +impl VertexPosition { + pub fn new(x: f32, y: f32, z: f32) -> Self { + Self { + value: Vec3::new(x, y, z), + } + } +} + impl VertexAttribute for VertexPosition { type Raw = [f32; 3]; fn name() -> &'static str { diff --git a/src/tasks/scheduler.rs b/src/tasks/scheduler.rs index c0ad5891..fb62cc29 100644 --- a/src/tasks/scheduler.rs +++ b/src/tasks/scheduler.rs @@ -158,22 +158,10 @@ pub fn spawn( // log::debug!("current state: {:?}", current_state); if current_state != default_state { if let Some(tasks) = pool.select_for_state(&default_state) { - for task in tasks.iter() { - // log::debug!( - // "tasks for default state: {:?}", - // pool.get(*task).and_then(|t| t.name()) - // ); - } queue.extend_from_slice(tasks); } } if let Some(tasks) = pool.select_for_state(¤t_state) { - for task in tasks.iter() { - // log::debug!( - // "tasks for current state: {:?}", - // pool.get(*task).and_then(|t| t.name()) - // ); - } queue.extend_from_slice(tasks); } diff --git a/src/tasks/worker.rs b/src/tasks/worker.rs index 52f05c67..820bcadf 100644 --- a/src/tasks/worker.rs +++ b/src/tasks/worker.rs @@ -28,7 +28,7 @@ pub fn spawn( break; } // TODO: implement Debug trait for message - _ => println!("{}: message ignored", name), + _ => println!("{name}: message ignored"), }; } }) diff --git a/src/window.rs b/src/window.rs index ce0e99d0..2c7acddc 100644 --- a/src/window.rs +++ b/src/window.rs @@ -167,7 +167,7 @@ impl winit::application::ApplicationHandler for EventLoop { .fps_request(app.fps_request()); scheduler.add_task(create_frame_task); - let submit_frame_task = graphics::SubmitFrame::default(); + let submit_frame_task = graphics::SubmitFrame::new(&display); scheduler.add_task(submit_frame_task); scheduler.add_task(input::ReadInput::default()); @@ -189,16 +189,13 @@ impl winit::application::ApplicationHandler for EventLoop { _device_id: winit::event::DeviceId, event: winit::event::DeviceEvent, ) { - match event { - winit::event::DeviceEvent::MouseMotion { delta } => { - let (horizontal, vertical) = delta; - let input_event = event::Event::MouseMove { - horizontal, - vertical, - }; - self.task_manager.provide(input_event); - } - _ => {} + if let winit::event::DeviceEvent::MouseMotion { delta } = event { + let (horizontal, vertical) = delta; + let input_event = event::Event::MouseMove { + horizontal, + vertical, + }; + self.task_manager.provide(input_event); } } diff --git a/src/world.rs b/src/world.rs index 023e21a5..a742df41 100644 --- a/src/world.rs +++ b/src/world.rs @@ -40,6 +40,15 @@ impl World { } } + fn gen_entity_id(&self) -> Id { + loop { + let id = Id::new(); + if !self.index.contains_key(&id) { + return id; + } + } + } + /// Spawn single or multiple entities in the world /// /// Returns number of spawned entities @@ -78,7 +87,7 @@ impl World { /// Returns iterator over entities defined by Query pattern pub fn query<'w, Q>( &'w self, - ) -> impl Iterator::Iter as Iterator>::Item> + 'w + ) -> impl Iterator>::Iter as Iterator>::Item> + 'w where Q: Query<'w>, { @@ -123,7 +132,7 @@ impl World { /// Exiles an entity from the world pub fn exile(&mut self, id: &Id) -> Option { self.index - .get(id) + .remove(id) .map(|index| self.content[index.container].remove(index.address)) } @@ -346,7 +355,7 @@ where type Item = Id; fn next(&mut self) -> Option { self.entries.next().map(|entry| { - let id = Id::::new(); + let id = self.world.gen_entity_id(); let volatile = T::volatile(); let entity = entry.entity().with(id); self.container_index = self @@ -395,18 +404,30 @@ unsafe impl Sync for World {} #[cfg(test)] mod tests { + use crate::{Entity, Id}; + use super::World; #[derive(Debug, Eq, PartialEq, Copy, Clone)] struct Armor(u32); #[derive(Debug, Eq, PartialEq, Copy, Clone)] struct HealthComponent(u32); + #[derive(Debug, Eq, PartialEq, Copy, Clone)] struct SpeedComponent(u32); + #[derive(Debug, Eq, PartialEq, Copy, Clone)] struct DamageComponent(u32); + #[derive(Debug, Eq, PartialEq, Copy, Clone)] struct WeightComponent(u32); fn spawn() -> World { let mut world = World::new(); + + world + .spawn(Some((HealthComponent(80), SpeedComponent(10)))) + .count(); + world + .spawn(Some((SpeedComponent(50), DamageComponent(45)))) + .count(); world .spawn(Some(( Armor(100), @@ -414,12 +435,6 @@ mod tests { DamageComponent(300), ))) .count(); - world - .spawn(Some((HealthComponent(80), SpeedComponent(10)))) - .count(); - world - .spawn(Some((SpeedComponent(50), DamageComponent(45)))) - .count(); world.spawn(Some((DamageComponent(600), Armor(10)))).count(); let bulk = (0..9).map(|_| (SpeedComponent(35), WeightComponent(5000))); @@ -428,6 +443,45 @@ mod tests { world } + #[test] + fn can_spawn_and_exile() { + let mut world = spawn(); + let id_to_exile = { + let mut iter = + world.query::<(&Id, &Armor, &HealthComponent, &DamageComponent)>(); + + let item = iter.next(); + assert!(item.is_some()); + + let mut id_to_exile: Id = Id::null(); + + if let Some((id, _armor, _health, _damage)) = item { + id_to_exile = *id; + } + id_to_exile + }; + assert_ne!(id_to_exile, Id::null()); + + let exiled_entity = world.exile(&id_to_exile); + + assert!(exiled_entity.is_some()); + + let exiled_entity = exiled_entity.unwrap(); + let id = exiled_entity.get::>(); + let armor = exiled_entity.get::(); + let health = exiled_entity.get::(); + let damage = exiled_entity.get::(); + assert_eq!(id.cloned(), Some(id_to_exile)); + assert_eq!(armor.cloned(), Some(Armor(100))); + assert_eq!(health.cloned(), Some(HealthComponent(100))); + assert_eq!(damage.cloned(), Some(DamageComponent(300))); + + let mut iter = world.query::<(&Id, &Armor, &HealthComponent, &DamageComponent)>(); + + let item = iter.next(); + assert!(item.is_none()); + } + #[test] fn can_spawn_and_query() { let world = spawn(); diff --git a/src/world/camera.rs b/src/world/camera.rs index 5f867b4b..00ecc017 100644 --- a/src/world/camera.rs +++ b/src/world/camera.rs @@ -10,12 +10,14 @@ pub struct Camera { pub proj: Mat4, /// View matrix pub view: Mat4, + /// Target point + pub target: Vec3, } impl Camera { /// Constructs new instance of Camera - pub fn new(proj: Mat4, view: Mat4) -> Self { - Self { proj, view } + pub fn new(proj: Mat4, view: Mat4, target: Vec3) -> Self { + Self { proj, view, target } } /// Returns view matrix constructor @@ -56,8 +58,8 @@ impl Lens { impl Default for Lens { fn default() -> Self { Self { - fov: 1.1, // std::f32::consts::FRAC_PI_4 - plane: 0.0625..524288.06, + fov: 2.1, // std::f32::consts::FRAC_PI_4 + plane: 0.0625..5248.06, } } } diff --git a/src/world/storage.rs b/src/world/storage.rs index 073399e7..5a69b415 100644 --- a/src/world/storage.rs +++ b/src/world/storage.rs @@ -30,6 +30,12 @@ impl Entity { self.map.insert(component_type_id, component); } + pub fn get(&self) -> Option<&T> { + self.map + .get(&TypeId::of::()) + .and_then(|v| v.downcast_ref()) + } + pub fn archetype(&self) -> Archetype { Archetype { inner: self.map.keys(), @@ -175,6 +181,7 @@ impl Container { .map(|value| unsafe { (*(value.get())).downcast_ref::().unwrap() }) } + #[allow(clippy::mut_from_ref)] pub unsafe fn get_mut(&self, entity_index: usize) -> Option<&mut C> { self.data .get(&TypeId::of::()) diff --git a/terrain.png b/terrain.png new file mode 100644 index 00000000..c4233541 Binary files /dev/null and b/terrain.png differ diff --git a/utils/terrain/main.rs b/utils/terrain/main.rs new file mode 100644 index 00000000..2b77b237 --- /dev/null +++ b/utils/terrain/main.rs @@ -0,0 +1,200 @@ +use dotrix::terrain::{ColorMap, FalloffConfig, HeightMap, NoiseConfig}; +use serde::{Deserialize, Serialize}; +use std::{ + error::Error, + io::{self, Write}, +}; +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +pub enum Command { + Generate, +} + +#[derive(Debug, StructOpt)] +#[structopt(name = "dotrix-terrain")] +pub struct Inputs { + /// Path to .toml configuration file + #[structopt(default_value = "configs/terrain.toml", short, long)] + pub config: String, + + #[structopt(subcommand)] + pub command: Command, +} + +#[derive(Deserialize, Serialize)] +pub struct Config { + name: String, + size: u32, + heightmap: HeightmapSetup, + falloff: Option, + moisture: Option, + colormap: Option, +} + +#[derive(Deserialize, Serialize)] +pub struct HeightmapSetup { + file: String, + falloff: bool, + noise: NoiseConfig, +} + +#[derive(Deserialize, Serialize)] +pub struct FalloffSetup { + file: String, + config: Option, +} + +#[derive(Deserialize, Serialize)] +pub struct MoistureSetup { + file: String, + noise: NoiseConfig, +} + +#[derive(Deserialize, Serialize)] +pub struct ColormapSetup { + file: String, + moisture: bool, + colors: Vec, +} + +fn main() -> Result<(), Box> { + let inputs = Inputs::from_args(); + let config: Config = toml::from_str( + std::fs::read_to_string(std::path::Path::new(inputs.config.as_str()))?.as_str(), + )?; + + // generate + if let Some((file, falloff)) = config.falloff.as_ref().and_then(|setup| { + setup + .config + .as_ref() + .map(|falloff| (setup.file.as_ref(), falloff)) + }) { + generate_falloff(file, config.size, falloff)?; + } + + generate_heightmap(&config)?; + + generate_moisture(&config)?; + + generate_colormap(&config)?; + + Ok(()) +} + +fn generate_falloff(file: &str, size: u32, falloff: &FalloffConfig) -> Result<(), Box> { + print!("Generate falloff map... "); + io::stdout().flush().ok(); + let heightmap = HeightMap::new_from_falloff("heightmap", size, falloff); + let pixels = (0..size) + .flat_map(|z| (0..size).map(move |x| (x, z))) + .map(|(x, z)| (heightmap.value(x, z) * 255.0) as u8) + .collect::>(); + + image::GrayImage::from_raw(size, size, pixels) + .expect("Could not generate heightmap pixels buffers") + .save_with_format(&std::path::Path::new(file), image::ImageFormat::Png)?; + println!("OK"); + Ok(()) +} + +fn generate_heightmap(config: &Config) -> Result<(), Box> { + print!("Generate heightmap... "); + io::stdout().flush().ok(); + let mut heightmap = + HeightMap::new_from_noise("heightmap", config.size, &config.heightmap.noise); + if config.heightmap.falloff { + if let Some(falloff_file) = config.falloff.as_ref().map(|setup| setup.file.as_str()) { + let image = image::io::Reader::open(falloff_file)?.decode()?; + let size = image.width(); + let bytes = image.into_bytes(); + let falloff_heightmap = HeightMap::new_from_bytes("falloff", size, &bytes); + heightmap.subtract(&falloff_heightmap); + } + } + heightmap.write_to_file( + std::path::Path::new(config.heightmap.file.as_str()), + image::ImageFormat::Png, + )?; + println!("OK"); + Ok(()) +} + +fn generate_moisture(config: &Config) -> Result<(), Box> { + if let Some(moisture) = config.moisture.as_ref() { + print!("Generate moisture map... "); + io::stdout().flush().ok(); + let heightmap = HeightMap::new_from_noise("moisturemap", config.size, &moisture.noise); + heightmap.write_to_file( + std::path::Path::new(moisture.file.as_str()), + image::ImageFormat::Png, + )?; + println!("OK"); + } + Ok(()) +} + +fn generate_colormap(config: &Config) -> Result<(), Box> { + if let Some(colormap_config) = config.colormap.as_ref() { + print!("Generate colormap... "); + io::stdout().flush().ok(); + let moisture = if colormap_config.moisture { + config.moisture.as_ref().map(|moisture| { + let image = image::io::Reader::open(moisture.file.as_str()) + .expect("Could not open moisture map") + .decode() + .expect("Could not decode moisture map"); + let size = image.width(); + let bytes = image.into_bytes(); + HeightMap::new_from_bytes("moisturemap", size, &bytes) + }) + } else { + None + }; + let image = image::io::Reader::open(config.heightmap.file.as_str()) + .expect("Could not open heightmap file") + .decode() + .expect("Could not decode heightmap map"); + let size = image.width(); + let bytes = image.into_bytes(); + let heightmap = HeightMap::new_from_bytes("heightmap", size, &bytes); + + let colors = colormap_config + .colors + .iter() + .map(|hex| { + [ + u8::from_str_radix(&hex[0..2], 16) + .expect("Red channel in color must be a valid HEX number"), + u8::from_str_radix(&hex[2..4], 16) + .expect("Green channel in color must be a valid HEX number"), + u8::from_str_radix(&hex[4..6], 16) + .expect("Blue channel in color must be a valid HEX number"), + ] + }) + .collect::>(); + + let colormap = ColorMap::new("colormap", colors, 0.2); + + let pixels = (0..size) + .flat_map(|x| (0..size).map(move |z| (x, z))) + .flat_map(|(x, z)| { + let moisture = moisture + .as_ref() + .map(|moisture| moisture.value(x, z)) + .unwrap_or(0.0); + colormap.color(heightmap.value(x, z), moisture).into_iter() + }) + .collect::>(); + + image::RgbImage::from_raw(size, size, pixels) + .expect("Could not generate colormap pixels buffers") + .save_with_format( + &std::path::Path::new(colormap_config.file.as_str()), + image::ImageFormat::Png, + )?; + println!("OK"); + } + Ok(()) +}