diff --git a/.gitignore b/.gitignore index a5f4f60..c0a55e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -crates/*/target \ No newline at end of file +crates/*/target +**/flamegraph.svg \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e9c6015 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "rust-analyzer.linkedProjects": [ + ".\\crates\\bevy_tiles_render\\Cargo.toml", + ".\\crates\\bevy_tiles\\Cargo.toml" + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7a0ab6d..edf4eaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,34 +86,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aery" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "472fdc6214d5c5b5d468712dfbb7f18928035a0324f3292b13a329c47a81af94" -dependencies = [ - "aery_macros", - "aquamarine", - "bevy_app", - "bevy_derive", - "bevy_ecs", - "bevy_hierarchy", - "bevy_log", - "bevy_utils", - "smallvec", -] - -[[package]] -name = "aery_macros" -version = "0.3.0-dev" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554bf2d435fa8cb00318d5d5556bb63ba511bea2a0534dd5c8bead4a55b4763b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", -] - [[package]] name = "ahash" version = "0.8.6" @@ -212,20 +184,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "aquamarine" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df752953c49ce90719c7bf1fc587bc8227aed04732ea0c0f85e5397d7fdbd1a1" -dependencies = [ - "include_dir", - "itertools", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "arrayref" version = "0.3.7" @@ -374,18 +332,18 @@ checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bevy" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329e344f835f5a9a4c46a6d1d57371f726aa2c482d1bd669b2b9c4eb1ee91fd7" +checksum = "e4bc7e09282a82a48d70ade0c4c1154b0fd7882a735a39c66766a5d0f4718ea9" dependencies = [ "bevy_internal", ] [[package]] name = "bevy_a11y" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271b812e5734f5056a400f7d64592dd82d6c0e6179389c2f066f433ab8bc7692" +checksum = "68080288c932634f6563d3a8299efe0ddc9ea6787539c4c771ba250d089a94f0" dependencies = [ "accesskit", "bevy_app", @@ -395,9 +353,9 @@ dependencies = [ [[package]] name = "bevy_animation" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab94187a1253433e14f175293d8a86ec1c2822fda2a17807908f11ec21f45f00" +checksum = "7aa37683b1281e1ba8cf285644e6e3f0704f14b3901c5ee282067ff7ff6f4a56" dependencies = [ "bevy_app", "bevy_asset", @@ -414,9 +372,9 @@ dependencies = [ [[package]] name = "bevy_app" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172d532ea812e5954fa814dae003c207f2a0b20c6e50431787c94a7159677ece" +checksum = "d41731817993f92e4363dd3335558e779e290bc71eefc0b5547052b85810907e" dependencies = [ "bevy_derive", "bevy_ecs", @@ -430,9 +388,9 @@ dependencies = [ [[package]] name = "bevy_asset" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccb2b67984088b23e223cfe9ec1befd89a110665a679acb06839bc4334ed37d6" +checksum = "935984568f75867dd7357133b06f4b1502cd2be55e4642d483ce597e46e63bff" dependencies = [ "async-broadcast", "async-fs", @@ -462,9 +420,9 @@ dependencies = [ [[package]] name = "bevy_asset_macros" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3245193e90fc8abcf1059a467cb224501dcda083d114c67c10ac66b7171e3a" +checksum = "3f48b9bbe4ec605e4910b5cd1e1a0acbfbe0b80af5f3bcc4489a9fdd1e80058c" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -474,9 +432,9 @@ dependencies = [ [[package]] name = "bevy_audio" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478de80ff25cb7decbcb22797774d1597e8c32914e81431c67d64faadc08f84a" +checksum = "18a69889e1bfa4dbac4e641536b94f91c441da55796ad9832e77836b8264688b" dependencies = [ "bevy_app", "bevy_asset", @@ -492,9 +450,9 @@ dependencies = [ [[package]] name = "bevy_core" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025e6800b73048092a55c3611e9327ad4c4c17b60517ec1c0086bb40b4b19ea8" +checksum = "3daa24502a14839509f02407bc7e48299fe84d260877de23b60662de0f4f4b6c" dependencies = [ "bevy_app", "bevy_ecs", @@ -507,9 +465,9 @@ dependencies = [ [[package]] name = "bevy_core_pipeline" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e4b08a2d53ba62d9ec1fca3f7f4e0f556e9f59e1c8e63a4b7c2a18c0701152c" +checksum = "b4b77c4fca6e90edbe2e72da7bc9aa7aed7dfdfded0920ae0a0c845f5e11084a" dependencies = [ "bevy_app", "bevy_asset", @@ -529,9 +487,9 @@ dependencies = [ [[package]] name = "bevy_derive" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24bf40259be12a1a24d9fd536f5ff18d31eeb5665b77e2732899783be6edc5d6" +checksum = "f484318350462c58ba3942a45a656c1fd6b6e484a6b6b7abc3a787ad1a51e500" dependencies = [ "bevy_macro_utils", "quote", @@ -540,9 +498,9 @@ dependencies = [ [[package]] name = "bevy_diagnostic" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b5a99a9fb6cd7d1eb1714fad193944a0317f0887a15cccb8309c8d37951132" +checksum = "fa38ca5967d335cc1006a0e0f1a86c350e2f15fd1878449f61d04cd57a7c4060" dependencies = [ "bevy_app", "bevy_core", @@ -555,9 +513,9 @@ dependencies = [ [[package]] name = "bevy_ecs" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae11a1f467c372b50e9d4b55e78370f5420c9db7416200cc441cc84f08174dd3" +checksum = "7709fbd22f81fb681534cd913c41e1cd18b17143368743281195d7f024b61aea" dependencies = [ "async-channel 1.9.0", "bevy_ecs_macros", @@ -576,9 +534,9 @@ dependencies = [ [[package]] name = "bevy_ecs_macros" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f642c2b67c4d0daf8edf15074f6351457eb487a34b3de1290c760d8f3ac9ec16" +checksum = "a8843aa489f159f25cdcd9fee75cd7d221a7098a71eaa72cb2d6b40ac4e3f1ba" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -588,9 +546,9 @@ dependencies = [ [[package]] name = "bevy_encase_derive" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b9fb5a62c4e3ab70caaa839470d35fa932001b1b34b08bc7f7f1909bd2b3a7" +checksum = "5328a3715e933ebbff07d0e99528dc423c4f7a53590ed1ac19a120348b028990" dependencies = [ "bevy_macro_utils", "encase_derive_impl", @@ -598,9 +556,9 @@ dependencies = [ [[package]] name = "bevy_gilrs" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad31cc2c84315e0759d793d6c5bcb7d8789bbc16359c98d1b766e708c1bbae49" +checksum = "9b81ca2ebf66cbc7f998f1f142b15038ffe3c4ae1d51f70adda26dcf51b0c4ca" dependencies = [ "bevy_app", "bevy_ecs", @@ -614,9 +572,9 @@ dependencies = [ [[package]] name = "bevy_gizmos" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87d1cc978b91f416b23eb16f00e69f95c3a04582021827d8082e92d4725cc510" +checksum = "db232274ddca2ae452eb2731b98267b795d133ddd14013121bc7daddde1c7491" dependencies = [ "bevy_app", "bevy_asset", @@ -634,9 +592,9 @@ dependencies = [ [[package]] name = "bevy_gltf" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f933745c0c86e2c07948def581259b466f99708328657054e956275430ccfd7" +checksum = "85adc6b1fc86687bf67149e0bafaa4d6da432232fa956472d1b37f19121d3ace" dependencies = [ "base64 0.13.1", "bevy_animation", @@ -664,9 +622,9 @@ dependencies = [ [[package]] name = "bevy_hierarchy" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fa240011fce8ee23f9b46e5a26a628a31d7860d6d2e4e0e361bb3ea6d5a703" +checksum = "06bd477152ce2ae1430f5e0a4f19216e5785c22fee1ab23788b5982dc59d1a55" dependencies = [ "bevy_app", "bevy_core", @@ -679,9 +637,9 @@ dependencies = [ [[package]] name = "bevy_input" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e86e241b3a10b79f65a69205552546723b855d3d4c1bd8261637c076144d32f" +checksum = "cab9a599189b2a694c182d60cd52219dd9364f9892ff542d87799b8e45d9e6dc" dependencies = [ "bevy_app", "bevy_ecs", @@ -693,9 +651,9 @@ dependencies = [ [[package]] name = "bevy_internal" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55124e486814c4d3632d5cfad9c4f4e46d052c028593ec46fef5bfbfb0f840b1" +checksum = "f124bece9831afd80897815231072d51bfe3ac58c6bb58eca8880963b6d0487c" dependencies = [ "bevy_a11y", "bevy_animation", @@ -732,9 +690,9 @@ dependencies = [ [[package]] name = "bevy_log" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011417debf7868b45932bb97fc0d5bfdeaf9304e324aa94840e2f1e6deeed69d" +checksum = "0dc10ba1d225a8477b9e80a1bf797d8a8b8274e83c9b24fb4d9351aec9229755" dependencies = [ "android_log-sys", "bevy_app", @@ -748,9 +706,9 @@ dependencies = [ [[package]] name = "bevy_macro_utils" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf6fba87c6d069fcbcd8a48625ca8ab4392ad40d2b260863ce7d641a0f42986d" +checksum = "e566640c6b6dced73d2006c764c2cffebe1a82be4809486c4a5d7b4b50efed4d" dependencies = [ "proc-macro2", "quote", @@ -761,9 +719,9 @@ dependencies = [ [[package]] name = "bevy_math" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "752764558a1f429c20704c3b836a019fa308961c43fdfef4f08e339d456c96be" +checksum = "58ddc2b76783939c530178f88e5711a1b01044d7b02db4033e2eb8b43b6cf4ec" dependencies = [ "glam", "serde", @@ -771,18 +729,18 @@ dependencies = [ [[package]] name = "bevy_mikktspace" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b596c41a56f2268ec7cde560edc588bc7b5886e4b49c8b27c4dcc9f7c743424c" +checksum = "8ec4962977a746d870170532fc92759e04d3dbcae8b7b82e7ca3bb83b1d75277" dependencies = [ "glam", ] [[package]] name = "bevy_pbr" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb6a35a78d355cc21c10f277dcd171eca65e30a90e76eb89f4dacf606621fe1" +checksum = "520bfd2a898c74f84ea52cfb8eb061f37373ad15e623489d5f75d27ebd6138fe" dependencies = [ "bevy_app", "bevy_asset", @@ -806,15 +764,15 @@ dependencies = [ [[package]] name = "bevy_ptr" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308a02679f6ce21ef71de20fae6d6a2016c07baa21d8e8d0558e6b7851e8adf2" +checksum = "c77ec20c8fafcdc196508ef5ccb4f0400a8d193cb61f7b14a36ed9a25ad423cf" [[package]] name = "bevy_reflect" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd56914a8ad57621d7a1a099f7e6b1f7482c9c76cedc9c3d4c175a203939c5d" +checksum = "d7921f15fc944c9c8ad01d7dbcea6505b8909c6655cd9382bab1407181556038" dependencies = [ "bevy_math", "bevy_ptr", @@ -831,9 +789,9 @@ dependencies = [ [[package]] name = "bevy_reflect_derive" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f627907c40ac552f798423447fc331fc1ddacd94c5f7a2a70942eb06bc8447" +checksum = "b4a8c5475f216e751ef4452a1306b00711f33d2d04d9f149e4c845dfeb6753a0" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -844,9 +802,9 @@ dependencies = [ [[package]] name = "bevy_render" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90d777f4c51bd58e9e40777c6cb8dde0778df7e2c5298b3f9e3455bd12a9856c" +checksum = "bdefdd3737125b0d94a6ff20bb70fa8cfe9d7d5dcd72ba4dfe6c5f1d30d9f6e4" dependencies = [ "async-channel 1.9.0", "bevy_app", @@ -890,9 +848,9 @@ dependencies = [ [[package]] name = "bevy_render_macros" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b00c3d0abff94a729460fc9aa95c2ceac71b49b3041166bb5ba3098e9657e7" +checksum = "64d86bfc5a1e7fbeeaec0c4ceab18155530f5506624670965db3415f75826bea" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -902,9 +860,9 @@ dependencies = [ [[package]] name = "bevy_scene" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6294396a6375f0b14341d8003408c10aa040e3f833ac8bd49677170ec55d73" +checksum = "e7df078b5e406e37c8a1c6ba0d652bf105fde713ce3c3efda7263fe27467eee5" dependencies = [ "bevy_app", "bevy_asset", @@ -923,9 +881,9 @@ dependencies = [ [[package]] name = "bevy_sprite" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f7d1f88a6e5497fdafd95c20984a1d1b5517bc39d51600b4988cd60c51837a" +checksum = "c7cc0c9d946e17e3e0aaa202f182837bc796c4f862b2e5a805134f873f21cf7f" dependencies = [ "bevy_app", "bevy_asset", @@ -949,9 +907,9 @@ dependencies = [ [[package]] name = "bevy_tasks" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a45be906618192515bc613e46546150089adbb4a82178dc462045acd1e89e92" +checksum = "f4fefa7fe0da8923525f7500e274f1bd60dbd79918a25cf7d0dfa0a6ba15c1cf" dependencies = [ "async-channel 1.9.0", "async-executor", @@ -963,9 +921,9 @@ dependencies = [ [[package]] name = "bevy_text" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c136af700af4f87c94f68d6e019528c371bf09ebf4a8ff7468bb3c73806b34f5" +checksum = "3a9a79d49ca06170d69149949b134c14e8b99ace1444c1ca2cd4743b19d5b055" dependencies = [ "ab_glyph", "bevy_app", @@ -987,7 +945,6 @@ dependencies = [ name = "bevy_tiles" version = "0.1.0" dependencies = [ - "aery", "bevy", "bimap", "rstest", @@ -997,17 +954,19 @@ dependencies = [ name = "bevy_tiles_render" version = "0.1.0" dependencies = [ - "aery", "bevy", "bevy_tiles", + "crossbeam", + "dashmap", + "rand", "rstest", ] [[package]] name = "bevy_time" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b29709cadf22d318a0b7c79f763e9c5ac414292bd0e850066fa935959021b276" +checksum = "e6250d76eed3077128b6a3d004f9f198b01107800b9824051e32bb658054e837" dependencies = [ "bevy_app", "bevy_ecs", @@ -1019,9 +978,9 @@ dependencies = [ [[package]] name = "bevy_transform" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70262c51e915b6224129206d23823364e650cf5eb5f4b6ce3ee379f608c180d2" +checksum = "d541e0c292edbd96afae816ee680e02247422423ccd5dc635c1e211a20ed64be" dependencies = [ "bevy_app", "bevy_ecs", @@ -1033,9 +992,9 @@ dependencies = [ [[package]] name = "bevy_ui" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd5ecbf2dceaab118769dd870e34d780bfde556af561fd10d8d613b0f237297e" +checksum = "d785e3b75dabcb2a8ad0d50933f8f3446d59e512cabc2d2a145e28c2bb8792ba" dependencies = [ "bevy_a11y", "bevy_app", @@ -1063,9 +1022,9 @@ dependencies = [ [[package]] name = "bevy_utils" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e75d4a34ef0b15dffd1ee9079ef1f0f5139527e192b9d5708b3e158777c753" +checksum = "7915222f4a08ccc782e08d10b751b42e5f9d786e697d0cb3fd09333cb7e8b6ea" dependencies = [ "ahash", "bevy_utils_proc_macros", @@ -1081,9 +1040,9 @@ dependencies = [ [[package]] name = "bevy_utils_proc_macros" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7dfd3735a61a1b681ed1e176afe4eae731bbb03e51ad871e9eb39e76a2d170e" +checksum = "7aafecc952b6b8eb1a93c12590bd867d25df2f4ae1033a01dfdfc3c35ebccfff" dependencies = [ "proc-macro2", "quote", @@ -1092,9 +1051,9 @@ dependencies = [ [[package]] name = "bevy_window" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e60d1830b3fbd7db5bfea7ac9fcd0f5e1d1af88c91ab469e697ab176d8b3140b" +checksum = "41ee72bf7f974000e9b31bb971a89387f1432ba9413f35c4fef59fef49767260" dependencies = [ "bevy_a11y", "bevy_app", @@ -1108,9 +1067,9 @@ dependencies = [ [[package]] name = "bevy_winit" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f8294e78c6a1f9c34d36501a377c5d20bf0fa23a0958187bb270187741448ba" +checksum = "1eb71f287eca9006dda998784c7b931e400ae2cc4c505da315882a8b082f21ad" dependencies = [ "accesskit_winit", "approx", @@ -1499,11 +1458,57 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eb9105919ca8e40d437fc9cbb8f1975d916f1bd28afe795a48aae32a2cc8920" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc6598521bb5a83d491e8c1fe51db7296019d2ca3cb93cc6c2a20369a4d78a2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1511,9 +1516,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" dependencies = [ "cfg-if", ] @@ -1529,6 +1534,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.2", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "dasp_sample" version = "0.11.0" @@ -1553,12 +1571,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - [[package]] name = "encase" version = "0.6.1" @@ -2111,25 +2123,6 @@ dependencies = [ "png", ] -[[package]] -name = "include_dir" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" -dependencies = [ - "proc-macro2", - "quote", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -2198,15 +2191,6 @@ dependencies = [ "mach2", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.9" @@ -2881,6 +2865,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -2891,30 +2881,6 @@ 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.109", - "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" @@ -2945,6 +2911,36 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17fd96390ed3feda12e1dfe2645ed587e0bea749e319333f104a33ff62f77a0b" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index bde696d..3326109 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,12 @@ opt-level = 1 opt-level = 3 [workspace.dependencies] -aery = "0.5.1" -bevy = {version = "0.12", default-features = false} +bevy = {version = "0.12.1", default-features = false} rstest = "0.18.2" +bimap = "0.6.3" +rand = "0.8.5" +crossbeam = "0.8.3" +dashmap = "5.5.3" [workspace.lints.clippy] type_complexity = "allow" @@ -31,4 +34,4 @@ undocumented_unsafe_blocks = "deny" all = "deny" [workspace.lints.rust] -unused_imports = "deny" \ No newline at end of file +unused_imports = "warn" \ No newline at end of file diff --git a/README.md b/README.md index 31cae5a..c57517f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ A suite of tilemap plugins for [`bevy`](https://bevyengine.org/), focusing on ergonomics and ease of use. +Expect crates in this suite to move a little bit slower than other tilemap crates, I have a busy schedule and a lot of the work here +is meant to be exploration of API design posibilities, rather than aiming for immediate use. + # Crates * [`bevy_tiles`](crates/bevy_tiles) [![Crates.io](https://img.shields.io/crates/v/bevy_tiles)](https://crates.io/crates/bevy_tiles) diff --git a/crates/bevy_tiles/clippy.toml b/clippy.toml similarity index 100% rename from crates/bevy_tiles/clippy.toml rename to clippy.toml diff --git a/crates/bevy_tiles/Cargo.toml b/crates/bevy_tiles/Cargo.toml index c64fcec..d319b8b 100644 --- a/crates/bevy_tiles/Cargo.toml +++ b/crates/bevy_tiles/Cargo.toml @@ -15,9 +15,8 @@ opt-level = 1 opt-level = 3 [dependencies] -aery = {workspace = true} bevy = {workspace = true} -bimap = "0.6.3" +bimap = {workspace = true} [dev-dependencies] rstest = {workspace = true} diff --git a/crates/bevy_tiles/src/chunks.rs b/crates/bevy_tiles/src/chunks.rs index 0c0e7ad..979e17f 100644 --- a/crates/bevy_tiles/src/chunks.rs +++ b/crates/bevy_tiles/src/chunks.rs @@ -1,23 +1,31 @@ use std::ops::Deref; -use aery::prelude::{CleanupPolicy, Relation}; -use bevy::ecs::{component::Component, entity::Entity}; +use bevy::{ + ecs::{component::Component, entity::Entity}, + math::Vec2, +}; mod chunk_query; pub use chunk_query::*; -/// An aery relation on chunks that point towards the map they are a part of. -#[derive(Relation)] -#[aery(Recursive)] -pub struct InMap(std::marker::PhantomData); +/// An relation on chunks that point towards the map they are a part of. +#[derive(Component, Clone)] +pub struct InMap(pub(crate) Entity); + +impl InMap { + /// Get the entity this chunk is in + pub fn get(&self) -> Entity { + self.0 + } +} /// The coordinate of a given chunk. /// # Note /// Right now, changes to this coordinate don't automatically update any information. /// If you wish to move a chunk, add, or remove a chunk, please do so via commands. /// Use this if you wish to track changes or other information. -#[derive(Component, Debug, PartialEq, Eq, Hash)] +#[derive(Component, Debug, PartialEq, Eq, Hash, Clone, Copy)] pub struct ChunkCoord([isize; N]); impl From<[isize; N]> for ChunkCoord { @@ -34,6 +42,12 @@ impl Deref for ChunkCoord { } } +impl From<&ChunkCoord<2>> for Vec2 { + fn from(value: &ChunkCoord<2>) -> Self { + Vec2::new(value.0[0] as f32, value.0[1] as f32) + } +} + /// Holds handles to all the tiles in a chunk. /// # Note /// Manually updating this value, adding it, or removing it from an entity may @@ -49,4 +63,14 @@ impl Chunk { tiles: vec![None; chunk_size], } } + + /// Gets the total number of tiles this chunk can hold. + pub fn total_size(&self) -> usize { + self.tiles.len() + } + + /// Gets a readonly reference to the underlying chunk data. + pub fn get_tiles(&self) -> &Vec> { + &self.tiles + } } diff --git a/crates/bevy_tiles/src/chunks/chunk_query.rs b/crates/bevy_tiles/src/chunks/chunk_query.rs index 0cdf8f0..a2f64b5 100644 --- a/crates/bevy_tiles/src/chunks/chunk_query.rs +++ b/crates/bevy_tiles/src/chunks/chunk_query.rs @@ -1,6 +1,5 @@ use std::ops::{Deref, DerefMut}; -use aery::prelude::*; use bevy::{ ecs::{ prelude::With, @@ -26,7 +25,7 @@ where Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static, { - chunk_q: Query<'w, 's, Q, (F, Relations>, With)>, + chunk_q: Query<'w, 's, Q, (F, With, With, With>)>, map_q: Query<'w, 's, &'static TileMap, With>>, } @@ -36,7 +35,7 @@ where Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static, { - type Target = Query<'w, 's, Q, (F, Relations>, With)>; + type Target = Query<'w, 's, Q, (F, With, With, With>)>; #[inline] fn deref(&self) -> &Self::Target { diff --git a/crates/bevy_tiles/src/commands.rs b/crates/bevy_tiles/src/commands.rs index 0e5e331..e99b0b9 100644 --- a/crates/bevy_tiles/src/commands.rs +++ b/crates/bevy_tiles/src/commands.rs @@ -14,10 +14,6 @@ use crate::{ tiles::{InChunk, TileCoord, TileIndex}, }; -use aery::{ - edges::{CheckedDespawn, Unset, Withdraw}, - prelude::Set, -}; use bevy::{ ecs::system::{Command, EntityCommands}, prelude::{Bundle, Commands, Entity, With, World}, @@ -228,6 +224,19 @@ where }); } + /// Spawn a new map, overwriting any old maps found. + pub fn spawn_map(&mut self, bundle: T) -> EntityCommands<'w, 's, '_> + where + T: Bundle + 'static, + { + let map_id = self.spawn(bundle).id(); + self.add(SpawnMap:: { + map_id, + label: PhantomData, + }); + self.entity(map_id) + } + /// Recursively despawns a map and all it's chunks and tiles. pub fn despawn_map(&mut self) -> &mut Self { self.add(DespawnMap:: { label: PhantomData }); @@ -250,9 +259,14 @@ where if let Some(chunk_info) = remove_chunk::(world, map, chunk_c) { chunk_info } else { - let chunk_id = world.spawn(ChunkCoord::from(chunk_c)).id(); + let chunk_id = world + .spawn(( + ChunkCoord::from(chunk_c), + MapLabel::::default(), + InMap(map_id), + )) + .id(); map.chunks.insert(chunk_c.into(), chunk_id); - Set::>::new(chunk_id, map_id).apply(world); (chunk_id, Chunk::new(L::CHUNK_SIZE.pow(N as u32))) } } @@ -283,7 +297,7 @@ where } else { ( world.spawn(MapLabel::::default()).id(), - TileMap::::default(), + TileMap::::with_chunk_size(L::CHUNK_SIZE), ) } } @@ -331,12 +345,12 @@ where } } - Set::>::new(tile_id, chunk_id).apply(world); - - world - .get_entity_mut(tile_id) - .unwrap() - .insert((TileIndex::from(tile_i), TileCoord::::new(tile_c))); + world.get_entity_mut(tile_id).unwrap().insert(( + TileIndex::from(tile_i), + TileCoord::::new(tile_c), + MapLabel::::default(), + InChunk(chunk_id), + )); world.get_entity_mut(chunk_id).unwrap().insert(chunk); world.get_entity_mut(map_id).unwrap().insert(map); @@ -369,9 +383,8 @@ where .and_then(|tile| tile.take()) .and_then(|tile_id| world.get_entity_mut(tile_id)) { - tile_e.remove::<(TileIndex, TileCoord)>(); + tile_e.remove::<(TileIndex, TileCoord, MapLabel, InChunk)>(); let tile_id = tile_e.id(); - Unset::>::new(tile_id, chunk_id).apply(world); Some(tile_id) } else { None @@ -412,12 +425,12 @@ pub fn insert_tile_batch( } } - Set::>::new(tile_id, chunk_id).apply(world); - - world - .get_entity_mut(tile_id) - .unwrap() - .insert((TileIndex::from(tile_i), TileCoord::::new(tile_c))); + world.get_entity_mut(tile_id).unwrap().insert(( + TileIndex::from(tile_i), + TileCoord::::new(tile_c), + MapLabel::::default(), + InChunk(chunk_id), + )); } world.get_entity_mut(chunk_id).unwrap().insert(chunk); @@ -473,9 +486,8 @@ where .and_then(|tile| tile.take()) .and_then(|tile_id| world.get_entity_mut(tile_id)) { - tile_e.remove::<(TileIndex, TileCoord)>(); + tile_e.remove::<(TileIndex, TileCoord, MapLabel, InChunk)>(); let tile_id = tile_e.id(); - Unset::>::new(tile_id, chunk_id).apply(world); tile_ids.push((tile_c, tile_id)); } } @@ -495,15 +507,16 @@ where let (map_id, mut map) = spawn_or_remove_map::(world); // Despawn the chunk if it exists - if let Some(chunk_id) = map.chunks.insert(chunk_c.into(), chunk_id) { - CheckedDespawn(chunk_id).apply(world); + if let Some(old_chunk) = take_chunk_despawn_tiles_inner::(world, chunk_c, &mut map) { + world.despawn(old_chunk); } world.get_entity_mut(chunk_id).unwrap().insert(( Chunk::new(L::CHUNK_SIZE.pow(N as u32)), ChunkCoord::from(chunk_c), + MapLabel::::default(), + InMap(map_id), )); - Set::>::new(chunk_id, map_id).apply(world); map.chunks.insert(chunk_c.into(), chunk_id); world.entity_mut(map_id).insert(map); @@ -526,10 +539,15 @@ where .remove::>(&chunk_c.into()) .and_then(|chunk_id| world.get_entity_mut(chunk_id)) { - chunk_e.remove::<(Chunk, ChunkCoord)>(); + let (chunk, _, _, _) = chunk_e + .take::<(Chunk, ChunkCoord, MapLabel, InMap)>() + .unwrap(); let chunk_id = chunk_e.id(); - Unset::>::new(chunk_id, map_id).apply(world); - Withdraw::>::new(chunk_id).apply(world); + for tile_id in chunk.tiles.into_iter().flatten() { + if let Some(mut tile) = world.get_entity_mut(tile_id) { + tile.remove::(); + } + } Some(chunk_id) } else { None @@ -551,26 +569,38 @@ where // Get the map or return let (map_id, mut map) = remove_map::(world)?; + let chunk_id = take_chunk_despawn_tiles_inner::(world, chunk_c, &mut map); + + world.entity_mut(map_id).insert(map); + + chunk_id +} + +pub(crate) fn take_chunk_despawn_tiles_inner( + world: &mut World, + chunk_c: [isize; N], + mut map: &mut TileMap, +) -> Option +where + L: TileMapLabel + Send + 'static, +{ // Get the old chunk or return let chunk_id = if let Some(mut chunk_e) = map .chunks .remove::>(&chunk_c.into()) .and_then(|chunk_id| world.get_entity_mut(chunk_id)) { - let (chunk, _) = chunk_e.take::<(Chunk, ChunkCoord)>().unwrap(); + let (chunk, _, _, _) = chunk_e + .take::<(Chunk, ChunkCoord, MapLabel, InMap)>() + .unwrap(); let chunk_id = chunk_e.id(); for tile_id in chunk.tiles.into_iter().flatten() { world.despawn(tile_id); } - Unset::>::new(chunk_id, map_id).apply(world); - Withdraw::>::new(chunk_id).apply(world); Some(chunk_id) } else { None }; - - world.entity_mut(map_id).insert(map); - chunk_id } @@ -586,16 +616,16 @@ pub fn insert_chunk_batch( // Get the chunks and entities from the map for (chunk_c, chunk_id) in chunks.into_iter() { - // Despawn the chunk if it exists - if let Some(chunk_id) = map.chunks.insert(chunk_c.into(), chunk_id) { - CheckedDespawn(chunk_id).apply(world); + if let Some(old_chunk) = take_chunk_despawn_tiles_inner::(world, chunk_c, &mut map) { + world.despawn(old_chunk); } world.get_entity_mut(chunk_id).unwrap().insert(( Chunk::new(L::CHUNK_SIZE.pow(N as u32)), ChunkCoord::from(chunk_c), + MapLabel::::default(), + InMap(map_id), )); - Set::>::new(chunk_id, map_id).apply(world); map.chunks.insert(chunk_c.into(), chunk_id); } @@ -629,10 +659,15 @@ where .remove::>(&chunk_c.into()) .and_then(|chunk_id| world.get_entity_mut(chunk_id)) { - chunk_e.remove::<(Chunk, ChunkCoord)>(); + let (chunk, _, _, _) = chunk_e + .take::<(Chunk, ChunkCoord, MapLabel, InMap)>() + .unwrap(); let chunk_id = chunk_e.id(); - Unset::>::new(chunk_id, map_id).apply(world); - Withdraw::>::new(chunk_id).apply(world); + for tile_id in chunk.tiles.into_iter().flatten() { + if let Some(mut tile) = world.get_entity_mut(tile_id) { + tile.remove::(); + } + } chunk_ids.push((chunk_c, chunk_id)); }; } @@ -666,13 +701,13 @@ where .remove::>(&chunk_c.into()) .and_then(|chunk_id| world.get_entity_mut(chunk_id)) { - let (chunk, _) = chunk_e.take::<(Chunk, ChunkCoord)>().unwrap(); + let (chunk, _, _, _) = chunk_e + .take::<(Chunk, ChunkCoord, MapLabel, InMap)>() + .unwrap(); let chunk_id = chunk_e.id(); for tile_id in chunk.tiles.into_iter().flatten() { world.despawn(tile_id); } - Unset::>::new(chunk_id, map_id).apply(world); - Withdraw::>::new(chunk_id).apply(world); chunk_ids.push((chunk_c, chunk_id)); }; } @@ -681,6 +716,21 @@ where chunk_ids } +/// Insert the given entity and have it be treated as the given map. +/// # Note +/// This will despawn any existing map with this label. +pub fn insert_map(world: &mut World, map_id: Entity) +where + L: TileMapLabel + Send + 'static, +{ + let map_info = remove_map::(world); + DespawnMap::::default().apply(world); + world.entity_mut(map_id).insert(( + MapLabel::::default(), + TileMap::::with_chunk_size(L::CHUNK_SIZE), + )); +} + trait GroupBy: Iterator { fn group_by( self, diff --git a/crates/bevy_tiles/src/commands/chunk_single.rs b/crates/bevy_tiles/src/commands/chunk_single.rs index 885e803..faab7f4 100644 --- a/crates/bevy_tiles/src/commands/chunk_single.rs +++ b/crates/bevy_tiles/src/commands/chunk_single.rs @@ -1,4 +1,3 @@ -use aery::edges::CheckedDespawn; use bevy::ecs::{entity::Entity, system::Command, world::World}; use crate::prelude::TileMapLabel; @@ -32,7 +31,7 @@ where fn apply(self, world: &mut World) { let tile_id = take_chunk_despawn_tiles::(world, self.chunk_c); if let Some(id) = tile_id { - CheckedDespawn(id).apply(world); + world.despawn(id); } } } diff --git a/crates/bevy_tiles/src/commands/map.rs b/crates/bevy_tiles/src/commands/map.rs index dd492e9..8f2ef35 100644 --- a/crates/bevy_tiles/src/commands/map.rs +++ b/crates/bevy_tiles/src/commands/map.rs @@ -1,25 +1,63 @@ -use aery::edges::CheckedDespawn; +use std::ops::Deref; + use bevy::ecs::{entity::Entity, query::With, system::Command, world::World}; use crate::{ maps::{MapLabel, TileMap}, - prelude::TileMapLabel, + prelude::{ChunkCoord, TileMapLabel}, }; +use super::{insert_map, take_chunk_despawn_tiles_inner}; + +pub struct SpawnMap { + pub map_id: Entity, + pub label: std::marker::PhantomData, +} + +impl Command for SpawnMap +where + L: TileMapLabel + 'static, +{ + fn apply(self, world: &mut World) { + insert_map::(world, self.map_id) + } +} + pub struct DespawnMap { pub label: std::marker::PhantomData, } +impl Default for DespawnMap { + fn default() -> Self { + Self { + label: Default::default(), + } + } +} + impl Command for DespawnMap where L: TileMapLabel + 'static, { fn apply(self, world: &mut World) { - if let Ok(map_id) = world + if let Ok((map_id)) = world .query_filtered::>, With>)>() - .get_single(world) + .get_single_mut(world) { - CheckedDespawn(map_id).apply(world); + let mut map = world + .get_entity_mut(map_id) + .unwrap() + .take::>() + .unwrap(); + let chunks: Vec> = map.chunks.keys().cloned().collect(); + for chunk_c in chunks { + if let Some(old_chunk) = + take_chunk_despawn_tiles_inner::(world, *chunk_c, &mut map) + { + world.despawn(old_chunk); + } + } + world.despawn(map_id); } } } diff --git a/crates/bevy_tiles/src/commands/tile_single.rs b/crates/bevy_tiles/src/commands/tile_single.rs index 7ecf341..ffd8fc3 100644 --- a/crates/bevy_tiles/src/commands/tile_single.rs +++ b/crates/bevy_tiles/src/commands/tile_single.rs @@ -1,4 +1,3 @@ -use aery::edges::CheckedDespawn; use bevy::ecs::{entity::Entity, system::Command, world::World}; use crate::prelude::TileMapLabel; @@ -32,7 +31,7 @@ where fn apply(self, world: &mut World) { let tile_id = take_tile::(world, self.tile_c); if let Some(id) = tile_id { - CheckedDespawn(id).apply(world); + world.despawn(id); } } } diff --git a/crates/bevy_tiles/src/coords.rs b/crates/bevy_tiles/src/coords.rs index ffa6a28..a956f8b 100644 --- a/crates/bevy_tiles/src/coords.rs +++ b/crates/bevy_tiles/src/coords.rs @@ -1,3 +1,5 @@ +use bevy::log::debug; + /// Calculate the coordinate of a chunk from a given tile coordinate and chunk size #[inline] pub fn calculate_chunk_coordinate( @@ -5,7 +7,11 @@ pub fn calculate_chunk_coordinate( chunk_size: usize, ) -> [isize; N] { for i in tile_c.iter_mut() { - *i = *i / (chunk_size as isize) - if *i < 0 { 1 } else { 0 } + if *i < 0 { + *i = (*i + 1) / (chunk_size as isize) - 1; + } else { + *i /= chunk_size as isize; + } } tile_c } @@ -173,6 +179,7 @@ mod tests { #[case(16, [15, 15], 255)] #[case(16, [-1, -1], 255)] #[case(16, [-16, -16], 0)] + #[case(8, [-8, -0], 0)] fn tile_index_test( #[case] chunk_size: usize, #[case] tile_c: [isize; 2], diff --git a/crates/bevy_tiles/src/lib.rs b/crates/bevy_tiles/src/lib.rs index 2539b83..c7e3fc0 100644 --- a/crates/bevy_tiles/src/lib.rs +++ b/crates/bevy_tiles/src/lib.rs @@ -1,6 +1,5 @@ //! A general purpose grided entity library meant to support tilemap libraries, //! or other libraries that require accessing entities in a grid based manner. -//! Built on top of the [`aery`](https://github.com/iiYese/aery) relations crate. //! //! The goal is to keep the API surface as simple and intuitive as possible, //! and to avoid deferred operations/states where possible to make the structures more intuitive work with. @@ -8,7 +7,6 @@ #![deny(missing_docs)] -use aery::Aery; use bevy::app::Plugin; /// Provides chunk level utilities. @@ -22,7 +20,7 @@ pub mod maps; /// Provides tile level utilities. pub mod tiles; -/// +/// Provides most of what you need to get started. pub mod prelude { pub use crate::commands::{TileCommandExt, TileCommands}; @@ -41,9 +39,5 @@ pub mod prelude { pub struct TilesPlugin; impl Plugin for TilesPlugin { - fn build(&self, app: &mut bevy::prelude::App) { - if !app.is_plugin_added::() { - app.add_plugins(Aery); - } - } + fn build(&self, app: &mut bevy::prelude::App) {} } diff --git a/crates/bevy_tiles/src/maps.rs b/crates/bevy_tiles/src/maps.rs index 0408f05..05b0a50 100644 --- a/crates/bevy_tiles/src/maps.rs +++ b/crates/bevy_tiles/src/maps.rs @@ -13,8 +13,12 @@ pub trait TileMapLabel: Send + Sync + 'static { const CHUNK_SIZE: usize; } +/// Marks an entity as being part of the tilemap. +/// # Note +/// Manually updating this value, adding it, or removing it from an entity may +/// cause issues, please only mutate map information via commands. #[derive(Component)] -pub(crate) struct MapLabel(PhantomData); +pub struct MapLabel(PhantomData); impl Default for MapLabel { fn default() -> Self { @@ -29,12 +33,20 @@ impl Default for MapLabel { #[derive(Component)] pub struct TileMap { pub(crate) chunks: HashMap, Entity>, + /// The size of a chunk in one direction. + pub chunk_size: usize, } -impl Default for TileMap { - fn default() -> Self { +impl TileMap { + pub(crate) fn with_chunk_size(chunk_size: usize) -> Self { Self { chunks: Default::default(), + chunk_size, } } + + /// Get readonly access to the chunk table + pub fn get_chunks(&self) -> &HashMap, Entity> { + &self.chunks + } } diff --git a/crates/bevy_tiles/src/tiles.rs b/crates/bevy_tiles/src/tiles.rs index d3e93cb..e5d5e6c 100644 --- a/crates/bevy_tiles/src/tiles.rs +++ b/crates/bevy_tiles/src/tiles.rs @@ -1,4 +1,3 @@ -use aery::prelude::*; use bevy::prelude::*; use std::ops::Deref; @@ -40,6 +39,12 @@ impl TileCoord { } } +impl From<[isize; N]> for TileCoord { + fn from(value: [isize; N]) -> Self { + Self(value) + } +} + impl Deref for TileCoord { type Target = [isize; N]; @@ -48,7 +53,13 @@ impl Deref for TileCoord { } } -/// An aery relation on tiles that point towards the chunk they are a part of. -#[derive(Relation)] -#[aery(Recursive)] -pub struct InChunk(std::marker::PhantomData); +/// A relation on tiles that point towards the chunk they are a part of. +#[derive(Component)] +pub struct InChunk(pub(crate) Entity); + +impl InChunk { + /// Get the referenced chunk entity. + pub fn get(&self) -> Entity { + self.0 + } +} diff --git a/crates/bevy_tiles/src/tiles/tile_query.rs b/crates/bevy_tiles/src/tiles/tile_query.rs index 4db0c0b..b6e810b 100644 --- a/crates/bevy_tiles/src/tiles/tile_query.rs +++ b/crates/bevy_tiles/src/tiles/tile_query.rs @@ -1,6 +1,5 @@ use std::ops::{Deref, DerefMut}; -use aery::prelude::*; use bevy::{ ecs::{ query::{ReadOnlyWorldQuery, With, WorldQuery}, @@ -30,8 +29,8 @@ where Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static, { - tile_q: Query<'w, 's, Q, (F, Relations>)>, - chunk_q: Query<'w, 's, &'static Chunk, Relations>>, + tile_q: Query<'w, 's, Q, (F, With, With>)>, + chunk_q: Query<'w, 's, &'static Chunk, (With, With>)>, map_q: Query<'w, 's, &'static TileMap, With>>, } @@ -41,7 +40,7 @@ where Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static, { - type Target = Query<'w, 's, Q, (F, Relations>)>; + type Target = Query<'w, 's, Q, (F, With, With>)>; fn deref(&self) -> &Self::Target { &self.tile_q diff --git a/crates/bevy_tiles_render/Cargo.toml b/crates/bevy_tiles_render/Cargo.toml index c6c032b..20f1f21 100644 --- a/crates/bevy_tiles_render/Cargo.toml +++ b/crates/bevy_tiles_render/Cargo.toml @@ -19,18 +19,20 @@ opt-level = 1 opt-level = 3 [dependencies] -aery = "0.5.1" -bevy = { version = "0.12", default-features = false, features = [ +bevy = { workspace = true, features = [ "bevy_core_pipeline", "bevy_render", "bevy_asset", "bevy_sprite", ] } bevy_tiles = {path = "../bevy_tiles"} +crossbeam = {workspace = true} +dashmap = {workspace = true} [dev-dependencies] -rstest = "0.18.2" -bevy = {version = "0.12", default-features = true} +rstest = {workspace = true} +bevy = {workspace = true, default-features = true} +rand = {workspace = true} -[lints.clippy] -all = "deny" \ No newline at end of file +[lints] +workspace = true \ No newline at end of file diff --git a/crates/bevy_tiles_render/assets/block.png b/crates/bevy_tiles_render/assets/block.png new file mode 100644 index 0000000..72700d3 Binary files /dev/null and b/crates/bevy_tiles_render/assets/block.png differ diff --git a/crates/bevy_tiles_render/assets/character.png b/crates/bevy_tiles_render/assets/character.png new file mode 100644 index 0000000..b78bec7 Binary files /dev/null and b/crates/bevy_tiles_render/assets/character.png differ diff --git a/crates/bevy_tiles_render/examples/hello_tile.rs b/crates/bevy_tiles_render/examples/hello_tile.rs new file mode 100644 index 0000000..2e7d5ec --- /dev/null +++ b/crates/bevy_tiles_render/examples/hello_tile.rs @@ -0,0 +1,80 @@ +use std::iter::repeat_with; + +use bevy::{ + app::{App, Startup, Update}, + core_pipeline::core_2d::Camera2dBundle, + diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, + ecs::system::Commands, + math::{Quat, Vec3}, + render::camera::OrthographicProjection, + transform::{ + components::{GlobalTransform, Transform}, + TransformBundle, + }, + DefaultPlugins, +}; +use bevy_tiles::{ + commands::TileCommandExt, coords::CoordIterator, maps::TileMapLabel, TilesPlugin, +}; +use bevy_tiles_render::{ + maps::{TileGridSize, TileMapRenderer, TileMapRenderingBundle, TileSize}, + TilesRenderPlugin, +}; +use rand::Rng; + +fn main() { + App::new() + .add_plugins(( + DefaultPlugins, + FrameTimeDiagnosticsPlugin, + LogDiagnosticsPlugin::default(), + )) + .add_plugins((TilesPlugin, TilesRenderPlugin)) + .add_systems(Startup, spawn) + .add_systems(Update, change_map) + .run(); +} + +struct GameLayer; + +impl TileMapLabel for GameLayer { + const CHUNK_SIZE: usize = 128; +} + +fn spawn(mut commands: Commands) { + let mut tile_commands = commands.tiles::(); + tile_commands.spawn_map(TileMapRenderingBundle { + tile_size: TileSize(16.0), + grid_size: TileGridSize(18.0), + tile_map_renderer: TileMapRenderer { batch_size: 512 }, + ..Default::default() + }); + + commands.spawn(Camera2dBundle { + projection: OrthographicProjection { + scale: 10.0, + far: 1000.0, + near: -1000.0, + ..Default::default() + }, + ..Default::default() + }); +} + +fn change_map(mut commands: Commands) { + let mut rng = rand::thread_rng(); + let changes = rng.gen_range(500..1000); + let mut tile_commands = commands.tiles::(); + + let spawn = rng.gen_bool(0.5); + + let tiles = Vec::from_iter( + repeat_with(|| [rng.gen_range(-1000..1000), rng.gen_range(-1000..1000)]).take(changes), + ); + + if spawn { + tile_commands.spawn_tile_batch(tiles, |_| ()) + } else { + tile_commands.despawn_tile_batch(tiles) + } +} diff --git a/crates/bevy_tiles_render/src/bindings.rs b/crates/bevy_tiles_render/src/bindings.rs new file mode 100644 index 0000000..060d9c8 --- /dev/null +++ b/crates/bevy_tiles_render/src/bindings.rs @@ -0,0 +1,262 @@ +use std::num::NonZeroU64; + +use bevy::{ + ecs::{component::Component, world::FromWorld}, + log::debug, + math::{Affine3, Vec2, Vec4}, + render::{ + render_resource::{ + BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingType, Buffer, BufferAddress, BufferBindingType, + BufferDescriptor, BufferInitDescriptor, BufferUsages, CommandEncoder, ShaderSize, + ShaderStages, ShaderType, StorageBuffer, UniformBuffer, + }, + renderer::{RenderDevice, RenderQueue}, + }, + transform::components::GlobalTransform, +}; + +use crate::{ + buffer_helpers::*, + chunk::{self, internal::ChunkUniforms}, + maps::internal::MapInfo, +}; + +#[derive(Component)] +pub struct ChunkBatchBindGroups { + pub map_bind_group: BindGroup, + pub chunk_bind_group: BindGroup, +} + +/// Contains all the data for an individual chunk that can be +/// consolidated into the batch buffers. +#[derive(Component)] +pub struct ChunkBuffer { + pub chunk_offset: Vec2, + pub tile_instances: GpuStorageBuffer, +} + +impl ChunkBuffer { + pub fn new(chunk_uniforms: &mut ChunkUniforms) -> Self { + Self { + chunk_offset: Vec2::from(&chunk_uniforms.chunk_coord), + tile_instances: GpuStorageBuffer::::from( + chunk_uniforms + .tile_instances + .take() + .expect("Couldn't find TileInstances"), + ), + } + } + + pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { + self.tile_instances.write_buffer(device, queue); + } +} + +#[derive(Component)] +pub struct ChunkBatchBuffer { + total_chunk_size: u64, + batch_size: u64, + pub chunk_offsets: GpuStorageBuffer, + pub tile_instances: Buffer, +} + +impl ChunkBatchBuffer { + pub fn with_size_no_default_values( + batch_size: usize, + chunk_size: usize, + device: &RenderDevice, + ) -> Self { + let total_chunk_size = chunk_size as u64 * chunk_size as u64; + Self { + total_chunk_size, + batch_size: batch_size as u64, + chunk_offsets: GpuStorageBuffer::::default(), + tile_instances: device.create_buffer(&BufferDescriptor { + label: None, + size: total_chunk_size * batch_size as u64 * u32::SHADER_SIZE.get(), + usage: BufferUsages::COPY_DST | BufferUsages::STORAGE, + mapped_at_creation: false, + }), + } + } + + /// # Note + /// after call push, write_buffer needs to be called as well as using the commands + /// from the command encoders to finish the copying. + pub fn push(&mut self, command_encoder: &mut CommandEncoder, chunk_buffer: &ChunkBuffer) { + let index = self.chunk_offsets.push(chunk_buffer.chunk_offset); + command_encoder.copy_buffer_to_buffer( + chunk_buffer.tile_instances.gpu_buffer().unwrap(), + 0, + &self.tile_instances, + index.get() as u64 * self.total_chunk_size * u32::SHADER_SIZE.get(), + self.total_chunk_size * u32::SHADER_SIZE.get(), + ) + } + + pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { + self.chunk_offsets.write_buffer(device, queue); + } + + pub fn bindings(&self) -> BindGroupEntries<2> { + BindGroupEntries::with_indices(( + (0, self.chunk_offsets.binding().unwrap()), + (1, self.tile_instances.as_entire_binding()), + )) + } + + pub fn layout_entries() -> Vec { + vec![ + // off_sets + GpuStorageBuffer::::binding_layout(0, ShaderStages::VERTEX_FRAGMENT), + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: Some(u32::min_size()), + }, + count: None, + }, + ] + } +} + +#[derive(Component)] +pub struct MapBatchBuffer { + chunk_size: UniformBuffer, + tile_size: UniformBuffer, + grid_size: UniformBuffer, + transform: UniformBuffer, +} + +impl MapBatchBuffer { + pub fn new(map_info: &MapInfo) -> Self { + Self { + chunk_size: map_info.chunk_size.into(), + tile_size: map_info.tile_size.0.into(), + grid_size: map_info.grid_size.0.into(), + transform: MapTransformUniform::from(&map_info.transform).into(), + } + } + + pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { + self.chunk_size.write_buffer(device, queue); + self.transform.write_buffer(device, queue); + self.tile_size.write_buffer(device, queue); + self.grid_size.write_buffer(device, queue); + } + + pub fn bindings(&self) -> BindGroupEntries<4> { + BindGroupEntries::with_indices(( + (0, self.transform.binding().unwrap()), + (1, self.chunk_size.binding().unwrap()), + (2, self.tile_size.binding().unwrap()), + (3, self.grid_size.binding().unwrap()), + )) + } + + pub fn layout_entries() -> Vec { + vec![ + // transform + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(MapTransformUniform::SHADER_SIZE), + }, + count: None, + }, + // chunk_size + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(u32::SHADER_SIZE), + }, + count: None, + }, + // tile_size + BindGroupLayoutEntry { + binding: 2, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(f32::SHADER_SIZE), + }, + count: None, + }, + // grid_size + BindGroupLayoutEntry { + binding: 3, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(f32::SHADER_SIZE), + }, + count: None, + }, + ] + } +} + +#[derive(ShaderType, Clone, Default)] +pub struct MapTransformUniform { + // Affine 4x3 matrix transposed to 3x4 + pub transform: [Vec4; 3], + // 3x3 matrix packed in mat2x4 and f32 as: + // [0].xyz, [1].x, + // [1].yz, [2].xy + // [2].z + pub inverse_transpose_model_a: [Vec4; 2], + pub inverse_transpose_model_b: f32, +} + +impl From<&GlobalTransform> for MapTransformUniform { + fn from(value: &GlobalTransform) -> Self { + let affine = Affine3::from(&value.affine()); + let (inverse_transpose_model_a, inverse_transpose_model_b) = affine.inverse_transpose_3x3(); + Self { + transform: affine.to_transpose(), + inverse_transpose_model_a, + inverse_transpose_model_b, + } + } +} + +pub struct ChunkBatchBindGroupLayouts { + pub map_layouts: BindGroupLayout, + pub chunk_layouts: BindGroupLayout, +} + +impl FromWorld for ChunkBatchBindGroupLayouts { + fn from_world(world: &mut bevy::prelude::World) -> Self { + let device = world + .get_resource::() + .expect("No render device found!"); + + let map_layouts = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("bevy_tiles_map_bind_group"), + entries: &MapBatchBuffer::layout_entries(), + }); + + let chunk_layouts = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("bevy_tiles_chunk_bind_group"), + entries: &ChunkBatchBuffer::layout_entries(), + }); + + Self { + map_layouts, + chunk_layouts, + } + } +} diff --git a/crates/bevy_tiles_render/src/buffer_helpers/gpu_storage_buffer.rs b/crates/bevy_tiles_render/src/buffer_helpers/gpu_storage_buffer.rs new file mode 100644 index 0000000..b07df95 --- /dev/null +++ b/crates/bevy_tiles_render/src/buffer_helpers/gpu_storage_buffer.rs @@ -0,0 +1,104 @@ +use std::{marker::PhantomData, mem}; + +use bevy::{ + ecs::{component::Component, system::Resource, world::FromWorld}, + render::{ + render_resource::{ + BindGroupLayoutEntry, BindingResource, BindingType, Buffer, BufferBindingType, + BufferUsages, GpuArrayBufferable, ShaderStages, StorageBuffer, + }, + renderer::{RenderDevice, RenderQueue}, + }, + utils::nonmax::NonMaxU32, +}; + +/// Stores an array of elements to be transferred to the GPU and made accessible to shaders as a read-only array. +/// This is modified from bevy's GpuArrayBuffer +pub struct GpuStorageBuffer { + gpu_buffer: StorageBuffer>, + buffer: Vec, +} + +impl From> for GpuStorageBuffer { + fn from(value: Vec) -> Self { + let mut gpu_buffer: StorageBuffer> = Default::default(); + gpu_buffer.add_usages(BufferUsages::COPY_SRC); + Self { + gpu_buffer, + buffer: value, + } + } +} + +impl Default for GpuStorageBuffer { + fn default() -> Self { + let mut gpu_buffer: StorageBuffer> = Default::default(); + gpu_buffer.add_usages(BufferUsages::COPY_SRC); + Self { + gpu_buffer, + buffer: Default::default(), + } + } +} + +impl GpuStorageBuffer { + pub fn clear(&mut self) { + self.buffer.clear() + } + + pub fn gpu_buffer(&self) -> Option<&Buffer> { + self.gpu_buffer.buffer() + } + + pub fn push(&mut self, value: T) -> NonMaxU32 { + let index = NonMaxU32::new(self.buffer.len() as u32).unwrap(); + self.buffer.push(value); + index + } + + pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { + self.gpu_buffer.set(mem::take(&mut self.buffer)); + self.gpu_buffer.write_buffer(device, queue); + } + + pub fn binding_layout(binding: u32, visibility: ShaderStages) -> BindGroupLayoutEntry { + BindGroupLayoutEntry { + binding, + visibility, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: Some(T::min_size()), + }, + count: None, + } + } + + pub fn binding(&self) -> Option { + self.gpu_buffer.binding() + } + pub fn insert(&mut self, index: usize, value: T) { + *self.buffer.get_mut(index).unwrap() = value; + } + + // Fails if used on the wrong size buffer + /// SAFETY: Use carefully, this is to allow for parallel writes to sections of a buffer. + pub unsafe fn raw_insert(&self, index: usize, value: T) { + let spot: *const T = self.buffer.get(index).unwrap(); + let spot: *mut T = spot as *mut T; + *spot = value; + } +} + +impl GpuStorageBuffer +where + T: Default, +{ + /// Creates a buffer of the given size filled with default values + pub fn with_size(size: usize) -> Self { + Self { + buffer: vec![T::default(); size], + gpu_buffer: Default::default(), + } + } +} diff --git a/crates/bevy_tiles_render/src/buffer_helpers/mod.rs b/crates/bevy_tiles_render/src/buffer_helpers/mod.rs new file mode 100644 index 0000000..0ad7685 --- /dev/null +++ b/crates/bevy_tiles_render/src/buffer_helpers/mod.rs @@ -0,0 +1,3 @@ +pub mod gpu_storage_buffer; + +pub use gpu_storage_buffer::*; diff --git a/crates/bevy_tiles_render/src/chunk.rs b/crates/bevy_tiles_render/src/chunk.rs deleted file mode 100644 index d84ba2a..0000000 --- a/crates/bevy_tiles_render/src/chunk.rs +++ /dev/null @@ -1,495 +0,0 @@ -use std::hash::{Hash, Hasher}; - -use bevy::math::Mat4; -use bevy::prelude::{Resource, Transform}; -use bevy::render::primitives::Aabb; -use bevy::{ - math::{UVec2, UVec3, UVec4, Vec2, Vec3Swizzles, Vec4, Vec4Swizzles}, - prelude::{Component, ComputedVisibility, Entity, GlobalTransform, Mesh, Vec3}, - render::{ - mesh::{GpuBufferInfo, GpuMesh, Indices, VertexAttributeValues}, - render_resource::{BufferInitDescriptor, BufferUsages, ShaderType}, - renderer::RenderDevice, - }, - utils::HashMap, -}; - -use crate::prelude::helpers::transform::{chunk_aabb, chunk_index_to_world_space}; -use crate::render::extract::ExtractedFrustum; -use crate::{ - map::{TilemapSize, TilemapTexture, TilemapType}, - tiles::TilePos, - FrustumCulling, TilemapGridSize, TilemapTileSize, -}; - -#[derive(Resource, Default, Clone, Debug)] -pub struct RenderChunk2dStorage { - chunks: HashMap>, - entity_to_chunk_tile: HashMap, - entity_to_chunk: HashMap, -} - -#[derive(Default, Component, Clone, Copy, Debug)] -pub struct ChunkId(pub UVec3); - -impl RenderChunk2dStorage { - #[allow(clippy::too_many_arguments)] - pub fn get_or_add( - &mut self, - tile_entity: Entity, - tile_pos: UVec2, - chunk_entity: Entity, - position: &UVec4, - chunk_size: UVec2, - mesh_type: TilemapType, - tile_size: TilemapTileSize, - texture_size: Vec2, - spacing: Vec2, - grid_size: TilemapGridSize, - texture: TilemapTexture, - map_size: TilemapSize, - transform: GlobalTransform, - visibility: &ComputedVisibility, - frustum_culling: &FrustumCulling, - ) -> &mut RenderChunk2d { - let pos = position.xyz(); - - self.entity_to_chunk_tile - .insert(tile_entity, (position.w, pos, tile_pos)); - - let chunk_storage = if self.chunks.contains_key(&position.w) { - self.chunks.get_mut(&position.w).unwrap() - } else { - let hash_map = HashMap::default(); - self.chunks.insert(position.w, hash_map); - self.chunks.get_mut(&position.w).unwrap() - }; - - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - position.hash(&mut hasher); - - if chunk_storage.contains_key(&pos) { - chunk_storage.get_mut(&pos).unwrap() - } else { - let chunk = RenderChunk2d::new( - hasher.finish(), - chunk_entity.to_bits(), - &pos, - chunk_size, - mesh_type, - tile_size, - spacing, - grid_size, - texture, - texture_size, - map_size, - transform, - visibility.is_visible(), - **frustum_culling, - ); - self.entity_to_chunk.insert(chunk_entity, pos); - chunk_storage.insert(pos, chunk); - chunk_storage.get_mut(&pos).unwrap() - } - } - - pub fn get(&self, position: &UVec4) -> Option<&RenderChunk2d> { - if let Some(chunk_storage) = self.chunks.get(&position.w) { - return chunk_storage.get(&position.xyz()); - } - None - } - - pub fn get_mut(&mut self, position: &UVec4) -> &mut RenderChunk2d { - let chunk_storage = self.chunks.get_mut(&position.w).unwrap(); - chunk_storage.get_mut(&position.xyz()).unwrap() - } - - pub fn remove_tile_with_entity(&mut self, entity: Entity) { - if let Some((chunk, tile_pos)) = self.get_mut_from_entity(entity) { - chunk.set(&tile_pos.into(), None); - } - - self.entity_to_chunk.remove(&entity); - self.entity_to_chunk_tile.remove(&entity); - } - - pub fn get_mut_from_entity(&mut self, entity: Entity) -> Option<(&mut RenderChunk2d, UVec2)> { - if !self.entity_to_chunk_tile.contains_key(&entity) { - return None; - } - - let (tilemap_id, chunk_pos, tile_pos) = self.entity_to_chunk_tile.get(&entity).unwrap(); - - let chunk_storage = self.chunks.get_mut(tilemap_id).unwrap(); - Some((chunk_storage.get_mut(&chunk_pos.xyz()).unwrap(), *tile_pos)) - } - - pub fn get_chunk_storage(&mut self, position: &UVec4) -> &mut HashMap { - if self.chunks.contains_key(&position.w) { - self.chunks.get_mut(&position.w).unwrap() - } else { - let hash_map = HashMap::default(); - self.chunks.insert(position.w, hash_map); - self.chunks.get_mut(&position.w).unwrap() - } - } - - pub fn remove(&mut self, position: &UVec4) { - let chunk_storage = self.get_chunk_storage(position); - - let pos = position.xyz(); - - chunk_storage.remove(&pos); - } - - pub fn count(&self) -> usize { - self.chunks.len() - } - - pub fn iter(&self) -> impl Iterator { - self.chunks.iter().flat_map(|(_, x)| x.iter().map(|x| x.1)) - } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.chunks - .iter_mut() - .flat_map(|(_, x)| x.iter_mut().map(|x| x.1)) - } - - pub fn remove_map(&mut self, entity: Entity) { - self.chunks.remove(&entity.index()); - } -} - -#[derive(Clone, Copy, Debug)] -pub struct PackedTileData { - pub visible: bool, - pub position: Vec4, - pub texture: Vec4, - pub color: Vec4, -} - -#[derive(Clone, Debug)] -pub struct RenderChunk2d { - pub id: u64, - pub tilemap_id: u64, - /// The index of the chunk. It is equivalent to the position of the chunk in "chunk - /// coordinates". - index: UVec3, - /// The position of this chunk, in world space, - position: Vec2, - /// Size of the chunk, in tiles. - pub size_in_tiles: UVec2, - /// [`TilemapSize`] of the map this chunk belongs to. - pub map_size: TilemapSize, - /// [`TilemapType`] of the map this chunk belongs to. - map_type: TilemapType, - /// The grid size of the map this chunk belongs to. - pub grid_size: TilemapGridSize, - /// The tile size of the map this chunk belongs to. - pub tile_size: TilemapTileSize, - /// The [`Aabb`] of this chunk, based on the map type, grid size, and tile size. It is not - /// transformed by the `global_transform` or [`local_transform`] - aabb: Aabb, - local_transform: Transform, - /// The [`GlobalTransform`] of this chunk, stored as a [`Transform`]. - global_transform: Transform, - /// The product of the local and global transforms. - transform: Transform, - /// The matrix computed from this chunk's `transform`. - transform_matrix: Mat4, - pub spacing: Vec2, - pub tiles: Vec>, - pub texture: TilemapTexture, - pub texture_size: Vec2, - pub mesh: Mesh, - pub gpu_mesh: Option, - pub dirty_mesh: bool, - pub visible: bool, - pub frustum_culling: bool, -} - -impl RenderChunk2d { - #[allow(clippy::too_many_arguments)] - pub fn new( - id: u64, - tilemap_id: u64, - index: &UVec3, - size_in_tiles: UVec2, - map_type: TilemapType, - tile_size: TilemapTileSize, - spacing: Vec2, - grid_size: TilemapGridSize, - texture: TilemapTexture, - texture_size: Vec2, - map_size: TilemapSize, - global_transform: GlobalTransform, - visible: bool, - frustum_culling: bool, - ) -> Self { - let position = chunk_index_to_world_space(index.xy(), size_in_tiles, &grid_size, &map_type); - let local_transform = Transform::from_translation(position.extend(0.0)); - let global_transform: Transform = global_transform.into(); - let transform = local_transform * global_transform; - let transform_matrix = transform.compute_matrix(); - let aabb = chunk_aabb(size_in_tiles, &grid_size, &tile_size, &map_type); - Self { - dirty_mesh: true, - gpu_mesh: None, - id, - index: *index, - position, - size_in_tiles, - map_size, - map_type, - grid_size, - tile_size, - aabb, - local_transform, - global_transform, - transform, - transform_matrix, - mesh: Mesh::new(bevy::render::render_resource::PrimitiveTopology::TriangleList), - spacing, - texture_size, - texture, - tilemap_id, - tiles: vec![None; (size_in_tiles.x * size_in_tiles.y) as usize], - visible, - frustum_culling, - } - } - - pub fn get(&self, tile_pos: &TilePos) -> &Option { - &self.tiles[tile_pos.to_index(&self.size_in_tiles.into())] - } - - pub fn get_mut(&mut self, tile_pos: &TilePos) -> &mut Option { - self.dirty_mesh = true; - &mut self.tiles[tile_pos.to_index(&self.size_in_tiles.into())] - } - - pub fn set(&mut self, tile_pos: &TilePos, tile: Option) { - self.dirty_mesh = true; - self.tiles[tile_pos.to_index(&self.size_in_tiles.into())] = tile; - } - - pub fn get_index(&self) -> UVec3 { - self.index - } - - pub fn get_map_type(&self) -> TilemapType { - self.map_type - } - - pub fn get_transform(&self) -> Transform { - self.transform - } - - pub fn get_transform_matrix(&self) -> Mat4 { - self.transform_matrix - } - - pub fn intersects_frustum(&self, frustum: &ExtractedFrustum) -> bool { - frustum.intersects_obb(&self.aabb, &self.transform_matrix) - } - - pub fn update_geometry( - &mut self, - global_transform: Transform, - grid_size: TilemapGridSize, - tile_size: TilemapTileSize, - map_type: TilemapType, - ) { - let mut dirty_local_transform = false; - - if self.grid_size != grid_size || self.tile_size != tile_size || self.map_type != map_type { - self.grid_size = grid_size; - self.map_type = map_type; - self.tile_size = tile_size; - - self.position = chunk_index_to_world_space( - self.index.xy(), - self.size_in_tiles, - &self.grid_size, - &self.map_type, - ); - - self.local_transform = Transform::from_translation(self.position.extend(0.0)); - dirty_local_transform = true; - - self.aabb = chunk_aabb( - self.size_in_tiles, - &self.grid_size, - &self.tile_size, - &self.map_type, - ); - } - - let mut dirty_global_transform = false; - if self.global_transform != global_transform { - self.global_transform = global_transform; - dirty_global_transform = true; - } - - if dirty_local_transform || dirty_global_transform { - self.transform = global_transform * self.local_transform; - self.transform_matrix = self.transform.compute_matrix(); - } - } - - pub fn prepare(&mut self, device: &RenderDevice) { - if self.dirty_mesh { - let size = ((self.size_in_tiles.x * self.size_in_tiles.y) * 4) as usize; - let mut positions: Vec<[f32; 4]> = Vec::with_capacity(size); - let mut textures: Vec<[f32; 4]> = Vec::with_capacity(size); - let mut colors: Vec<[f32; 4]> = Vec::with_capacity(size); - let mut indices: Vec = - Vec::with_capacity(((self.size_in_tiles.x * self.size_in_tiles.y) * 6) as usize); - - let mut i = 0; - - // Convert tile into mesh data. - for tile in self.tiles.iter().filter_map(|x| x.as_ref()) { - if !tile.visible { - continue; - } - - let position: [f32; 4] = tile.position.to_array(); - positions.extend( - [ - // X, Y - position, - // X, Y + 1 - //[tile_pos.x, tile_pos.y + 1.0, animation_speed], - position, - // X + 1, Y + 1 - //[tile_pos.x + 1.0, tile_pos.y + 1.0, animation_speed], - position, - // X + 1, Y - //[tile_pos.x + 1.0, tile_pos.y, animation_speed], - position, - ] - .into_iter(), - ); - - let color: [f32; 4] = tile.color.into(); - colors.extend([color, color, color, color].into_iter()); - - // flipping and rotation packed in bits - // bit 0 : flip_x - // bit 1 : flip_y - // bit 2 : flip_d (anti diagonal) - - // let tile_flip_bits = - // tile.flip_x as i32 | (tile.flip_y as i32) << 1 | (tile.flip_d as i32) << 2; - - //let texture: [f32; 4] = tile.texture.xyxx().into(); - let texture: [f32; 4] = tile.texture.to_array(); - textures.extend([texture, texture, texture, texture].into_iter()); - - indices.extend_from_slice(&[i, i + 2, i + 1, i, i + 3, i + 2]); - i += 4; - } - - self.mesh.insert_attribute( - crate::render::ATTRIBUTE_POSITION, - VertexAttributeValues::Float32x4(positions), - ); - self.mesh.insert_attribute( - crate::render::ATTRIBUTE_TEXTURE, - VertexAttributeValues::Float32x4(textures), - ); - self.mesh.insert_attribute( - crate::render::ATTRIBUTE_COLOR, - VertexAttributeValues::Float32x4(colors), - ); - self.mesh.set_indices(Some(Indices::U32(indices))); - - let vertex_buffer_data = self.mesh.get_vertex_buffer_data(); - let vertex_buffer = device.create_buffer_with_data(&BufferInitDescriptor { - usage: BufferUsages::VERTEX, - label: Some("Mesh Vertex Buffer"), - contents: &vertex_buffer_data, - }); - - let buffer_info = - self.mesh - .get_index_buffer_bytes() - .map_or(GpuBufferInfo::NonIndexed {}, |data| { - GpuBufferInfo::Indexed { - buffer: device.create_buffer_with_data(&BufferInitDescriptor { - usage: BufferUsages::INDEX, - contents: data, - label: Some("Mesh Index Buffer"), - }), - count: self.mesh.indices().unwrap().len() as u32, - index_format: self.mesh.indices().unwrap().into(), - } - }); - - let mesh_vertex_buffer_layout = self.mesh.get_mesh_vertex_buffer_layout(); - self.gpu_mesh = Some(GpuMesh { - vertex_buffer, - vertex_count: self.mesh.count_vertices() as u32, - buffer_info, - morph_targets: None, - layout: mesh_vertex_buffer_layout, - primitive_topology: bevy::render::render_resource::PrimitiveTopology::TriangleList, - }); - self.dirty_mesh = false; - } - } -} - -// Used to transfer info to the GPU for tile building. -#[derive(Debug, Default, Copy, Component, Clone, ShaderType)] -pub struct TilemapUniformData { - pub texture_size: Vec2, - pub tile_size: Vec2, - pub grid_size: Vec2, - pub spacing: Vec2, - pub chunk_pos: Vec2, - pub map_size: Vec2, - pub time: f32, - pub pad: Vec3, -} - -impl From<&RenderChunk2d> for TilemapUniformData { - fn from(chunk: &RenderChunk2d) -> Self { - let chunk_ix: Vec2 = chunk.index.xy().as_vec2(); - let chunk_size: Vec2 = chunk.size_in_tiles.as_vec2(); - let map_size: Vec2 = chunk.map_size.into(); - let tile_size: Vec2 = chunk.tile_size.into(); - Self { - texture_size: chunk.texture_size, - tile_size, - grid_size: chunk.grid_size.into(), - spacing: chunk.spacing, - chunk_pos: chunk_ix * chunk_size, - map_size: map_size * tile_size, - time: 0.0, - pad: Vec3::ZERO, - } - } -} - -impl From<&mut RenderChunk2d> for TilemapUniformData { - fn from(chunk: &mut RenderChunk2d) -> Self { - let chunk_pos: Vec2 = chunk.index.xy().as_vec2(); - let chunk_size: Vec2 = chunk.size_in_tiles.as_vec2(); - let map_size: Vec2 = chunk.map_size.into(); - let tile_size: Vec2 = chunk.tile_size.into(); - Self { - texture_size: chunk.texture_size, - tile_size, - grid_size: chunk.grid_size.into(), - spacing: chunk.spacing, - chunk_pos: chunk_pos * chunk_size, - map_size: map_size * tile_size, - time: 0.0, - pad: Vec3::ZERO, - } - } -} diff --git a/crates/bevy_tiles_render/src/chunk/internal.rs b/crates/bevy_tiles_render/src/chunk/internal.rs new file mode 100644 index 0000000..d8c42d6 --- /dev/null +++ b/crates/bevy_tiles_render/src/chunk/internal.rs @@ -0,0 +1,27 @@ +use bevy::{ + ecs::{component::Component, entity::Entity, system::Resource}, + prelude::{Deref, DerefMut}, +}; +use bevy_tiles::chunks::ChunkCoord; +use dashmap::DashMap; + +use crate::bindings::ChunkBuffer; + +#[derive(Default, Resource, Deref, DerefMut)] +pub struct SavedChunks(DashMap); + +#[derive(Component, Deref)] +pub struct BatchSize(pub u32); + +/// Holds a reference to the batch this chunk is in +#[derive(Component, Deref, Clone)] +pub struct ChunkBatch(pub Entity); + +/// Data needed to render a chunk in the batched chunk rendering pipeline. +/// This needs to be able to be instantiated in the extract stage and should +/// not have knowledge of the batch it's in. +#[derive(Debug, Component)] +pub struct ChunkUniforms { + pub chunk_coord: ChunkCoord, + pub tile_instances: Option>, +} diff --git a/crates/bevy_tiles_render/src/chunk/mod.rs b/crates/bevy_tiles_render/src/chunk/mod.rs new file mode 100644 index 0000000..ef77e6c --- /dev/null +++ b/crates/bevy_tiles_render/src/chunk/mod.rs @@ -0,0 +1,4 @@ +//! Components that can affect chunk rendering + +/// Components used by rendering pipeline +pub(crate) mod internal; diff --git a/crates/bevy_tiles_render/src/cleanup.rs b/crates/bevy_tiles_render/src/cleanup.rs new file mode 100644 index 0000000..7dd5900 --- /dev/null +++ b/crates/bevy_tiles_render/src/cleanup.rs @@ -0,0 +1,38 @@ +use bevy::ecs::{entity::Entity, query::With, system::ResMut, world::World}; + +use crate::{ + bindings::ChunkBuffer, + chunk::internal::SavedChunks, + maps::internal::{MapInfo, SavedMaps}, +}; + +pub fn save_chunks(mut world: &mut World) { + // Get the map id's we need + let map_ids: Vec = world + .query_filtered::>() + .iter(world) + .collect(); + + // Get the chunk id's we need + let chunk_ids: Vec = world + .query_filtered::>() + .iter(world) + .collect(); + + // Remove the map entities and put them into our hashmap + let mut saved_maps = SavedMaps::default(); + for map_id in map_ids.into_iter() { + let mut map = world.entity_mut(map_id); + saved_maps.insert(map_id, map.take::().unwrap()); + } + + // Remove the chunk entities and put them into our hashmap + let mut saved_chunks = SavedChunks::default(); + for chunk_id in chunk_ids.into_iter() { + let mut chunk = world.entity_mut(chunk_id); + saved_chunks.insert(chunk_id, chunk.take::().unwrap()); + } + + world.insert_resource(saved_maps); + world.insert_resource(saved_chunks); +} diff --git a/crates/bevy_tiles_render/src/draw.rs b/crates/bevy_tiles_render/src/draw.rs index 20808d4..5b4caeb 100644 --- a/crates/bevy_tiles_render/src/draw.rs +++ b/crates/bevy_tiles_render/src/draw.rs @@ -1,206 +1,120 @@ -use std::marker::PhantomData; - +//! Contains commands given to the Transparent2D render step +//! to draw tiles. +//! +//! In order to batch draw calls of batched chunk draws (used for larger scenes if for lower memory situations) +//! the draw commands consist of copying individual chunk buffers to the various instance buffers before +//! issuing a draw call for a given batch of chunks. use bevy::{ core_pipeline::core_2d::Transparent2d, - ecs::system::{ - lifetimeless::{Read, SQuery, SRes}, - SystemParamItem, + ecs::{ + query::ROQueryItem, + system::{lifetimeless::Read, SystemParamItem}, }, - math::UVec4, - prelude::Handle, - render::{ - mesh::GpuBufferInfo, - render_phase::{RenderCommand, RenderCommandResult, TrackedRenderPass}, - render_resource::PipelineCache, - view::ViewUniformOffset, + log::debug, + render::render_phase::{ + PhaseItem, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, }, + sprite::SetMesh2dViewBindGroup, }; -use crate::map::TilemapId; -use crate::TilemapTexture; +use crate::{bindings::ChunkBatchBindGroups, chunk::internal::BatchSize, maps::internal::MapInfo}; -use super::{ - chunk::{ChunkId, RenderChunk2dStorage, TilemapUniformData}, - material::{MaterialTilemap, RenderMaterialsTilemap}, - prepare::MeshUniform, - queue::{ImageBindGroups, TilemapViewBindGroup, TransformBindGroup}, - DynamicUniformIndex, -}; +pub type DrawChunks = ( + SetItemPipeline, + SetMesh2dViewBindGroup<0>, + SetMapBindGroup<1>, + SetChunkBindGroup<2>, + DrawChunkBatch, +); -pub struct SetMeshViewBindGroup; -impl RenderCommand for SetMeshViewBindGroup { +pub struct SetMapBindGroup; +impl RenderCommand for SetMapBindGroup { type Param = (); - type ViewWorldQuery = (Read, Read); - type ItemWorldQuery = (); - #[inline] - fn render<'w>( - _item: &Transparent2d, - (view_uniform, pbr_view_bind_group): (&'w ViewUniformOffset, &'w TilemapViewBindGroup), - _entity: (), - _param: (), - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - pass.set_bind_group(I, &pbr_view_bind_group.value, &[view_uniform.offset]); - RenderCommandResult::Success - } -} - -pub struct SetTransformBindGroup; -impl RenderCommand for SetTransformBindGroup { - type Param = SRes; type ViewWorldQuery = (); - type ItemWorldQuery = ( - Read>, - Read>, - ); + + type ItemWorldQuery = Read; + #[inline] fn render<'w>( - _item: &Transparent2d, + item: &Transparent2d, _view: (), - (transform_index, tilemap_index): ( - &'w DynamicUniformIndex, - &'w DynamicUniformIndex, - ), - transform_bind_group: SystemParamItem<'w, '_, Self::Param>, + bind_groups: ROQueryItem<'w, Self::ItemWorldQuery>, + _: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { + debug!("Setting Chunk Map Level Bind Groups"); + + let mut dynamic_offsets: [u32; 1] = Default::default(); + let mut offset_count = 0; + if let Some(dynamic_offset) = item.dynamic_offset() { + dynamic_offsets[offset_count] = dynamic_offset.get(); + offset_count += 1; + } + pass.set_bind_group( I, - &transform_bind_group.into_inner().value, - &[transform_index.index(), tilemap_index.index()], + &bind_groups.map_bind_group, + &dynamic_offsets[..offset_count], ); - RenderCommandResult::Success } } -pub struct SetTextureBindGroup; -impl RenderCommand for SetTextureBindGroup { - type Param = SRes; +pub struct SetChunkBindGroup; +impl RenderCommand for SetChunkBindGroup { + type Param = (); + type ViewWorldQuery = (); - type ItemWorldQuery = Read; - #[inline] - fn render<'w>( - _item: &Transparent2d, - _view: (), - texture: &'w TilemapTexture, - image_bind_groups: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let bind_group = image_bind_groups.into_inner().values.get(texture).unwrap(); - pass.set_bind_group(I, bind_group, &[]); - RenderCommandResult::Success - } -} + type ItemWorldQuery = Read; -pub struct SetItemPipeline; -impl RenderCommand for SetItemPipeline { - type Param = SRes; - type ViewWorldQuery = (); - type ItemWorldQuery = (); #[inline] fn render<'w>( item: &Transparent2d, _view: (), - _entity: (), - pipeline_cache: SystemParamItem<'w, '_, Self::Param>, + bind_groups: ROQueryItem<'w, Self::ItemWorldQuery>, + _: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - if let Some(pipeline) = pipeline_cache - .into_inner() - .get_render_pipeline(item.pipeline) - { - pass.set_render_pipeline(pipeline); - RenderCommandResult::Success - } else { - RenderCommandResult::Failure - } - } -} - -pub type DrawTilemap = ( - SetItemPipeline, - SetMeshViewBindGroup<0>, - SetTransformBindGroup<1>, - SetTextureBindGroup<2>, - DrawMesh, -); + debug!("Setting Chunk Level Bind Groups"); -pub type DrawTilemapMaterial = ( - SetItemPipeline, - SetMeshViewBindGroup<0>, - SetTransformBindGroup<1>, - SetTextureBindGroup<2>, - SetMaterialBindGroup, - DrawMesh, -); - -pub struct SetMaterialBindGroup(PhantomData); -impl RenderCommand - for SetMaterialBindGroup -{ - type Param = (SRes>, SQuery<&'static Handle>); - type ViewWorldQuery = (); - type ItemWorldQuery = Read; - #[inline] - fn render<'w>( - _item: &Transparent2d, - _view: (), - id: &'w TilemapId, - (material_bind_groups, material_handles): SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - if let Ok(material_handle) = material_handles.get(id.0) { - let bind_group = material_bind_groups - .into_inner() - .get(material_handle) - .unwrap(); - pass.set_bind_group(I, &bind_group.bind_group, &[]); + let mut dynamic_offsets: [u32; 1] = Default::default(); + let mut offset_count = 0; + if let Some(dynamic_offset) = item.dynamic_offset() { + dynamic_offsets[offset_count] = dynamic_offset.get(); + offset_count += 1; } + pass.set_bind_group( + I, + &bind_groups.chunk_bind_group, + &dynamic_offsets[..offset_count], + ); RenderCommandResult::Success } } -pub struct DrawMesh; -impl RenderCommand for DrawMesh { - type Param = SRes; +pub struct DrawChunkBatch; +impl RenderCommand for DrawChunkBatch { + type Param = (); + type ViewWorldQuery = (); - type ItemWorldQuery = (Read, Read); + + type ItemWorldQuery = (Read, Read); + #[inline] fn render<'w>( - _item: &Transparent2d, - _view: (), - (chunk_id, tilemap_id): (&'w ChunkId, &'w TilemapId), - chunk_storage: SystemParamItem<'w, '_, Self::Param>, + item: &Transparent2d, + _view: ROQueryItem<'w, Self::ViewWorldQuery>, + (map_info, batch_size): ROQueryItem<'w, Self::ItemWorldQuery>, + _: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - if let Some(chunk) = chunk_storage.into_inner().get(&UVec4::new( - chunk_id.0.x, - chunk_id.0.y, - chunk_id.0.z, - tilemap_id.0.index(), - )) { - if let Some(gpu_mesh) = &chunk.gpu_mesh { - pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); - match &gpu_mesh.buffer_info { - GpuBufferInfo::Indexed { - buffer, - index_format, - count, - } => { - pass.set_index_buffer(buffer.slice(..), 0, *index_format); - pass.draw_indexed(0..*count, 0, 0..1); - } - GpuBufferInfo::NonIndexed {} => { - pass.draw(0..gpu_mesh.vertex_count, 0..1); - } - } - } - } - + pass.draw( + 0..(map_info.chunk_size * map_info.chunk_size * 6), + 0..**batch_size, + ); RenderCommandResult::Success } } diff --git a/crates/bevy_tiles_render/src/extract.rs b/crates/bevy_tiles_render/src/extract.rs index a65fe10..ba8d2ce 100644 --- a/crates/bevy_tiles_render/src/extract.rs +++ b/crates/bevy_tiles_render/src/extract.rs @@ -1,404 +1,136 @@ -use bevy::prelude::Res; -use bevy::prelude::Time; -use bevy::render::primitives::{Aabb, Frustum}; -use bevy::render::render_resource::FilterMode; -use bevy::render::render_resource::TextureFormat; -use bevy::{math::Vec4, prelude::*, render::Extract, utils::HashMap}; +use bevy::{ + ecs::{ + entity::Entity, + query::{Changed, With}, + system::{Commands, Query, ResMut}, + }, + render::Extract, + transform::components::GlobalTransform, + utils::hashbrown::HashMap, +}; +use bevy_tiles::{ + chunks::{Chunk, ChunkCoord, InMap}, + maps::TileMap, + tiles::{InChunk, TileIndex}, +}; +use crossbeam::queue::ArrayQueue; -use crate::prelude::TilemapGridSize; -use crate::render::{DefaultSampler, SecondsSinceStartup}; -use crate::tiles::AnimatedTile; -use crate::tiles::TilePosOld; use crate::{ - map::{ - TilemapId, TilemapSize, TilemapSpacing, TilemapTexture, TilemapTextureSize, - TilemapTileSize, TilemapType, + chunk::internal::{ChunkUniforms, SavedChunks}, + maps::{ + internal::{MapChunks, MapInfo, SavedMaps}, + TileGridSize, TileMapRenderer, TileSize, }, - tiles::{TileColor, TileFlip, TilePos, TileTextureIndex, TileVisible}, - FrustumCulling, }; -use super::RemovedMapEntity; -use super::{chunk::PackedTileData, RemovedTileEntity}; - -#[derive(Component)] -pub struct ExtractedTile { - pub entity: Entity, - pub position: TilePos, - pub old_position: TilePosOld, - pub tile: PackedTileData, - pub tilemap_id: TilemapId, -} - -#[derive(Bundle)] -pub struct ExtractedTileBundle { - tile: ExtractedTile, -} - -#[derive(Component)] -pub struct ExtractedRemovedTile { - pub entity: Entity, -} - -#[derive(Bundle)] -pub struct ExtractedRemovedTileBundle { - tile: ExtractedRemovedTile, -} - -#[derive(Component)] -pub struct ExtractedRemovedMap { - pub entity: Entity, -} - -#[derive(Bundle)] -pub struct ExtractedRemovedMapBundle { - map: ExtractedRemovedMap, -} - -#[derive(Bundle)] -pub struct ExtractedTilemapBundle { - transform: GlobalTransform, - tile_size: TilemapTileSize, - grid_size: TilemapGridSize, - texture_size: TilemapTextureSize, - spacing: TilemapSpacing, - map_type: TilemapType, - texture: TilemapTexture, - map_size: TilemapSize, - visibility: ComputedVisibility, - frustum_culling: FrustumCulling, -} - -#[derive(Component)] -pub(crate) struct ExtractedTilemapTexture { - pub tilemap_id: TilemapId, - pub tile_size: TilemapTileSize, - pub texture_size: TilemapTextureSize, - pub tile_spacing: TilemapSpacing, - pub tile_count: u32, - pub texture: TilemapTexture, - pub filtering: FilterMode, - pub format: TextureFormat, -} - -impl ExtractedTilemapTexture { - pub fn new( - tilemap_entity: Entity, - texture: TilemapTexture, - tile_size: TilemapTileSize, - tile_spacing: TilemapSpacing, - filtering: FilterMode, - image_assets: &Res>, - ) -> ExtractedTilemapTexture { - let (tile_count, texture_size, format) = match &texture { - TilemapTexture::Single(handle) => { - let image = image_assets.get(handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); - let texture_size: TilemapTextureSize = image.size().into(); - let tile_count_x = ((texture_size.x) / (tile_size.x + tile_spacing.x)).floor(); - let tile_count_y = ((texture_size.y) / (tile_size.y + tile_spacing.y)).floor(); - ( - (tile_count_x * tile_count_y) as u32, - texture_size, - image.texture_descriptor.format, - ) - } - #[cfg(not(feature = "atlas"))] - TilemapTexture::Vector(handles) => { - for handle in handles { - let image = image_assets.get(handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); - let this_tile_size: TilemapTileSize = image.size().into(); - if this_tile_size != tile_size { - panic!( - "Expected all provided image assets to have size {tile_size:?}, \ - but found image with size: {this_tile_size:?}", - ); - } - } - let first_format = image_assets - .get(handles.first().unwrap()) - .unwrap() - .texture_descriptor - .format; - - for handle in handles { - let image = image_assets.get(handle).unwrap(); - if image.texture_descriptor.format != first_format { - panic!("Expected all provided image assets to have a format of: {:?} but found image with format: {:?}", first_format, image.texture_descriptor.format); - } - } - - (handles.len() as u32, tile_size.into(), first_format) - } - #[cfg(not(feature = "atlas"))] - TilemapTexture::TextureContainer(image_handle) => { - let image = image_assets.get(image_handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); - let tile_size: TilemapTileSize = image.size().into(); - ( - image.texture_descriptor.array_layer_count(), - tile_size.into(), - image.texture_descriptor.format, - ) - } - }; - - ExtractedTilemapTexture { - tilemap_id: TilemapId(tilemap_entity), - texture, - tile_size, - tile_spacing, - filtering, - tile_count, - texture_size, - format, - } - } -} - -#[derive(Bundle)] -pub(crate) struct ExtractedTilemapTextureBundle { - data: ExtractedTilemapTexture, -} - -#[derive(Component, Debug)] -pub struct ExtractedFrustum { - frustum: Frustum, -} - -impl ExtractedFrustum { - pub fn intersects_obb(&self, aabb: &Aabb, transform_matrix: &Mat4) -> bool { - self.frustum - .intersects_obb(aabb, transform_matrix, true, false) - } -} - -#[allow(clippy::too_many_arguments)] -pub fn extract( +pub fn extract_chunks( mut commands: Commands, - default_image_settings: Res, - changed_tiles_query: Extract< - Query< - ( - Entity, - &TilePos, - &TilePosOld, - &TilemapId, - &TileTextureIndex, - &TileVisible, - &TileFlip, - &TileColor, - Option<&AnimatedTile>, - ), - Or<( - Changed, - Changed, - Changed, - Changed, - Changed, - )>, - >, - >, - tilemap_query: Extract< + mut saved_maps: ResMut, + mut saved_chunks: ResMut, + maps: Extract< Query<( Entity, - &GlobalTransform, - &TilemapTileSize, - &TilemapSpacing, - &TilemapGridSize, - &TilemapType, - &TilemapTexture, - &TilemapSize, - &ComputedVisibility, - &FrustumCulling, + &TileMap, + &TileMapRenderer, + Option<&GlobalTransform>, + Option<&TileSize>, + Option<&TileGridSize>, )>, >, - changed_tilemap_query: Extract< + changed_maps: Extract< Query< - Entity, - Or<( - Added, - Changed, + (), + ( + Changed, + Changed, Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - Changed, - )>, + Changed, + Changed, + ), >, >, - camera_query: Extract>>, - images: Extract>>, - time: Extract>, + chunks: Extract>, + changed_chunks: Extract, Changed, Changed)>>, + tiles: Extract>>, + changed_tiles: Extract>>, ) { - let mut extracted_tiles = Vec::new(); - let mut extracted_tilemaps = HashMap::default(); - let mut extracted_tilemap_textures = Vec::new(); - // Process all tiles - for ( - entity, - tile_pos, - tile_pos_old, - tilemap_id, - tile_texture, - visible, - flip, - color, - animated, - ) in changed_tiles_query.iter() - { - // flipping and rotation packed in bits - // bit 0 : flip_x - // bit 1 : flip_y - // bit 2 : flip_d (anti diagonal) - let tile_flip_bits = flip.x as i32 | (flip.y as i32) << 1 | (flip.d as i32) << 2; - - let mut position = Vec4::new(tile_pos.x as f32, tile_pos.y as f32, 0.0, 0.0); - let mut texture = Vec4::new(tile_texture.0 as f32, tile_flip_bits as f32, 0.0, 0.0); - if let Some(animation_data) = animated { - position.z = animation_data.speed; - texture.z = animation_data.start as f32; - texture.w = animation_data.end as f32; - } else { - texture.z = tile_texture.0 as f32; - texture.w = tile_texture.0 as f32; + let maps_iter = maps.iter(); + let mut extracted_maps = Vec::with_capacity(maps_iter.len()); + let mut map_chunks: HashMap<_, _> = + HashMap::::with_capacity(maps_iter.len()); + + for (map_id, map, renderer, transform, tile_size, grid_size) in maps_iter { + map_chunks.insert(map_id, MapChunks::default()); + if let Some(saved_map) = saved_maps.remove(&map_id) { + if !changed_maps.contains(map_id) { + extracted_maps.push(saved_map); + continue; + } } - - let tile = PackedTileData { - visible: visible.0, - position, - texture, - color: color.0.into(), - }; - - let data = tilemap_query.get(tilemap_id.0).unwrap(); - - extracted_tilemaps.insert( - data.0, - ( - data.0, - ExtractedTilemapBundle { - transform: *data.1, - tile_size: *data.2, - texture_size: TilemapTextureSize::default(), - spacing: *data.3, - grid_size: *data.4, - map_type: *data.5, - texture: data.6.clone_weak(), - map_size: *data.7, - visibility: data.8.clone(), - frustum_culling: *data.9, - }, - ), - ); - - extracted_tiles.push(( - entity, - ExtractedTileBundle { - tile: ExtractedTile { - entity, - position: *tile_pos, - old_position: *tile_pos_old, - tile, - tilemap_id: *tilemap_id, - }, + let transform = transform.cloned().unwrap_or_default(); + let tile_size = tile_size.cloned().unwrap_or_default(); + let grid_size = grid_size.cloned().unwrap_or_default(); + extracted_maps.push(( + map_id, + MapInfo { + chunk_size: map.chunk_size as u32, + tile_map_renderer: renderer.clone(), + tile_size, + grid_size, + transform, }, )); } + commands.insert_or_spawn_batch(extracted_maps); - for tilemap_entity in changed_tilemap_query.iter() { - if let Ok(data) = tilemap_query.get(tilemap_entity) { - extracted_tilemaps.insert( - data.0, - ( - data.0, - ExtractedTilemapBundle { - transform: *data.1, - tile_size: *data.2, - texture_size: TilemapTextureSize::default(), - spacing: *data.3, - grid_size: *data.4, - map_type: *data.5, - texture: data.6.clone_weak(), - map_size: *data.7, - visibility: data.8.clone(), - frustum_culling: *data.9, - }, - ), - ); - } - } - - let extracted_tilemaps: Vec<(Entity, ExtractedTilemapBundle)> = - extracted_tilemaps.drain().map(|kv| kv.1).collect(); - - // Extracts tilemap textures. - for (entity, _, tile_size, tile_spacing, _, _, texture, _, _, _) in tilemap_query.iter() { - if texture.verify_ready(&images) { - extracted_tilemap_textures.push(( - entity, - ExtractedTilemapTextureBundle { - data: ExtractedTilemapTexture::new( - entity, - texture.clone_weak(), - *tile_size, - *tile_spacing, - default_image_settings.0.min_filter, - &images, - ), - }, - )) - } - } - - for (entity, frustum) in camera_query.iter() { - commands - .get_or_spawn(entity) - .insert(ExtractedFrustum { frustum: *frustum }); + let chunks_len = chunks.iter().len(); + if chunks_len == 0 { + return; } + let extracted_chunks = ArrayQueue::new(chunks_len); + let extracted_saved_chunks = ArrayQueue::new(chunks_len); + let chunk_edges = ArrayQueue::new(chunks_len); + + changed_tiles.iter().for_each(|in_chunk| { + saved_chunks.remove(&in_chunk.get()); + }); + + chunks + .par_iter() + .for_each(|(chunk_id, in_map, chunk, chunk_coord)| { + map_chunks.get(&in_map.get()).unwrap().push(chunk_id); + chunk_edges.push((chunk_id, in_map.clone())); + + // TODO: Check if it's changed + if let Some(chunk) = saved_chunks.remove(&chunk_id) { + if !changed_chunks.contains(chunk_id) { + extracted_saved_chunks.push(chunk); + return; + } + } - commands.insert_or_spawn_batch(extracted_tiles); - commands.insert_or_spawn_batch(extracted_tilemaps); - commands.insert_or_spawn_batch(extracted_tilemap_textures); - commands.insert_resource(SecondsSinceStartup(time.elapsed_seconds_f64() as f32)); -} + let mut extracted_tile_instances = Vec::with_capacity(chunk.total_size()); -pub fn extract_removal( - mut commands: Commands, - removed_tiles_query: Extract>, - removed_maps_query: Extract>, -) { - let mut removed_tiles: Vec<(Entity, ExtractedRemovedTileBundle)> = Vec::new(); - for entity in removed_tiles_query.iter() { - removed_tiles.push(( - entity.0, - ExtractedRemovedTileBundle { - tile: ExtractedRemovedTile { entity: entity.0 }, - }, - )); - } - - commands.insert_or_spawn_batch(removed_tiles); + for tile in chunk.get_tiles() { + if tile.and_then(|tile_id| tiles.get(tile_id).ok()).is_some() { + extracted_tile_instances.push(1); + } else { + extracted_tile_instances.push(0); + } + } - let mut removed_maps: Vec<(Entity, ExtractedRemovedMapBundle)> = Vec::new(); - for entity in removed_maps_query.iter() { - removed_maps.push(( - entity.0, - ExtractedRemovedMapBundle { - map: ExtractedRemovedMap { entity: entity.0 }, - }, - )); - } + extracted_chunks + .push(( + chunk_id, + ChunkUniforms { + chunk_coord: *chunk_coord, + tile_instances: Some(extracted_tile_instances), + }, + )) + .expect("Failed to extract chunk: {:?}"); + }); - commands.insert_or_spawn_batch(removed_maps); + commands.insert_or_spawn_batch(extracted_saved_chunks); + commands.insert_or_spawn_batch(extracted_chunks); + commands.insert_or_spawn_batch(map_chunks); } diff --git a/crates/bevy_tiles_render/src/include_shader.rs b/crates/bevy_tiles_render/src/include_shader.rs deleted file mode 100644 index abac0f0..0000000 --- a/crates/bevy_tiles_render/src/include_shader.rs +++ /dev/null @@ -1,18 +0,0 @@ -use regex::Regex; - -pub fn include_shader(shader_includes: Vec<&'static str>, shader: &'static str) -> String { - let mut final_shader = String::new(); - - final_shader.push_str(shader); - - let re = Regex::new(r"#include \d").unwrap(); - - for capture in re.captures_iter(shader) { - let include_text: String = capture.get(0).unwrap().as_str().into(); - let include_id_text = include_text.replace("#include ", ""); - let include_id: usize = include_id_text.parse::().unwrap(); - final_shader = final_shader.replace(&include_text, shader_includes[include_id]); - } - - final_shader -} diff --git a/crates/bevy_tiles_render/src/lib.rs b/crates/bevy_tiles_render/src/lib.rs index 7fabbd6..4e40e16 100644 --- a/crates/bevy_tiles_render/src/lib.rs +++ b/crates/bevy_tiles_render/src/lib.rs @@ -1,332 +1,93 @@ -use std::marker::PhantomData; - use bevy::{ - asset::load_internal_asset, + app::Plugin, + asset::{load_internal_asset, Handle}, core_pipeline::core_2d::Transparent2d, - prelude::*, - reflect::TypeUuid, + ecs::schedule::{apply_deferred, IntoSystemConfigs}, render::{ - mesh::MeshVertexAttribute, render_phase::AddRenderCommand, - render_resource::{ - FilterMode, SamplerDescriptor, SpecializedRenderPipelines, VertexFormat, - }, - Render, RenderApp, RenderSet, + render_resource::{Shader, SpecializedRenderPipelines}, + ExtractSchedule, Render, RenderApp, RenderSet, }, }; -#[cfg(not(feature = "atlas"))] -use bevy::render::renderer::RenderDevice; -use bevy_tiles::tiles::{TileCoord, TileMapLabel}; - -use crate::prelude::{TilemapRenderSettings, TilemapTexture}; -use crate::render::{ - material::{MaterialTilemapPlugin, StandardTilemapMaterial}, - prepare::{MeshUniformResource, TilemapUniformResource}, -}; +use chunk::internal::SavedChunks; +use cleanup::save_chunks; +use extract::extract_chunks; +use maps::internal::SavedMaps; +use prepare::{create_bind_groups, prepare_chunk_batch, prepare_chunks}; +use queue::{create_chunk_batches, queue_chunks}; -use self::{ - chunk::RenderChunk2dStorage, - draw::DrawTilemap, - pipeline::{TilemapPipeline, TILEMAP_SHADER_FRAGMENT, TILEMAP_SHADER_VERTEX}, - queue::ImageBindGroups, -}; +use crate::{draw::DrawChunks, pipeline::TilesChunkPipeline}; -mod chunk; +mod bindings; +mod buffer_helpers; +pub mod chunk; +mod cleanup; mod draw; mod extract; -mod include_shader; -pub mod material; +pub mod maps; mod pipeline; -pub(crate) mod prepare; +mod prepare; mod queue; +pub mod tiles; -#[cfg(not(feature = "atlas"))] -mod texture_array_cache; - -#[cfg(not(feature = "atlas"))] -use self::extract::ExtractedTilemapTexture; -#[cfg(not(feature = "atlas"))] -pub(crate) use self::texture_array_cache::TextureArrayCache; - -/// The default chunk_size (in tiles) used per mesh. -const CHUNK_SIZE_2D: UVec2 = UVec2::from_array([64, 64]); - -#[derive(Copy, Clone, Debug, Component)] -pub(crate) struct ExtractedFilterMode(FilterMode); - -#[derive(Resource, Deref)] -pub struct DefaultSampler(SamplerDescriptor<'static>); - -/// Sorts chunks using Y sort during render. -/// -/// Initialized from [`TilemapRenderSettings`](crate::map::TilemapRenderSettings) resource, if -/// provided. Otherwise, defaults to false. -#[derive(Resource, Debug, Copy, Clone, Deref)] -pub struct RenderYSort(bool); - -pub struct TilemapRenderingPlugin; - -#[derive(Resource, Default, Deref, DerefMut)] -pub struct SecondsSinceStartup(pub f32); - -pub const COLUMN_EVEN_HEX: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7704924705970804993); -pub const COLUMN_HEX: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 11710877199891728627); -pub const COLUMN_ODD_HEX: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 6706359414982022142); -pub const COMMON: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 15420881977837458322); -pub const DIAMOND_ISO: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 6710251300621614118); -pub const MESH_OUTPUT: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2707251459590872179); -pub const ROW_EVEN_HEX: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7149718726759672633); -pub const ROW_HEX: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 5506589682629967569); -pub const ROW_ODD_HEX: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 13608302855194400936); -pub const STAGGERED_ISO: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 9802843761568314416); -pub const SQUARE: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7333720254399106799); -pub const TILEMAP_VERTEX_OUTPUT: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 6104533649830094529); - -impl Plugin for TilemapRenderingPlugin { - fn build(&self, app: &mut App) { - #[cfg(not(feature = "atlas"))] - app.add_systems(Update, set_texture_to_copy_src); - - app.add_systems(First, clear_removed); - app.add_systems(PostUpdate, (removal_helper_tilemap, removal_helper)); - - app.add_plugins(MaterialTilemapPlugin::::default()); - - app.world - .resource_mut::>() - .set_untracked( - Handle::::default(), - StandardTilemapMaterial::default(), - ); - } - - fn finish(&self, app: &mut App) { - // Extract the chunk size from the TilemapRenderSettings used to initialize the - // ChunkCoordinate resource to insert into the render pipeline - let (chunk_size, y_sort) = { - match app.world.get_resource::() { - Some(settings) => (settings.render_chunk_size, settings.y_sort), - None => (CHUNK_SIZE_2D, false), - } - }; - - let sampler = app.get_added_plugins::().first().map_or_else( - || ImagePlugin::default_nearest().default_sampler, - |plugin| plugin.default_sampler.clone(), - ); - - load_internal_asset!( - app, - COLUMN_EVEN_HEX, - "shaders/column_even_hex.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!( - app, - COLUMN_HEX, - "shaders/column_hex.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!( - app, - COLUMN_ODD_HEX, - "shaders/column_odd_hex.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!(app, COMMON, "shaders/common.wgsl", Shader::from_wgsl); - - load_internal_asset!( - app, - DIAMOND_ISO, - "shaders/diamond_iso.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!( - app, - ROW_EVEN_HEX, - "shaders/row_even_hex.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!(app, ROW_HEX, "shaders/row_hex.wgsl", Shader::from_wgsl); - - load_internal_asset!( - app, - ROW_ODD_HEX, - "shaders/row_odd_hex.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!(app, ROW_HEX, "shaders/row_hex.wgsl", Shader::from_wgsl); - - load_internal_asset!( - app, - MESH_OUTPUT, - "shaders/mesh_output.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!(app, SQUARE, "shaders/square.wgsl", Shader::from_wgsl); +const TILES_VERT: Handle = Handle::weak_from_u128(163058266501073814892310220797241232500); +const TILES_FRAG: Handle = Handle::weak_from_u128(163058266501073814892310220797241232501); - load_internal_asset!( - app, - STAGGERED_ISO, - "shaders/staggered_iso.wgsl", - Shader::from_wgsl - ); +pub struct TilesRenderPlugin; - load_internal_asset!( - app, - TILEMAP_VERTEX_OUTPUT, - "shaders/tilemap_vertex_output.wgsl", - Shader::from_wgsl - ); +impl Plugin for TilesRenderPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + let render_app = app.get_sub_app_mut(RenderApp).expect("No RenderApp found!"); - load_internal_asset!( - app, - TILEMAP_SHADER_VERTEX, - "shaders/tilemap_vertex.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!( - app, - TILEMAP_SHADER_FRAGMENT, - "shaders/tilemap_fragment.wgsl", - Shader::from_wgsl - ); - - let render_app = match app.get_sub_app_mut(RenderApp) { - Ok(render_app) => render_app, - Err(_) => return, - }; - - render_app.init_resource::(); - - #[cfg(not(feature = "atlas"))] - render_app - .init_resource::() - .add_systems(Render, prepare_textures.in_set(RenderSet::Prepare)); + render_app.init_resource::(); + render_app.init_resource::(); + // Respawn chunks that we saved from the last frame + // Copy over tile data render_app - .insert_resource(DefaultSampler(sampler)) - .insert_resource(RenderChunkSize(chunk_size)) - .insert_resource(RenderYSort(y_sort)) - .insert_resource(RenderChunk2dStorage::default()) - .insert_resource(SecondsSinceStartup(0.0)) - .add_systems( - ExtractSchedule, - (extract::extract, extract::extract_removal), - ) + .add_systems(ExtractSchedule, extract_chunks) .add_systems( Render, - (prepare::prepare_removal, prepare::prepare) + (create_chunk_batches, apply_deferred, queue_chunks) .chain() - .in_set(RenderSet::Prepare), + .in_set(RenderSet::Queue), ) .add_systems( Render, - queue::queue_transform_bind_group.in_set(RenderSet::Queue), + ( + prepare_chunks, + apply_deferred, + prepare_chunk_batch, + apply_deferred, + create_bind_groups, + ) + .chain() + .in_set(RenderSet::Prepare), ) - .init_resource::() - .init_resource::>() - .init_resource::() - .init_resource::(); - - render_app.add_render_command::(); - } -} - -pub fn set_texture_to_copy_src( - mut images: ResMut>, - texture_query: Query<&TilemapTexture>, -) { - // quick and dirty, run this for all textures anytime a texture component is created. - for texture in texture_query.iter() { - texture.set_images_to_copy_src(&mut images) - } -} - -/// Stores the index of a uniform inside of [`ComponentUniforms`]. -#[derive(Component)] -pub struct DynamicUniformIndex { - index: u32, - marker: PhantomData, -} - -impl DynamicUniformIndex { - #[inline] - pub fn index(&self) -> u32 { - self.index + .add_systems(Render, (save_chunks).in_set(RenderSet::Cleanup)); } -} -pub const ATTRIBUTE_POSITION: MeshVertexAttribute = - MeshVertexAttribute::new("Position", 229221259, VertexFormat::Float32x4); -pub const ATTRIBUTE_TEXTURE: MeshVertexAttribute = - MeshVertexAttribute::new("Texture", 222922753, VertexFormat::Float32x4); -pub const ATTRIBUTE_COLOR: MeshVertexAttribute = - MeshVertexAttribute::new("Color", 231497124, VertexFormat::Float32x4); + fn finish(&self, app: &mut bevy::prelude::App) { + let render_app = app.get_sub_app_mut(RenderApp).expect("No RenderApp found!"); -#[derive(Component)] -pub struct RemovedTileEntity(pub Entity); - -#[derive(Component)] -pub struct RemovedMapEntity(pub Entity); - -fn removal_helper(mut commands: Commands, mut removed_query: RemovedComponents) { - for entity in removed_query.iter() { - commands.spawn(RemovedTileEntity(entity)); - } -} - -fn removal_helper_tilemap(mut commands: Commands, mut removed_query: RemovedComponents) { - for entity in removed_query.iter() { - commands.spawn(RemovedMapEntity(entity)); - } -} - -fn clear_removed( - mut commands: Commands, - removed_query: Query>, - removed_map_query: Query>, -) { - for entity in removed_query.iter() { - commands.entity(entity).despawn(); - } + render_app.add_render_command::(); + render_app + .init_resource::() + .init_resource::>(); - for entity in removed_map_query.iter() { - commands.entity(entity).despawn(); - } -} + load_internal_asset!( + app, + TILES_FRAG, + "shaders/tiles_frag.wgsl", + Shader::from_wgsl + ); -#[cfg(not(feature = "atlas"))] -fn prepare_textures( - render_device: Res, - mut texture_array_cache: ResMut, - extracted_tilemap_textures: Query<&ExtractedTilemapTexture>, - render_images: Res>, -) { - for extracted_texture in extracted_tilemap_textures.iter() { - texture_array_cache.add_extracted_texture(extracted_texture); + load_internal_asset!( + app, + TILES_VERT, + "shaders/tiles_vert.wgsl", + Shader::from_wgsl + ); } - - texture_array_cache.prepare(&render_device, &render_images); } diff --git a/crates/bevy_tiles_render/src/maps/internal.rs b/crates/bevy_tiles_render/src/maps/internal.rs new file mode 100644 index 0000000..0edf1ce --- /dev/null +++ b/crates/bevy_tiles_render/src/maps/internal.rs @@ -0,0 +1,24 @@ +use bevy::{ + ecs::{component::Component, entity::Entity, system::Resource}, + prelude::{Deref, DerefMut}, + transform::components::GlobalTransform, +}; +use crossbeam::queue::SegQueue; +use dashmap::DashMap; + +use super::{TileGridSize, TileMapRenderer, TileSize}; + +#[derive(Default, Resource, Deref, DerefMut)] +pub struct SavedMaps(DashMap); + +#[derive(Default, Component, Deref, DerefMut)] +pub struct MapChunks(SegQueue); + +#[derive(Clone, Component)] +pub struct MapInfo { + pub chunk_size: u32, + pub tile_map_renderer: TileMapRenderer, + pub tile_size: TileSize, + pub grid_size: TileGridSize, + pub transform: GlobalTransform, +} diff --git a/crates/bevy_tiles_render/src/maps/mod.rs b/crates/bevy_tiles_render/src/maps/mod.rs new file mode 100644 index 0000000..0f2f879 --- /dev/null +++ b/crates/bevy_tiles_render/src/maps/mod.rs @@ -0,0 +1,54 @@ +//! Components that can affect map rendering + +use bevy::{ + ecs::{bundle::Bundle, component::Component}, + prelude::Deref, + transform::components::{GlobalTransform, Transform}, +}; + +pub(crate) mod internal; + +#[derive(Bundle, Default)] +pub struct TileMapRenderingBundle { + pub tile_map_renderer: TileMapRenderer, + pub tile_size: TileSize, + pub grid_size: TileGridSize, + pub transform: Transform, + pub global_transform: GlobalTransform, +} + +/// Marks a tilemap as renderable, without this it cannot be rendered. +#[derive(Clone, Component)] +pub struct TileMapRenderer { + pub batch_size: u32, +} + +impl Default for TileMapRenderer { + fn default() -> Self { + Self { batch_size: 128 } + } +} + +/// The size of a tile in pixels. +#[derive(Clone, Deref, Component)] +pub struct TileSize(pub f32); + +/// Defaults to 16 pixels +impl Default for TileSize { + fn default() -> Self { + Self(16.0) + } +} +/// The size of a tile grid in pixels. +/// # Example +/// A [`TileSize`] of 16 with a [`GridSize`] of 18 would lead to a 2 pixel gap between tiles. +/// A [`TileSize`] of 16 with a [`GridSize`] of 14 would lead to a 2 pixel overlap between tiles. +#[derive(Clone, Deref, Component)] +pub struct TileGridSize(pub f32); + +/// Defaults to 16 pixels +impl Default for TileGridSize { + fn default() -> Self { + Self(16.0) + } +} diff --git a/crates/bevy_tiles_render/src/material.rs b/crates/bevy_tiles_render/src/material.rs deleted file mode 100644 index f164369..0000000 --- a/crates/bevy_tiles_render/src/material.rs +++ /dev/null @@ -1,533 +0,0 @@ -use bevy::{ - core_pipeline::core_2d::Transparent2d, - prelude::*, - reflect::{TypePath, TypeUuid}, - render::{ - extract_component::ExtractComponentPlugin, - globals::GlobalsBuffer, - render_asset::{PrepareAssetSet, RenderAssets}, - render_phase::{AddRenderCommand, DrawFunctions, RenderPhase}, - render_resource::{ - AsBindGroup, AsBindGroupError, BindGroup, BindGroupDescriptor, BindGroupEntry, - BindGroupLayout, BindingResource, OwnedBindingResource, PipelineCache, - RenderPipelineDescriptor, ShaderRef, SpecializedRenderPipeline, - SpecializedRenderPipelines, - }, - renderer::RenderDevice, - texture::FallbackImage, - view::{ExtractedView, ViewUniforms, VisibleEntities}, - Extract, Render, RenderApp, RenderSet, - }, - utils::{FloatOrd, HashMap, HashSet}, -}; -use std::{hash::Hash, marker::PhantomData}; - -#[cfg(not(feature = "atlas"))] -use bevy::render::renderer::RenderQueue; - -use crate::prelude::TilemapId; - -use super::{ - chunk::{ChunkId, RenderChunk2dStorage}, - draw::DrawTilemapMaterial, - pipeline::{TilemapPipeline, TilemapPipelineKey}, - queue::{ImageBindGroups, TilemapViewBindGroup}, - RenderYSort, -}; - -#[cfg(not(feature = "atlas"))] -pub(crate) use super::TextureArrayCache; - -pub trait MaterialTilemap: - AsBindGroup + Send + Sync + Clone + TypeUuid + TypePath + Sized + 'static -{ - /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader - /// will be used. - fn vertex_shader() -> ShaderRef { - ShaderRef::Default - } - - /// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the default mesh fragment shader - /// will be used. - fn fragment_shader() -> ShaderRef { - ShaderRef::Default - } - - /// Customizes the default [`RenderPipelineDescriptor`]. - #[allow(unused_variables)] - #[inline] - fn specialize(descriptor: &mut RenderPipelineDescriptor, key: MaterialTilemapKey) {} -} - -pub struct MaterialTilemapKey { - pub tilemap_pipeline_key: TilemapPipelineKey, - pub bind_group_data: M::Data, -} - -impl Eq for MaterialTilemapKey where M::Data: PartialEq {} - -impl PartialEq for MaterialTilemapKey -where - M::Data: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.tilemap_pipeline_key == other.tilemap_pipeline_key - && self.bind_group_data == other.bind_group_data - } -} - -impl Clone for MaterialTilemapKey -where - M::Data: Clone, -{ - fn clone(&self) -> Self { - Self { - tilemap_pipeline_key: self.tilemap_pipeline_key, - bind_group_data: self.bind_group_data.clone(), - } - } -} - -impl Hash for MaterialTilemapKey -where - M::Data: Hash, -{ - fn hash(&self, state: &mut H) { - self.tilemap_pipeline_key.hash(state); - self.bind_group_data.hash(state); - } -} - -pub struct MaterialTilemapPlugin(PhantomData); - -impl Default for MaterialTilemapPlugin { - fn default() -> Self { - Self(Default::default()) - } -} - -impl Plugin for MaterialTilemapPlugin -where - M::Data: PartialEq + Eq + Hash + Clone, -{ - fn build(&self, app: &mut App) { - app.add_asset::() - .add_plugins(ExtractComponentPlugin::>::extract_visible()); - } - - fn finish(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .add_render_command::>() - .init_resource::>() - .init_resource::>() - .init_resource::>() - .init_resource::>>() - .add_systems(ExtractSchedule, extract_materials_tilemap::) - .add_systems( - Render, - prepare_materials_tilemap:: - .in_set(RenderSet::Prepare) - .after(PrepareAssetSet::PreAssetPrepare), - ) - .add_systems( - Render, - queue_material_tilemap_meshes::.in_set(RenderSet::Queue), - ); - } - } -} - -pub struct PreparedMaterialTilemap { - pub bindings: Vec, - pub bind_group: BindGroup, - pub key: T::Data, -} - -#[derive(Resource)] -struct ExtractedMaterialsTilemap { - extracted: Vec<(Handle, M)>, - removed: Vec>, -} - -impl Default for ExtractedMaterialsTilemap { - fn default() -> Self { - Self { - extracted: Default::default(), - removed: Default::default(), - } - } -} - -#[derive(Resource)] -pub struct MaterialTilemapPipeline { - pub tilemap_pipeline: TilemapPipeline, - pub material_tilemap_layout: BindGroupLayout, - pub vertex_shader: Option>, - pub fragment_shader: Option>, - marker: PhantomData, -} - -impl Clone for MaterialTilemapPipeline { - fn clone(&self) -> Self { - Self { - tilemap_pipeline: self.tilemap_pipeline.clone(), - material_tilemap_layout: self.material_tilemap_layout.clone(), - vertex_shader: self.vertex_shader.clone(), - fragment_shader: self.fragment_shader.clone(), - marker: PhantomData, - } - } -} - -impl SpecializedRenderPipeline for MaterialTilemapPipeline -where - M::Data: PartialEq + Eq + Hash + Clone, -{ - type Key = MaterialTilemapKey; - - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.tilemap_pipeline.specialize(key.tilemap_pipeline_key); - if let Some(vertex_shader) = &self.vertex_shader { - descriptor.vertex.shader = vertex_shader.clone(); - } - - if let Some(fragment_shader) = &self.fragment_shader { - descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); - } - descriptor.layout = vec![ - self.tilemap_pipeline.view_layout.clone(), - self.tilemap_pipeline.mesh_layout.clone(), - self.tilemap_pipeline.material_layout.clone(), - self.material_tilemap_layout.clone(), - ]; - - M::specialize(&mut descriptor, key); - descriptor - } -} - -impl FromWorld for MaterialTilemapPipeline { - fn from_world(world: &mut World) -> Self { - let asset_server = world.resource::(); - let render_device = world.resource::(); - let material_tilemap_layout = M::bind_group_layout(render_device); - - MaterialTilemapPipeline { - tilemap_pipeline: world.resource::().clone(), - material_tilemap_layout, - vertex_shader: match M::vertex_shader() { - ShaderRef::Default => None, - ShaderRef::Handle(handle) => Some(handle), - ShaderRef::Path(path) => Some(asset_server.load(path)), - }, - fragment_shader: match M::fragment_shader() { - ShaderRef::Default => None, - ShaderRef::Handle(handle) => Some(handle), - ShaderRef::Path(path) => Some(asset_server.load(path)), - }, - marker: PhantomData, - } - } -} - -/// Stores all prepared representations of [`Material2d`] assets for as long as they exist. -#[derive(Resource, Deref, DerefMut)] -pub struct RenderMaterialsTilemap( - HashMap, PreparedMaterialTilemap>, -); - -impl Default for RenderMaterialsTilemap { - fn default() -> Self { - Self(Default::default()) - } -} - -/// This system extracts all created or modified assets of the corresponding [`Material2d`] type -/// into the "render world". -fn extract_materials_tilemap( - mut commands: Commands, - mut events: Extract>>, - assets: Extract>>, -) { - let mut changed_assets = HashSet::default(); - let mut removed = Vec::new(); - for event in events.iter() { - match event { - AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { - changed_assets.insert(handle.clone_weak()); - } - AssetEvent::Removed { handle } => { - changed_assets.remove(handle); - removed.push(handle.clone_weak()); - } - } - } - - let mut extracted_assets = Vec::new(); - for handle in changed_assets.drain() { - if let Some(asset) = assets.get(&handle) { - extracted_assets.push((handle, asset.clone())); - } - } - - commands.insert_resource(ExtractedMaterialsTilemap { - extracted: extracted_assets, - removed, - }); -} - -/// All [`Material2d`] values of a given type that should be prepared next frame. -pub struct PrepareNextFrameMaterials { - assets: Vec<(Handle, M)>, -} - -impl Default for PrepareNextFrameMaterials { - fn default() -> Self { - Self { - assets: Default::default(), - } - } -} - -/// This system prepares all assets of the corresponding [`Material2d`] type -/// which where extracted this frame for the GPU. -fn prepare_materials_tilemap( - mut prepare_next_frame: Local>, - mut extracted_assets: ResMut>, - mut render_materials: ResMut>, - render_device: Res, - images: Res>, - fallback_image: Res, - pipeline: Res>, -) { - let queued_assets = std::mem::take(&mut prepare_next_frame.assets); - for (handle, material) in queued_assets { - match prepare_material_tilemap( - &material, - &render_device, - &images, - &fallback_image, - &pipeline, - ) { - Ok(prepared_asset) => { - render_materials.insert(handle, prepared_asset); - } - Err(AsBindGroupError::RetryNextUpdate) => { - prepare_next_frame.assets.push((handle, material)); - } - } - } - - for removed in std::mem::take(&mut extracted_assets.removed) { - render_materials.remove(&removed); - } - - for (handle, material) in std::mem::take(&mut extracted_assets.extracted) { - match prepare_material_tilemap( - &material, - &render_device, - &images, - &fallback_image, - &pipeline, - ) { - Ok(prepared_asset) => { - render_materials.insert(handle, prepared_asset); - } - Err(AsBindGroupError::RetryNextUpdate) => { - prepare_next_frame.assets.push((handle, material)); - } - } - } -} - -fn prepare_material_tilemap( - material: &M, - render_device: &RenderDevice, - images: &RenderAssets, - fallback_image: &FallbackImage, - pipeline: &MaterialTilemapPipeline, -) -> Result, AsBindGroupError> { - let prepared = material.as_bind_group( - &pipeline.material_tilemap_layout, - render_device, - images, - fallback_image, - )?; - Ok(PreparedMaterialTilemap { - bindings: prepared.bindings, - bind_group: prepared.bind_group, - key: prepared.data, - }) -} - -#[allow(clippy::too_many_arguments)] -pub fn queue_material_tilemap_meshes( - mut commands: Commands, - y_sort: Res, - chunk_storage: Res, - transparent_2d_draw_functions: Res>, - render_device: Res, - (tilemap_pipeline, material_tilemap_pipeline, mut material_pipelines): ( - Res, - Res>, - ResMut>>, - ), - pipeline_cache: Res, - view_uniforms: Res, - gpu_images: Res>, - msaa: Res, - globals_buffer: Res, - mut image_bind_groups: ResMut, - (standard_tilemap_meshes, materials): ( - Query<(Entity, &ChunkId, &Transform, &TilemapId)>, - Query<&Handle>, - ), - mut views: Query<( - Entity, - &ExtractedView, - &VisibleEntities, - &mut RenderPhase, - )>, - render_materials: Res>, - #[cfg(not(feature = "atlas"))] (mut texture_array_cache, render_queue): ( - ResMut, - Res, - ), -) where - M::Data: PartialEq + Eq + Hash + Clone, -{ - #[cfg(not(feature = "atlas"))] - texture_array_cache.queue(&render_device, &render_queue, &gpu_images); - - if standard_tilemap_meshes.is_empty() { - return; - } - - if let (Some(view_binding), Some(globals)) = ( - view_uniforms.uniforms.binding(), - globals_buffer.buffer.binding(), - ) { - for (entity, view, visible_entities, mut transparent_phase) in views.iter_mut() { - let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: view_binding.clone(), - }, - BindGroupEntry { - binding: 1, - resource: globals.clone(), - }, - ], - label: Some("tilemap_view_bind_group"), - layout: &tilemap_pipeline.view_layout, - }); - - commands.entity(entity).insert(TilemapViewBindGroup { - value: view_bind_group, - }); - - let draw_tilemap = transparent_2d_draw_functions - .read() - .get_id::>() - .unwrap(); - - for (entity, chunk_id, transform, tilemap_id) in standard_tilemap_meshes.iter() { - if !visible_entities - .entities - .iter() - .any(|&entity| entity.index() == tilemap_id.0.index()) - { - continue; - } - - let Ok(material_handle) = materials.get(tilemap_id.0) else { - continue; - }; - let Some(material) = render_materials.get(material_handle) else { - continue; - }; - - if let Some(chunk) = chunk_storage.get(&UVec4::new( - chunk_id.0.x, - chunk_id.0.y, - chunk_id.0.z, - tilemap_id.0.index(), - )) { - #[cfg(not(feature = "atlas"))] - if !texture_array_cache.contains(&chunk.texture) { - continue; - } - - #[cfg(feature = "atlas")] - if gpu_images.get(chunk.texture.image_handle()).is_none() { - continue; - } - - image_bind_groups - .values - .entry(chunk.texture.clone_weak()) - .or_insert_with(|| { - #[cfg(not(feature = "atlas"))] - let gpu_image = texture_array_cache.get(&chunk.texture); - #[cfg(feature = "atlas")] - let gpu_image = gpu_images.get(chunk.texture.image_handle()).unwrap(); - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView( - &gpu_image.texture_view, - ), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&gpu_image.sampler), - }, - ], - label: Some("sprite_material_bind_group"), - layout: &tilemap_pipeline.material_layout, - }) - }); - - let key = TilemapPipelineKey { - msaa: msaa.samples(), - map_type: chunk.get_map_type(), - hdr: view.hdr, - }; - - let pipeline_id = material_pipelines.specialize( - &pipeline_cache, - &material_tilemap_pipeline, - MaterialTilemapKey { - tilemap_pipeline_key: key, - bind_group_data: material.key.clone(), - }, - ); - let z = if **y_sort { - transform.translation.z - + (1.0 - - (transform.translation.y - / (chunk.map_size.y as f32 * chunk.tile_size.y))) - } else { - transform.translation.z - }; - transparent_phase.add(Transparent2d { - entity, - draw_function: draw_tilemap, - pipeline: pipeline_id, - sort_key: FloatOrd(z), - batch_range: None, - }); - } - } - } - } -} - -#[derive(AsBindGroup, TypeUuid, Debug, Clone, Default, TypePath)] -#[uuid = "d6f8aeb8-510c-499a-9c0b-38551ae0b72a"] -pub struct StandardTilemapMaterial {} - -impl MaterialTilemap for StandardTilemapMaterial {} diff --git a/crates/bevy_tiles_render/src/pipeline.rs b/crates/bevy_tiles_render/src/pipeline.rs index 6752b4c..30cdb0d 100644 --- a/crates/bevy_tiles_render/src/pipeline.rs +++ b/crates/bevy_tiles_render/src/pipeline.rs @@ -1,251 +1,101 @@ use bevy::{ - prelude::{Component, FromWorld, HandleUntyped, Resource, Shader, World}, - reflect::TypeUuid, + ecs::{ + system::Resource, + world::{FromWorld, World}, + }, render::{ - globals::GlobalsUniform, render_resource::{ - BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, - BlendComponent, BlendFactor, BlendOperation, BlendState, BufferBindingType, - ColorTargetState, ColorWrites, Face, FragmentState, FrontFace, MultisampleState, - PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, - SamplerBindingType, ShaderStages, ShaderType, SpecializedRenderPipeline, TextureFormat, - TextureSampleType, TextureViewDimension, VertexBufferLayout, VertexFormat, VertexState, - VertexStepMode, + BindGroupLayout, BlendState, ColorTargetState, ColorWrites, Face, FragmentState, + FrontFace, MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology, + RenderPipelineDescriptor, SpecializedRenderPipeline, TextureFormat, VertexBufferLayout, + VertexFormat, VertexState, VertexStepMode, }, renderer::RenderDevice, texture::BevyDefault, - view::{ViewTarget, ViewUniform}, + view::ViewTarget, }, + sprite::{Mesh2dPipeline, Mesh2dPipelineKey}, }; -use crate::map::{HexCoordSystem, IsoCoordSystem, TilemapType}; - -use super::{chunk::TilemapUniformData, prepare::MeshUniform}; - -pub const TILEMAP_SHADER_VERTEX: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 8094008129742001941); -pub const TILEMAP_SHADER_FRAGMENT: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 5716002228110903793); +use crate::{ + bindings::{ChunkBatchBindGroupLayouts, MapTransformUniform}, + TILES_FRAG, TILES_VERT, +}; -#[derive(Clone, Resource)] -pub struct TilemapPipeline { - pub view_layout: BindGroupLayout, - pub material_layout: BindGroupLayout, - pub mesh_layout: BindGroupLayout, +#[derive(Resource)] +pub struct TilesChunkPipeline { + pub mesh2d_pipeline: Mesh2dPipeline, + pub chunk_batch_bind_groups: ChunkBatchBindGroupLayouts, } -impl FromWorld for TilemapPipeline { +impl FromWorld for TilesChunkPipeline { fn from_world(world: &mut World) -> Self { - let world = world.cell(); - let render_device = world.get_resource::().unwrap(); - - let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[ - // View - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(ViewUniform::min_size()), - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::VERTEX_FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: Some(GlobalsUniform::min_size()), - }, - count: None, - }, - ], - label: Some("tilemap_view_layout"), - }); - - let mesh_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[ - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - // TODO: change this to MeshUniform::std140_size_static once crevice fixes this! - // Context: https://github.com/LPGhatguy/crevice/issues/29 - min_binding_size: Some(MeshUniform::min_size()), - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::VERTEX_FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(TilemapUniformData::min_size()), - }, - count: None, - }, - ], - label: Some("tilemap_mesh_layout"), - }); - - #[cfg(not(feature = "atlas"))] - let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[ - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - multisampled: false, - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2Array, - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("tilemap_material_layout"), - }); - - #[cfg(feature = "atlas")] - let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[ - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - multisampled: false, - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("tilemap_material_layout"), - }); - - TilemapPipeline { - view_layout, - material_layout, - mesh_layout, + Self { + mesh2d_pipeline: Mesh2dPipeline::from_world(world), + chunk_batch_bind_groups: ChunkBatchBindGroupLayouts::from_world(world), } } } -#[derive(Debug, Component, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TilemapPipelineKey { - pub msaa: u32, - pub map_type: TilemapType, - pub hdr: bool, -} -impl SpecializedRenderPipeline for TilemapPipeline { - type Key = TilemapPipelineKey; +impl SpecializedRenderPipeline for TilesChunkPipeline { + type Key = Mesh2dPipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut shader_defs = Vec::new(); - - #[cfg(feature = "atlas")] - shader_defs.push("ATLAS".into()); - - let mesh_string = match key.map_type { - TilemapType::Square { .. } => "SQUARE", - TilemapType::Isometric(coord_system) => match coord_system { - IsoCoordSystem::Diamond => "ISO_DIAMOND", - IsoCoordSystem::Staggered => "ISO_STAGGERED", - }, - TilemapType::Hexagon(coord_system) => match coord_system { - HexCoordSystem::Column => "COLUMN_HEX", - HexCoordSystem::ColumnEven => "COLUMN_EVEN_HEX", - HexCoordSystem::ColumnOdd => "COLUMN_ODD_HEX", - HexCoordSystem::Row => "ROW_HEX", - HexCoordSystem::RowEven => "ROW_EVEN_HEX", - HexCoordSystem::RowOdd => "ROW_ODD_HEX", - }, + fn specialize( + &self, + key: Self::Key, + ) -> bevy::render::render_resource::RenderPipelineDescriptor { + let format = match key.contains(Mesh2dPipelineKey::HDR) { + true => ViewTarget::TEXTURE_FORMAT_HDR, + false => TextureFormat::bevy_default(), }; - shader_defs.push(mesh_string.into()); - let formats = vec![ - // Position - VertexFormat::Float32x4, - // Uv - VertexFormat::Float32x4, - // Color - VertexFormat::Float32x4, - ]; - - let vertex_layout = - VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats); + let shader_defs = Vec::new(); RenderPipelineDescriptor { vertex: VertexState { - shader: TILEMAP_SHADER_VERTEX.typed::(), - entry_point: "vertex".into(), + shader: TILES_VERT, + entry_point: "vs_main".into(), shader_defs: shader_defs.clone(), - buffers: vec![vertex_layout], + // We generate clip space triangles on the fly based on the implicit index buffer + buffers: vec![], }, fragment: Some(FragmentState { - shader: TILEMAP_SHADER_FRAGMENT.typed::(), + // Use our custom shader + shader: TILES_FRAG, shader_defs, - entry_point: "fragment".into(), + entry_point: "fs_main".into(), targets: vec![Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, - blend: Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }, - alpha: BlendComponent { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, - }, - }), + format, + blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], }), + // Use the two standard uniforms for 2d meshes layout: vec![ - self.view_layout.clone(), - self.mesh_layout.clone(), - self.material_layout.clone(), + // Bind group 0 is the view uniform + self.mesh2d_pipeline.view_layout.clone(), + // Bind group 1 are the map components + self.chunk_batch_bind_groups.map_layouts.clone(), + // Bind group 2 are the chunk components + self.chunk_batch_bind_groups.chunk_layouts.clone(), ], + push_constant_ranges: Vec::new(), primitive: PrimitiveState { - conservative: false, - cull_mode: Some(Face::Back), front_face: FrontFace::Ccw, + cull_mode: Some(Face::Back), + unclipped_depth: false, polygon_mode: PolygonMode::Fill, - strip_index_format: None, + conservative: false, topology: PrimitiveTopology::TriangleList, - unclipped_depth: false, + strip_index_format: None, }, depth_stencil: None, multisample: MultisampleState { - count: key.msaa, + count: key.msaa_samples(), mask: !0, alpha_to_coverage_enabled: false, }, - label: Some("tilemap_pipeline".into()), - push_constant_ranges: vec![], + label: Some("tiles_pipeline".into()), } } } diff --git a/crates/bevy_tiles_render/src/prepare.rs b/crates/bevy_tiles_render/src/prepare.rs index 2ee7b68..fb6b2f8 100644 --- a/crates/bevy_tiles_render/src/prepare.rs +++ b/crates/bevy_tiles_render/src/prepare.rs @@ -1,229 +1,138 @@ -use std::marker::PhantomData; - -use crate::map::{ - TilemapId, TilemapSize, TilemapSpacing, TilemapTexture, TilemapTextureSize, TilemapTileSize, - TilemapType, -}; -use crate::render::extract::ExtractedFrustum; -use crate::{ - prelude::TilemapGridSize, render::RenderChunkSize, render::SecondsSinceStartup, FrustumCulling, -}; -use bevy::log::trace; -use bevy::prelude::Resource; use bevy::{ - math::{Mat4, UVec4}, - prelude::{ - Commands, Component, ComputedVisibility, Entity, GlobalTransform, Query, Res, ResMut, Vec2, + ecs::{ + entity::Entity, + system::{Commands, ParallelCommands, Query, Res}, }, + log::debug, render::{ - render_resource::{DynamicUniformBuffer, ShaderType}, + render_resource::CommandEncoderDescriptor, renderer::{RenderDevice, RenderQueue}, }, + utils::hashbrown::HashMap, }; +use bevy_tiles::chunks::ChunkCoord; +use crossbeam::queue::ArrayQueue; -use super::{ - chunk::{ChunkId, PackedTileData, RenderChunk2dStorage, TilemapUniformData}, - extract::{ExtractedRemovedMap, ExtractedRemovedTile, ExtractedTile, ExtractedTilemapTexture}, - DynamicUniformIndex, +use crate::{ + bindings::{ChunkBatchBindGroups, ChunkBatchBuffer, ChunkBuffer, MapBatchBuffer}, + chunk::internal::{BatchSize, ChunkBatch, ChunkUniforms}, + maps::internal::MapInfo, + pipeline::TilesChunkPipeline, }; -#[derive(Resource, Default)] -pub struct MeshUniformResource(pub DynamicUniformBuffer); - -#[derive(Resource, Default)] -pub struct TilemapUniformResource(pub DynamicUniformBuffer); - -#[derive(ShaderType, Component, Clone)] -pub struct MeshUniform { - pub transform: Mat4, +// Write individual chunk data to the GPU before consolidation +pub fn prepare_chunks( + mut commands: Commands, + device: Res, + queue: Res, + mut chunks: Query<(Entity, &mut ChunkUniforms)>, +) { + let size = chunks.iter().len(); + if size == 0 { + return; + } + let uniform_buffers = ArrayQueue::new(size); + chunks + .par_iter_mut() + .for_each(|(chunk_id, mut chunk_uniform)| { + let mut buffer = ChunkBuffer::new(&mut chunk_uniform); + buffer.write_buffer(&device, &queue); + let _ = uniform_buffers.push((chunk_id, buffer)); + }); + commands.insert_or_spawn_batch(uniform_buffers); } -#[allow(clippy::too_many_arguments, clippy::type_complexity)] -pub(crate) fn prepare( +// Consolidate individual chunk information into the batch entities +pub fn prepare_chunk_batch( mut commands: Commands, - mut chunk_storage: ResMut, - mut mesh_uniforms: ResMut, - mut tilemap_uniforms: ResMut, - chunk_size: Res, - extracted_tiles: Query<&ExtractedTile>, - extracted_tilemaps: Query<( - Entity, - &GlobalTransform, - &TilemapTileSize, - &TilemapTextureSize, - &TilemapSpacing, - &TilemapGridSize, - &TilemapType, - &TilemapTexture, - &TilemapSize, - &ComputedVisibility, - &FrustumCulling, - )>, - extracted_tilemap_textures: Query<&ExtractedTilemapTexture>, - extracted_frustum_query: Query<&ExtractedFrustum>, - render_device: Res, - render_queue: Res, - seconds_since_startup: Res, + device: Res, + queue: Res, + chunks: Query<(&ChunkBatch, &ChunkBuffer)>, + chunk_batches: Query<(Entity, &BatchSize, &MapInfo)>, ) { - for tile in extracted_tiles.iter() { - // First if the tile position has changed remove the tile from the old location. - if tile.position != tile.old_position.0 { - chunk_storage.remove_tile_with_entity(tile.entity); - } - - let chunk_index = chunk_size.map_tile_to_chunk(&tile.position); - let ( - _entity, - transform, - tile_size, - texture_size, - spacing, - grid_size, - mesh_type, - texture, - map_size, - visibility, - frustum_culling, - ) = extracted_tilemaps.get(tile.tilemap_id.0).unwrap(); - - let chunk_data = UVec4::new( - chunk_index.x, - chunk_index.y, - transform.translation().z as u32, - tile.tilemap_id.0.index(), - ); + let batch_iter = chunk_batches.iter(); + let batch_count = batch_iter.len(); + + let mut instance_indices = HashMap::with_capacity(batch_count); + let mut chunk_batch_buffers = HashMap::with_capacity(batch_count); + let mut global_uniform_buffers = Vec::with_capacity(batch_count); - let in_chunk_tile_index = chunk_size.map_tile_to_chunk_tile(&tile.position, &chunk_index); - let chunk = chunk_storage.get_or_add( - tile.entity, - in_chunk_tile_index, - tile.tilemap_id.0, - &chunk_data, - **chunk_size, - *mesh_type, - *tile_size, - (*texture_size).into(), - (*spacing).into(), - *grid_size, - texture.clone(), - *map_size, - *transform, - visibility, - frustum_culling, + if batch_iter.len() == 0 { + return; + } + + for (batch_id, batch_size, map_info) in batch_iter { + debug!( + "Preparing batch {:?} with size {:?}", + batch_id, **batch_size ); - chunk.set( - &in_chunk_tile_index.into(), - Some(PackedTileData { - position: chunk_size - .map_tile_to_chunk_tile(&tile.position, &chunk_index) - .as_vec2() - .extend(tile.tile.position.z) - .extend(tile.tile.position.w), - ..tile.tile - }), + + // Create all our instance buffers before we start iterating over chunks + instance_indices.insert(batch_id, 0); + chunk_batch_buffers.insert( + batch_id, + ChunkBatchBuffer::with_size_no_default_values( + **batch_size as usize, + map_info.chunk_size as usize, + &device, + ), ); - } - // Copies transform changes from tilemap to chunks. - for ( - entity, - global_transform, - tile_size, - texture_size, - spacing, - grid_size, - map_type, - texture, - map_size, - visibility, - frustum_culling, - ) in extracted_tilemaps.iter() - { - let chunks = chunk_storage.get_chunk_storage(&UVec4::new(0, 0, 0, entity.index())); - for chunk in chunks.values_mut() { - chunk.texture = texture.clone(); - chunk.map_size = *map_size; - chunk.texture_size = (*texture_size).into(); - chunk.spacing = (*spacing).into(); - chunk.visible = visibility.is_visible(); - chunk.frustum_culling = **frustum_culling; - chunk.update_geometry( - (*global_transform).into(), - *grid_size, - *tile_size, - *map_type, - ); - } + // Create all our global uniforms for the batches + let mut map_buffers = MapBatchBuffer::new(map_info); + + map_buffers.write_buffer(&device, &queue); + + global_uniform_buffers.push((batch_id, (map_buffers))); } - for tilemap in extracted_tilemap_textures.iter() { - let texture_size: Vec2 = tilemap.texture_size.into(); - let chunks = - chunk_storage.get_chunk_storage(&UVec4::new(0, 0, 0, tilemap.tilemap_id.0.index())); - for chunk in chunks.values_mut() { - chunk.texture_size = texture_size; - } + let mut command_encoder = device.create_command_encoder(&CommandEncoderDescriptor { + label: Some("bevy_tiles_render::batch_buffer_copies"), + }); + + for (batch_id, chunk_buffer) in chunks.iter() { + let chunk_batch_buffer = chunk_batch_buffers.get_mut(&**batch_id).unwrap(); + chunk_batch_buffer.push(&mut command_encoder, chunk_buffer); } - mesh_uniforms.0.clear(); - tilemap_uniforms.0.clear(); - - for chunk in chunk_storage.iter_mut() { - if !chunk.visible { - trace!("Visibility culled chunk: {:?}", chunk.get_index()); - continue; - } - - if chunk.frustum_culling - && !extracted_frustum_query - .iter() - .any(|frustum| chunk.intersects_frustum(frustum)) - { - trace!("Frustum culled chunk: {:?}", chunk.get_index()); - continue; - } - trace!("Preparing chunk: {:?}", chunk.get_index()); - - chunk.prepare(&render_device); - - let mut chunk_uniform: TilemapUniformData = chunk.into(); - chunk_uniform.time = **seconds_since_startup; - - commands.spawn(( - chunk.texture.clone_weak(), - chunk.get_transform(), - ChunkId(chunk.get_index()), - chunk.get_map_type(), - TilemapId(Entity::from_bits(chunk.tilemap_id)), - DynamicUniformIndex:: { - index: mesh_uniforms.0.push(MeshUniform { - transform: chunk.get_transform_matrix(), - }), - marker: PhantomData, - }, - DynamicUniformIndex:: { - index: tilemap_uniforms.0.push(chunk_uniform), - marker: PhantomData, - }, - )); + for (_, buffer) in chunk_batch_buffers.iter_mut() { + buffer.write_buffer(&device, &queue) } - mesh_uniforms.0.write_buffer(&render_device, &render_queue); - tilemap_uniforms - .0 - .write_buffer(&render_device, &render_queue); + queue.submit([command_encoder.finish()]); + + commands.insert_or_spawn_batch(global_uniform_buffers); + commands.insert_or_spawn_batch(chunk_batch_buffers); } -pub fn prepare_removal( - mut chunk_storage: ResMut, - removed_tiles: Query<&ExtractedRemovedTile>, - removed_maps: Query<&ExtractedRemovedMap>, +pub fn create_bind_groups( + mut commands: Commands, + device: Res, + chunk_pipeline: Res, + chunk_batches: Query<(Entity, &MapBatchBuffer, &ChunkBatchBuffer)>, ) { - for removed_tile in removed_tiles.iter() { - chunk_storage.remove_tile_with_entity(removed_tile.entity) - } + // Create bind groups + debug!( + "Creating bind group for {} batches", + chunk_batches.iter().len() + ); + for (batch_id, map_buffers, chunk_offsets) in chunk_batches.iter() { + let map_bind_group = device.create_bind_group( + "batch_map_bind_group", + &chunk_pipeline.chunk_batch_bind_groups.map_layouts, + &map_buffers.bindings(), + ); + + let chunk_bind_group = device.create_bind_group( + "batch_chunk_bind_group", + &chunk_pipeline.chunk_batch_bind_groups.chunk_layouts, + &chunk_offsets.bindings(), + ); - for removed_map in removed_maps.iter() { - chunk_storage.remove_map(removed_map.entity); + debug!("Adding bind groups to batch {:?}", batch_id); + commands.entity(batch_id).insert(ChunkBatchBindGroups { + map_bind_group, + chunk_bind_group, + }); } } diff --git a/crates/bevy_tiles_render/src/queue.rs b/crates/bevy_tiles_render/src/queue.rs index ca0353f..a32e50a 100644 --- a/crates/bevy_tiles_render/src/queue.rs +++ b/crates/bevy_tiles_render/src/queue.rs @@ -1,58 +1,118 @@ use bevy::{ - prelude::*, + core_pipeline::core_2d::Transparent2d, + ecs::{ + entity::Entity, + query::{Or, With}, + system::{Commands, ParallelCommands, Query, Res, ResMut}, + }, + log::debug, render::{ - render_resource::{BindGroup, BindGroupDescriptor, BindGroupEntry}, + render_phase::{DrawFunctions, RenderPhase}, + render_resource::{PipelineCache, PrimitiveTopology, SpecializedRenderPipelines}, renderer::RenderDevice, + view::{ExtractedView, Msaa}, }, - utils::HashMap, + sprite::Mesh2dPipelineKey, + utils::FloatOrd, }; +use bevy_tiles::{chunks::InMap, maps::TileMap}; -use super::{ - pipeline::TilemapPipeline, - prepare::{MeshUniformResource, TilemapUniformResource}, +use crate::{ + bindings::ChunkBuffer, + chunk::internal::{BatchSize, ChunkBatch, ChunkUniforms}, + draw::DrawChunks, + maps::internal::{MapChunks, MapInfo}, + pipeline::TilesChunkPipeline, }; -use crate::TilemapTexture; -#[derive(Resource)] -pub struct TransformBindGroup { - pub value: BindGroup, +pub fn create_chunk_batches( + commands: ParallelCommands, + maps: Query<(&MapInfo, &MapChunks)>, + chunks: Query, With)>>, +) { + maps.par_iter().for_each(|(map_info, map_chunks)| { + commands.command_scope(|mut commands| { + let max_batch_size = map_info.tile_map_renderer.batch_size; + let chunk_count = chunks.iter().len(); + let batch_count = chunk_count / max_batch_size as usize + + if (chunk_count % max_batch_size as usize) > 0 { + 1 + } else { + 0 + }; + + if batch_count == 0 { + return; + } + + let mut batches = Vec::with_capacity(batch_count); + let mut batched_chunks = Vec::with_capacity(chunk_count); + + let mut batch_size = 0; + let mut current_batch = ChunkBatch(commands.spawn_empty().id()); + + while let Some(chunk_id) = map_chunks.pop() { + if chunks.get(chunk_id).is_ok() { + if batch_size == max_batch_size { + batches.push((*current_batch, (BatchSize(batch_size), map_info.clone()))); + batch_size = 0; + current_batch = ChunkBatch(commands.spawn_empty().id()); + } + batched_chunks.push((chunk_id, current_batch.clone())); + batch_size += 1; + } + } + + if batch_size > 0 { + batches.push((*current_batch, (BatchSize(batch_size), map_info.clone()))); + } + + commands.insert_or_spawn_batch(batches); + commands.insert_or_spawn_batch(batched_chunks); + }); + }); } -pub fn queue_transform_bind_group( +pub fn queue_chunks( mut commands: Commands, - tilemap_pipeline: Res, - render_device: Res, - transform_uniforms: Res, - tilemap_uniforms: Res, + mut pipelines: ResMut>, + device: Res, + chunk_pipeline: Res, + pipeline_cache: Res, + msaa: Res, + transparent_draw_functions: Res>, + mut views: Query<(&mut RenderPhase, &ExtractedView)>, + chunk_batches: Query<(Entity, &BatchSize)>, ) { - if let (Some(binding1), Some(binding2)) = - (transform_uniforms.0.binding(), tilemap_uniforms.0.binding()) - { - commands.insert_resource(TransformBindGroup { - value: render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: binding1, - }, - BindGroupEntry { - binding: 1, - resource: binding2, - }, - ], - label: Some("transform_bind_group"), - layout: &tilemap_pipeline.mesh_layout, - }), - }); - } -} + for (mut transparent_phase, view) in &mut views { + let chunk_batch_iter = chunk_batches.iter(); + if chunk_batch_iter.len() == 0 { + continue; + } -#[derive(Component)] -pub struct TilemapViewBindGroup { - pub value: BindGroup, -} + let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) + | Mesh2dPipelineKey::from_hdr(view.hdr) + | Mesh2dPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList); + let pipeline_id = pipelines.specialize(&pipeline_cache, &chunk_pipeline, mesh_key); + + let draw_chunks = transparent_draw_functions.read().id::(); -#[derive(Default, Resource)] -pub struct ImageBindGroups { - pub values: HashMap, + for (batch_id, batch_size) in chunk_batch_iter { + debug!("Queuing draw call for batch: {:?}", batch_id); + transparent_phase.add(Transparent2d { + entity: batch_id, + draw_function: draw_chunks, + pipeline: pipeline_id, + sort_key: FloatOrd(0.0), + // Ignore this, we do our own batching + batch_range: 0..1, + dynamic_offset: None, + }); + } + + debug!( + "Queued {:?} Chunk Batches for Drawing", + chunk_batches.iter().len() + ); + } } diff --git a/crates/bevy_tiles_render/src/shaders/column_even_hex.wgsl b/crates/bevy_tiles_render/src/shaders/column_even_hex.wgsl deleted file mode 100644 index 1c2bbd2..0000000 --- a/crates/bevy_tiles_render/src/shaders/column_even_hex.wgsl +++ /dev/null @@ -1,40 +0,0 @@ -#define_import_path bevy_ecs_tilemap::column_even_hex - -#import bevy_ecs_tilemap::mesh_output MeshOutput -#import bevy_ecs_tilemap::common VertexInput, tilemap_data, mesh - -// Gets the screen space coordinates of the bottom left of an isometric tile position. -fn hex_col_tile_pos_to_world_pos(pos: vec2, grid_width: f32, grid_height: f32) -> vec2 { - let SQRT_3: f32 = 1.7320508; - let HALF_SQRT_3: f32 = 0.8660254; - let COL_BASIS_X: vec2 = vec2(HALF_SQRT_3, 0.5); - let COL_BASIS_Y: vec2 = vec2(0.0, 1.0); - - let unscaled_pos = pos.x * COL_BASIS_X + pos.y * COL_BASIS_Y; - return vec2(COL_BASIS_X.x * grid_width * unscaled_pos.x, grid_height * unscaled_pos.y); -} - -fn col_even_to_axial(offset_pos: vec2) -> vec2 { - let delta: f32 = ceil(offset_pos.x / 2.0); - return vec2(offset_pos.x, offset_pos.y - delta); -} - -fn get_mesh(v_index: u32, vertex_position: vec3) -> MeshOutput { - var out: MeshOutput; - - let axial_pos = col_even_to_axial(vertex_position.xy); - let center = hex_col_tile_pos_to_world_pos(axial_pos, tilemap_data.grid_size.x, tilemap_data.grid_size.y); - let bot_left = center - 0.5 * tilemap_data.tile_size; - let top_right = bot_left + tilemap_data.tile_size; - - var positions = array, 4>( - bot_left, - vec2(bot_left.x, top_right.y), - top_right, - vec2(top_right.x, bot_left.y) - ); - - out.world_position = mesh.model * vec4(positions[v_index % 4u], 0.0, 1.0); - - return out; -} diff --git a/crates/bevy_tiles_render/src/shaders/column_hex.wgsl b/crates/bevy_tiles_render/src/shaders/column_hex.wgsl deleted file mode 100644 index 2bf6ddc..0000000 --- a/crates/bevy_tiles_render/src/shaders/column_hex.wgsl +++ /dev/null @@ -1,34 +0,0 @@ -#define_import_path bevy_ecs_tilemap::column_hex - -#import bevy_ecs_tilemap::common VertexInput, tilemap_data, mesh -#import bevy_ecs_tilemap::mesh_output MeshOutput - -// Gets the screen space coordinates of the bottom left of an isometric tile position. -fn hex_col_tile_pos_to_world_pos(pos: vec2, grid_width: f32, grid_height: f32) -> vec2 { - let SQRT_3: f32 = 1.7320508; - let HALF_SQRT_3: f32 = 0.8660254; - let COL_BASIS_X: vec2 = vec2(HALF_SQRT_3, 0.5); - let COL_BASIS_Y: vec2 = vec2(0.0, 1.0); - - let unscaled_pos = pos.x * COL_BASIS_X + pos.y * COL_BASIS_Y; - return vec2(COL_BASIS_X.x * grid_width * unscaled_pos.x, grid_height * unscaled_pos.y); -} - -fn get_mesh(v_index: u32, vertex_position: vec3) -> MeshOutput { - var out: MeshOutput; - - let center = hex_col_tile_pos_to_world_pos(vertex_position.xy, tilemap_data.grid_size.x, tilemap_data.grid_size.y); - let bot_left = center - 0.5 * tilemap_data.tile_size; - let top_right = bot_left + tilemap_data.tile_size; - - var positions = array, 4>( - bot_left, - vec2(bot_left.x, top_right.y), - top_right, - vec2(top_right.x, bot_left.y) - ); - - out.world_position = mesh.model * vec4(positions[v_index % 4u], 0.0, 1.0); - - return out; -} diff --git a/crates/bevy_tiles_render/src/shaders/column_odd_hex.wgsl b/crates/bevy_tiles_render/src/shaders/column_odd_hex.wgsl deleted file mode 100644 index f0b96ac..0000000 --- a/crates/bevy_tiles_render/src/shaders/column_odd_hex.wgsl +++ /dev/null @@ -1,41 +0,0 @@ -#define_import_path bevy_ecs_tilemap::column_odd_hex - -#import bevy_ecs_tilemap::mesh_output MeshOutput -#import bevy_ecs_tilemap::common VertexInput, tilemap_data, mesh - - -// Gets the screen space coordinates of the bottom left of an isometric tile position. -fn hex_col_tile_pos_to_world_pos(pos: vec2, grid_width: f32, grid_height: f32) -> vec2 { - let SQRT_3: f32 = 1.7320508; - let HALF_SQRT_3: f32 = 0.8660254; - let COL_BASIS_X: vec2 = vec2(HALF_SQRT_3, 0.5); - let COL_BASIS_Y: vec2 = vec2(0.0, 1.0); - - let unscaled_pos = pos.x * COL_BASIS_X + pos.y * COL_BASIS_Y; - return vec2(COL_BASIS_X.x * grid_width * unscaled_pos.x, grid_height * unscaled_pos.y); -} - -fn col_even_to_axial(offset_pos: vec2) -> vec2 { - let delta: f32 = floor(offset_pos.x / 2.0); - return vec2(offset_pos.x, offset_pos.y - delta); -} - -fn get_mesh(v_index: u32, vertex_position: vec3) -> MeshOutput { - var out: MeshOutput; - - let axial_pos = col_even_to_axial(vertex_position.xy); - let center = hex_col_tile_pos_to_world_pos(axial_pos, tilemap_data.grid_size.x, tilemap_data.grid_size.y); - let bot_left = center - 0.5 * tilemap_data.tile_size; - let top_right = bot_left + tilemap_data.tile_size; - - var positions = array, 4>( - bot_left, - vec2(bot_left.x, top_right.y), - top_right, - vec2(top_right.x, bot_left.y) - ); - - out.world_position = mesh.model * vec4(positions[v_index % 4u], 0.0, 1.0); - - return out; -} diff --git a/crates/bevy_tiles_render/src/shaders/common.wgsl b/crates/bevy_tiles_render/src/shaders/common.wgsl deleted file mode 100644 index 1cc2540..0000000 --- a/crates/bevy_tiles_render/src/shaders/common.wgsl +++ /dev/null @@ -1,77 +0,0 @@ -#define_import_path bevy_ecs_tilemap::common - -#import bevy_sprite::mesh2d_view_bindings - -struct Mesh { - model: mat4x4, -}; -@group(1) @binding(0) -var mesh: Mesh; - -struct TilemapData { - texture_size: vec2, - tile_size: vec2, - grid_size: vec2, - spacing: vec2, - chunk_pos: vec2, - map_size: vec2, - time: f32, - _padding: vec3, // hack for webgl2 16 byte alignment -}; -@group(1) @binding(1) -var tilemap_data: TilemapData; - -struct VertexInput { - @builtin(vertex_index) v_index: u32, - @location(0) uv: vec4, - @location(1) position: vec4, - @location(2) color: vec4, -} - -#ifdef ATLAS -@group(2) @binding(0) -var sprite_texture: texture_2d; -#else -@group(2) @binding(0) -var sprite_texture: texture_2d_array; -#endif - -@group(2) @binding(1) -var sprite_sampler: sampler; - -#import bevy_ecs_tilemap::vertex_output MeshVertexOutput - -fn process_fragment(in: MeshVertexOutput) -> vec4 { - #ifdef ATLAS - let half_texture_pixel_size_u = 0.5 / tilemap_data.texture_size.x; - let half_texture_pixel_size_v = 0.5 / tilemap_data.texture_size.y; - let half_tile_pixel_size_u = 0.5 / tilemap_data.tile_size.x; - let half_tile_pixel_size_v = 0.5 / tilemap_data.tile_size.y; - - // Offset the UV 1/2 pixel from the sides of the tile, so that the sampler doesn't bleed onto - // adjacent tiles at the edges. - var uv_offset: vec2 = vec2(0.0, 0.0); - if (in.uv.z < half_tile_pixel_size_u) { - uv_offset.x = half_texture_pixel_size_u; - } else if (in.uv.z > (1.0 - half_tile_pixel_size_u)) { - uv_offset.x = - half_texture_pixel_size_u; - } - if (in.uv.w < half_tile_pixel_size_v) { - uv_offset.y = half_texture_pixel_size_v; - } else if (in.uv.w > (1.0 - half_tile_pixel_size_v)) { - uv_offset.y = - half_texture_pixel_size_v; - } - - let color = textureSample(sprite_texture, sprite_sampler, in.uv.xy + uv_offset) * in.color; - if (color.a < 0.001) { - discard; - } - return color; - #else - let color = textureSample(sprite_texture, sprite_sampler, in.uv.xy, in.tile_id) * in.color; - if (color.a < 0.001) { - discard; - } - return color; - #endif -} \ No newline at end of file diff --git a/crates/bevy_tiles_render/src/shaders/diamond_iso.wgsl b/crates/bevy_tiles_render/src/shaders/diamond_iso.wgsl deleted file mode 100644 index 8d07dcd..0000000 --- a/crates/bevy_tiles_render/src/shaders/diamond_iso.wgsl +++ /dev/null @@ -1,32 +0,0 @@ -#define_import_path bevy_ecs_tilemap::diamond_iso - -#import bevy_ecs_tilemap::common VertexInput, tilemap_data, mesh -#import bevy_ecs_tilemap::mesh_output MeshOutput - -const DIAMOND_BASIS_X: vec2 = vec2(0.5, -0.5); -const DIAMOND_BASIS_Y: vec2 = vec2(0.5, 0.5); - -// Gets the screen space coordinates of the bottom left of an isometric tile position. -fn diamond_tile_pos_to_world_pos(pos: vec2, grid_width: f32, grid_height: f32) -> vec2 { - let unscaled_pos = pos.x * DIAMOND_BASIS_X + pos.y * DIAMOND_BASIS_Y; - return vec2(grid_width * unscaled_pos.x, grid_height * unscaled_pos.y); -} - -fn get_mesh(v_index: u32, vertex_position: vec3) -> MeshOutput { - var out: MeshOutput; - - let center = diamond_tile_pos_to_world_pos(vertex_position.xy, tilemap_data.grid_size.x, tilemap_data.grid_size.y); - let bot_left = center - 0.5 * tilemap_data.tile_size; - let top_right = bot_left + tilemap_data.tile_size; - - var positions = array, 4>( - bot_left, - vec2(bot_left.x, top_right.y), - top_right, - vec2(top_right.x, bot_left.y) - ); - - out.world_position = mesh.model * vec4(positions[v_index % 4u], 0.0, 1.0); - - return out; -} \ No newline at end of file diff --git a/crates/bevy_tiles_render/src/shaders/mesh_output.wgsl b/crates/bevy_tiles_render/src/shaders/mesh_output.wgsl deleted file mode 100644 index 7fd881b..0000000 --- a/crates/bevy_tiles_render/src/shaders/mesh_output.wgsl +++ /dev/null @@ -1,6 +0,0 @@ -#define_import_path bevy_ecs_tilemap::mesh_output - -struct MeshOutput { - world_position: vec4, - uv: vec2, -}; diff --git a/crates/bevy_tiles_render/src/shaders/row_even_hex.wgsl b/crates/bevy_tiles_render/src/shaders/row_even_hex.wgsl deleted file mode 100644 index ed8f920..0000000 --- a/crates/bevy_tiles_render/src/shaders/row_even_hex.wgsl +++ /dev/null @@ -1,40 +0,0 @@ -#define_import_path bevy_ecs_tilemap::row_even_hex - -#import bevy_ecs_tilemap::common VertexInput, tilemap_data, mesh -#import bevy_ecs_tilemap::mesh_output MeshOutput - -// Gets the screen space coordinates of the bottom left of an isometric tile position. -fn hex_row_tile_pos_to_world_pos(pos: vec2, grid_width: f32, grid_height: f32) -> vec2 { - let SQRT_3: f32 = 1.7320508; - let HALF_SQRT_3: f32 = 0.8660254; - let ROW_BASIS_X: vec2 = vec2(1.0, 0.0); - let ROW_BASIS_Y: vec2 = vec2(0.5, HALF_SQRT_3); - - let unscaled_pos = pos.x * ROW_BASIS_X + pos.y * ROW_BASIS_Y; - return vec2(grid_width * unscaled_pos.x, ROW_BASIS_Y.y * grid_height * unscaled_pos.y); -} - -fn row_even_to_axial(offset_pos: vec2) -> vec2 { - let delta: f32 = ceil(offset_pos.y / 2.0); - return vec2(offset_pos.x - delta, offset_pos.y); -} - -fn get_mesh(v_index: u32, vertex_position: vec3) -> MeshOutput { - var out: MeshOutput; - - let axial_pos = row_even_to_axial(vertex_position.xy); - let center = hex_row_tile_pos_to_world_pos(axial_pos, tilemap_data.grid_size.x, tilemap_data.grid_size.y); - let bot_left = center - 0.5 * tilemap_data.tile_size; - let top_right = bot_left + tilemap_data.tile_size; - - var positions = array, 4>( - bot_left, - vec2(bot_left.x, top_right.y), - top_right, - vec2(top_right.x, bot_left.y) - ); - - out.world_position = mesh.model * vec4(positions[v_index % 4u], 0.0, 1.0); - - return out; -} diff --git a/crates/bevy_tiles_render/src/shaders/row_hex.wgsl b/crates/bevy_tiles_render/src/shaders/row_hex.wgsl deleted file mode 100644 index 5e8ad40..0000000 --- a/crates/bevy_tiles_render/src/shaders/row_hex.wgsl +++ /dev/null @@ -1,34 +0,0 @@ -#define_import_path bevy_ecs_tilemap::row_hex - -#import bevy_ecs_tilemap::common VertexInput, tilemap_data, mesh -#import bevy_ecs_tilemap::mesh_output MeshOutput - -// Gets the screen space coordinates of the bottom left of an isometric tile position. -fn hex_row_tile_pos_to_world_pos(pos: vec2, grid_width: f32, grid_height: f32) -> vec2 { - let SQRT_3: f32 = 1.7320508; - let HALF_SQRT_3: f32 = 0.8660254; - let ROW_BASIS_X: vec2 = vec2(1.0, 0.0); - let ROW_BASIS_Y: vec2 = vec2(0.5, HALF_SQRT_3); - - let unscaled_pos = pos.x * ROW_BASIS_X + pos.y * ROW_BASIS_Y; - return vec2(grid_width * unscaled_pos.x, ROW_BASIS_Y.y * grid_height * unscaled_pos.y); -} - -fn get_mesh(v_index: u32, vertex_position: vec3) -> MeshOutput { - var out: MeshOutput; - - let center = hex_row_tile_pos_to_world_pos(vertex_position.xy, tilemap_data.grid_size.x, tilemap_data.grid_size.y); - let bot_left = center - 0.5 * tilemap_data.tile_size; - let top_right = bot_left + tilemap_data.tile_size; - - var positions = array, 4>( - bot_left, - vec2(bot_left.x, top_right.y), - top_right, - vec2(top_right.x, bot_left.y) - ); - - out.world_position = mesh.model * vec4(positions[v_index % 4u], 0.0, 1.0); - - return out; -} diff --git a/crates/bevy_tiles_render/src/shaders/row_odd_hex.wgsl b/crates/bevy_tiles_render/src/shaders/row_odd_hex.wgsl deleted file mode 100644 index 4ae284b..0000000 --- a/crates/bevy_tiles_render/src/shaders/row_odd_hex.wgsl +++ /dev/null @@ -1,40 +0,0 @@ -#define_import_path bevy_ecs_tilemap::row_odd_hex - -#import bevy_ecs_tilemap::common VertexInput, tilemap_data, mesh -#import bevy_ecs_tilemap::mesh_output MeshOutput - -// Gets the screen space coordinates of the bottom left of an isometric tile position. -fn hex_row_tile_pos_to_world_pos(pos: vec2, grid_width: f32, grid_height: f32) -> vec2 { - let SQRT_3: f32 = 1.7320508; - let HALF_SQRT_3: f32 = 0.8660254; - let ROW_BASIS_X: vec2 = vec2(1.0, 0.0); - let ROW_BASIS_Y: vec2 = vec2(0.5, HALF_SQRT_3); - - let unscaled_pos = pos.x * ROW_BASIS_X + pos.y * ROW_BASIS_Y; - return vec2(grid_width * unscaled_pos.x, ROW_BASIS_Y.y * grid_height * unscaled_pos.y); -} - -fn row_odd_to_axial(offset_pos: vec2) -> vec2 { - let delta: f32 = floor(offset_pos.y / 2.0); - return vec2(offset_pos.x - delta, offset_pos.y); -} - -fn get_mesh(v_index: u32, vertex_position: vec3) -> MeshOutput { - var out: MeshOutput; - - let axial_pos = row_odd_to_axial(vertex_position.xy); - let center = hex_row_tile_pos_to_world_pos(axial_pos, tilemap_data.grid_size.x, tilemap_data.grid_size.y); - let bot_left = center - 0.5 * tilemap_data.tile_size; - let top_right = bot_left + tilemap_data.tile_size; - - var positions = array, 4>( - bot_left, - vec2(bot_left.x, top_right.y), - top_right, - vec2(top_right.x, bot_left.y) - ); - - out.world_position = mesh.model * vec4(positions[v_index % 4u], 0.0, 1.0); - - return out; -} diff --git a/crates/bevy_tiles_render/src/shaders/square.wgsl b/crates/bevy_tiles_render/src/shaders/square.wgsl deleted file mode 100644 index c86f6b7..0000000 --- a/crates/bevy_tiles_render/src/shaders/square.wgsl +++ /dev/null @@ -1,22 +0,0 @@ -#define_import_path bevy_ecs_tilemap::square -#import bevy_ecs_tilemap::common VertexInput, tilemap_data, mesh -#import bevy_ecs_tilemap::mesh_output MeshOutput - -fn get_mesh(v_index: u32, vertex_position: vec3) -> MeshOutput { - var out: MeshOutput; - - let center = vertex_position.xy * tilemap_data.grid_size; - let bot_left = center - 0.5 * tilemap_data.tile_size; - let top_right = bot_left + tilemap_data.tile_size; - - var positions = array, 4>( - bot_left, - vec2(bot_left.x, top_right.y), - top_right, - vec2(top_right.x, bot_left.y) - ); - - out.world_position = mesh.model * vec4(positions[v_index % 4u], 0.0, 1.0); - - return out; -} diff --git a/crates/bevy_tiles_render/src/shaders/staggered_iso.wgsl b/crates/bevy_tiles_render/src/shaders/staggered_iso.wgsl deleted file mode 100644 index e8e08ab..0000000 --- a/crates/bevy_tiles_render/src/shaders/staggered_iso.wgsl +++ /dev/null @@ -1,37 +0,0 @@ -#define_import_path bevy_ecs_tilemap::staggered_iso - -#import bevy_ecs_tilemap::common VertexInput, tilemap_data, mesh -#import bevy_ecs_tilemap::mesh_output MeshOutput - -const DIAMOND_BASIS_X: vec2 = vec2(0.5, -0.5); -const DIAMOND_BASIS_Y: vec2 = vec2(0.5, 0.5); - -// Gets the screen space coordinates of the bottom left of an isometric tile position. -fn diamond_tile_pos_to_world_pos(pos: vec2, grid_width: f32, grid_height: f32) -> vec2 { - let unscaled_pos = pos.x * DIAMOND_BASIS_X + pos.y * DIAMOND_BASIS_Y; - return vec2(grid_width * unscaled_pos.x, grid_height * unscaled_pos.y); -} - -// Gets the screen space coordinates of the bottom left of an isometric tile position. -fn staggered_tile_pos_to_world_pos(pos: vec2, grid_width: f32, grid_height: f32) -> vec2 { - return diamond_tile_pos_to_world_pos(vec2(pos.x, pos.y + pos.x), grid_width, grid_height); -} - -fn get_mesh(v_index: u32, vertex_position: vec3) -> MeshOutput { - var out: MeshOutput; - - let center = staggered_tile_pos_to_world_pos(vertex_position.xy, tilemap_data.grid_size.x, tilemap_data.grid_size.y); - let bot_left = center - 0.5 * tilemap_data.tile_size; - let top_right = bot_left + tilemap_data.tile_size; - - var positions = array, 4>( - bot_left, - vec2(bot_left.x, top_right.y), - top_right, - vec2(top_right.x, bot_left.y) - ); - - out.world_position = mesh.model * vec4(positions[v_index % 4u], 0.0, 1.0); - - return out; -} \ No newline at end of file diff --git a/crates/bevy_tiles_render/src/shaders/tilemap_fragment.wgsl b/crates/bevy_tiles_render/src/shaders/tilemap_fragment.wgsl deleted file mode 100644 index 6f2da1a..0000000 --- a/crates/bevy_tiles_render/src/shaders/tilemap_fragment.wgsl +++ /dev/null @@ -1,7 +0,0 @@ -#import bevy_ecs_tilemap::common process_fragment -#import bevy_ecs_tilemap::vertex_output MeshVertexOutput - -@fragment -fn fragment(in: MeshVertexOutput) -> @location(0) vec4 { - return process_fragment(in); -} \ No newline at end of file diff --git a/crates/bevy_tiles_render/src/shaders/tilemap_vertex.wgsl b/crates/bevy_tiles_render/src/shaders/tilemap_vertex.wgsl deleted file mode 100644 index e164d9f..0000000 --- a/crates/bevy_tiles_render/src/shaders/tilemap_vertex.wgsl +++ /dev/null @@ -1,136 +0,0 @@ -#import bevy_ecs_tilemap::common VertexInput, tilemap_data -#import bevy_ecs_tilemap::mesh_output MeshOutput -#import bevy_sprite::mesh2d_view_bindings view -#import bevy_ecs_tilemap::vertex_output MeshVertexOutput - -#ifdef SQUARE - #import bevy_ecs_tilemap::square get_mesh -#endif - -#ifdef ISO_DIAMOND - #import bevy_ecs_tilemap::diamond_iso get_mesh -#endif - -#ifdef ISO_STAGGERED - #import bevy_ecs_tilemap::staggered_iso get_mesh -#endif - -#ifdef COLUMN_EVEN_HEX - #import bevy_ecs_tilemap::column_even_hex get_mesh -#endif - -#ifdef COLUMN_HEX - #import bevy_ecs_tilemap::column_hex get_mesh -#endif - -#ifdef COLUMN_ODD_HEX - #import bevy_ecs_tilemap::column_odd_hex get_mesh -#endif - -#ifdef ROW_EVEN_HEX - #import bevy_ecs_tilemap::row_even_hex get_mesh -#endif - -#ifdef ROW_HEX - #import bevy_ecs_tilemap::row_hex get_mesh -#endif - -#ifdef ROW_ODD_HEX - #import bevy_ecs_tilemap::row_odd_hex get_mesh -#endif - - -@vertex -fn vertex(vertex_input: VertexInput) -> MeshVertexOutput { - var out: MeshVertexOutput; - let animation_speed = vertex_input.position.z; - - let mesh_data: MeshOutput = get_mesh(vertex_input.v_index, vec3(vertex_input.position.xy, 0.0)); - - let frames: f32 = f32(vertex_input.uv.w - vertex_input.uv.z); - - var current_animation_frame = fract(tilemap_data.time * animation_speed) * frames; - - current_animation_frame = clamp(f32(vertex_input.uv.z) + current_animation_frame, f32(vertex_input.uv.z), f32(vertex_input.uv.w)); - - let texture_index: u32 = u32(current_animation_frame); - - #ifdef ATLAS - // Get the top-left corner of the current frame in the texture, accounting for padding around the whole texture - // as well as spacing between the tiles. - let columns: u32 = u32(round((tilemap_data.texture_size.x - tilemap_data.spacing.x) / (tilemap_data.tile_size.x + tilemap_data.spacing.x))); - let sprite_sheet_x: f32 = tilemap_data.spacing.x + floor(f32(texture_index % columns)) * (tilemap_data.tile_size.x + tilemap_data.spacing.x); - let sprite_sheet_y: f32 = tilemap_data.spacing.y + floor(f32(texture_index / columns)) * (tilemap_data.tile_size.y + tilemap_data.spacing.y); - - let start_u: f32 = sprite_sheet_x / tilemap_data.texture_size.x; - let end_u: f32 = (sprite_sheet_x + tilemap_data.tile_size.x) / tilemap_data.texture_size.x; - let start_v: f32 = sprite_sheet_y / tilemap_data.texture_size.y; - let end_v: f32 = (sprite_sheet_y + tilemap_data.tile_size.y) / tilemap_data.texture_size.y; - #else - let start_u: f32 = 0.0; - let end_u: f32 = 1.0; - let start_v: f32 = 0.0; - let end_v: f32 = 1.0; - #endif - - var atlas_uvs: array, 4>; - - var x1: array, 8> = array, 8>( - // The x and y are the texture UV, and the z and w and the local tile UV - vec4(start_u, end_v, 0.0, 1.0), // no flip/rotation - vec4(end_u, end_v, 1.0, 1.0), // flip x - vec4(start_u, start_v, 0.0, 0.0), // flip y - vec4(end_u, start_v, 1.0, 0.0), // flip x y - vec4(end_u, start_v, 1.0, 0.0), // flip d - vec4(end_u, end_v, 1.0, 1.0), // flip x d - vec4(start_u, start_v, 0.0, 0.0), // flip y d - vec4(start_u, end_v, 0.0, 1.0) - ); - - var x2: array, 8> = array, 8>( - vec4(start_u, start_v, 0.0, 0.0), - vec4(end_u, start_v, 1.0, 0.0), - vec4(start_u, end_v, 0.0, 1.0), - vec4(end_u, end_v, 1.0, 1.0), - vec4(start_u, start_v, 0.0, 0.0), - vec4(start_u, end_v, 0.0, 1.0), - vec4(end_u, start_v, 1.0, 0.0), - vec4(end_u, end_v, 1.0, 1.0) - ); - - var x3: array, 8> = array, 8>( - vec4(end_u, start_v, 1.0, 0.0), - vec4(start_u, start_v, 0.0, 0.0), - vec4(end_u, end_v, 1.0, 1.0), - vec4(start_u, end_v, 0.0, 1.0), - vec4(start_u, end_v, 0.0, 1.0), - vec4(start_u, start_v, 0.0, 0.0), - vec4(end_u, end_v, 1.0, 1.0), - vec4(end_u, start_v, 1.0, 0.0) - ); - - var x4: array, 8> = array, 8>( - vec4(end_u, end_v, 1.0, 1.0), - vec4(start_u, end_v, 0.0, 1.0), - vec4(end_u, start_v, 1.0, 0.0), - vec4(start_u, start_v, 0.0, 0.0), - vec4(end_u, end_v, 1.0, 1.0), - vec4(end_u, start_v, 1.0, 0.0), - vec4(start_u, end_v, 0.0, 1.0), - vec4(start_u, start_v, 0.0, 0.0), - ); - - atlas_uvs = array, 4>( - x1[u32(vertex_input.uv.y)], - x2[u32(vertex_input.uv.y)], - x3[u32(vertex_input.uv.y)], - x4[u32(vertex_input.uv.y)] - ); - - out.uv = atlas_uvs[vertex_input.v_index % 4u]; - out.tile_id = i32(texture_index); - // out.uv = out.uv + 1e-5; - out.position = view.view_proj * mesh_data.world_position; - out.color = vertex_input.color; - return out; -} diff --git a/crates/bevy_tiles_render/src/shaders/tilemap_vertex_output.wgsl b/crates/bevy_tiles_render/src/shaders/tilemap_vertex_output.wgsl deleted file mode 100644 index 96531d8..0000000 --- a/crates/bevy_tiles_render/src/shaders/tilemap_vertex_output.wgsl +++ /dev/null @@ -1,8 +0,0 @@ -#define_import_path bevy_ecs_tilemap::vertex_output - -struct MeshVertexOutput { - @builtin(position) position: vec4, - @location(0) uv: vec4, - @location(1) color: vec4, - @location(2) @interpolate(flat) tile_id: i32, -} diff --git a/crates/bevy_tiles_render/src/shaders/tiles_frag.wgsl b/crates/bevy_tiles_render/src/shaders/tiles_frag.wgsl new file mode 100644 index 0000000..0f2eca6 --- /dev/null +++ b/crates/bevy_tiles_render/src/shaders/tiles_frag.wgsl @@ -0,0 +1,18 @@ + +@group(1) @binding(1) var chunk_size: u32; +@group(2) @binding(1) var tile_instances: array; + +struct FragIn { + @location(0) tile_index: u32, + @location(1) chunk_index: u32, +}; + +/// Entry point for the fragment shader +@fragment +fn fs_main(in: FragIn) -> @location(0) vec4 { + let global_tile_index = in.chunk_index * chunk_size * chunk_size + in.tile_index; + if tile_instances[global_tile_index] == 0u{ + discard; + } + return vec4(1.0, 1.0, 1.0, 1.0); +} \ No newline at end of file diff --git a/crates/bevy_tiles_render/src/shaders/tiles_vert.wgsl b/crates/bevy_tiles_render/src/shaders/tiles_vert.wgsl new file mode 100644 index 0000000..5d3a24e --- /dev/null +++ b/crates/bevy_tiles_render/src/shaders/tiles_vert.wgsl @@ -0,0 +1,105 @@ +#define_import_path bevy_tiles::vert +// Import the standard 2d mesh uniforms and set their bind groups +#import bevy_render::view::View +#import bevy_render::globals::Globals +#import bevy_render::{ + instance_index::get_instance_index, + maths::{affine_to_square, mat2x4_f32_to_mat3x3_unpack}, +} + +@group(0) @binding(0) var view: View; + +@group(0) @binding(1) var globals: Globals; + +// Map level uniforms +@group(1) @binding(0) var mesh: Mesh2d; +@group(1) @binding(1) var chunk_size: u32; +@group(1) @binding(2) var tile_size: f32; +@group(1) @binding(3) var grid_size: f32; + +// Chunk level uniforms +@group(2) @binding(0) var chunk_offsets: array>; + +// The structure of the vertex buffer is as specified in `specialize()` +struct VertIn { + @builtin(vertex_index) vertex_index: u32, + @builtin(instance_index) instance_index: u32, +}; + +struct VertOut { + @builtin(position) clip_position: vec4, + @location(0) tile_index: u32, + @location(1) chunk_index: u32, +}; + +// LUT for quad verts +var positions: array, 6> = array, 6>( + vec2(0.0, 0.0), + vec2(1.0, 0.0), + vec2(1.0, 1.0), + vec2(1.0, 1.0), + vec2(0.0, 1.0), + vec2(0.0, 0.0), +); + +/// Entry point for the vertex shader +@vertex +fn vs_main(v: VertIn) -> VertOut { + let tile_index = v.vertex_index / 6u; + let chunk_index = v.instance_index; + let tile_offset = vec2( + f32(tile_index % chunk_size), + f32((tile_index / chunk_size) % chunk_size) + ); + let grid_offset = grid_size - tile_size; + let chunk_offset = f32(chunk_size) * chunk_offsets[chunk_index]; + let vertex_position = + // Base tile position + tile_size * (positions[v.vertex_index % 6u] + tile_offset + chunk_offset) + + // Account for grid size + (grid_offset) * (tile_offset + chunk_offset) + vec2(grid_offset / 2.0); + + let model = affine_to_square(mesh.model); + let clip_position = mesh2d_position_local_to_clip(model, vec4(vertex_position, 1.0, 1.0)); + + var out: VertOut; + out.clip_position = clip_position; + out.tile_index = tile_index; + out.chunk_index = chunk_index; + return out; +} + +struct Mesh2d { + model: mat3x4, + inverse_transpose_model_a: mat2x4, + inverse_transpose_model_b: f32, + flags: u32, +}; + + +fn mesh2d_position_local_to_world(model: mat4x4, vertex_position: vec4) -> vec4 { + return model * vertex_position; +} + +fn mesh2d_position_world_to_clip(world_position: vec4) -> vec4 { + return view.view_proj * world_position; +} + +// NOTE: The intermediate world_position assignment is important +// for precision purposes when using the 'equals' depth comparison +// function. +fn mesh2d_position_local_to_clip(model: mat4x4, vertex_position: vec4) -> vec4 { + let world_position = mesh2d_position_local_to_world(model, vertex_position); + return mesh2d_position_world_to_clip(world_position); +} + +fn mesh2d_tangent_local_to_world(model: mat4x4, vertex_tangent: vec4) -> vec4 { + return vec4( + mat3x3( + model[0].xyz, + model[1].xyz, + model[2].xyz + ) * vertex_tangent.xyz, + vertex_tangent.w + ); +} \ No newline at end of file diff --git a/crates/bevy_tiles_render/src/texture_array_cache.rs b/crates/bevy_tiles_render/src/texture_array_cache.rs deleted file mode 100644 index 3fb1788..0000000 --- a/crates/bevy_tiles_render/src/texture_array_cache.rs +++ /dev/null @@ -1,333 +0,0 @@ -use crate::render::extract::ExtractedTilemapTexture; -use crate::{TilemapSpacing, TilemapTexture, TilemapTextureSize, TilemapTileSize}; -use bevy::asset::Assets; -use bevy::prelude::Resource; -use bevy::{ - prelude::{Image, Res}, - render::{ - render_asset::RenderAssets, - render_resource::{ - AddressMode, CommandEncoderDescriptor, Extent3d, FilterMode, ImageCopyTexture, - Origin3d, SamplerDescriptor, TextureAspect, TextureDescriptor, TextureDimension, - TextureFormat, TextureUsages, TextureViewDescriptor, TextureViewDimension, - }, - renderer::{RenderDevice, RenderQueue}, - texture::GpuImage, - }, - utils::{HashMap, HashSet}, -}; - -#[derive(Resource, Default, Debug, Clone)] -pub struct TextureArrayCache { - textures: HashMap, - meta_data: HashMap< - TilemapTexture, - ( - u32, - TilemapTileSize, - TilemapTextureSize, - TilemapSpacing, - FilterMode, - TextureFormat, - ), - >, - prepare_queue: HashSet, - queue_queue: HashSet, - bad_flag_queue: HashSet, -} - -impl TextureArrayCache { - /// Adds an `ExtractedTilemapTexture` to the texture array cache. - /// - /// Unlike [`add_texture`](TextureArrayCache::add_texture) it does not perform any verification - /// checks, as this is assumed to have been done during [`ExtractedTilemapTexture::new`]. - pub(crate) fn add_extracted_texture(&mut self, extracted_texture: &ExtractedTilemapTexture) { - if !self.meta_data.contains_key(&extracted_texture.texture) { - self.meta_data.insert( - extracted_texture.texture.clone_weak(), - ( - extracted_texture.tile_count, - extracted_texture.tile_size, - extracted_texture.texture_size, - extracted_texture.tile_spacing, - extracted_texture.filtering, - extracted_texture.format, - ), - ); - self.prepare_queue - .insert(extracted_texture.texture.clone_weak()); - } - } - - /// Adds a `TilemapTexture` to the texture array cache. - pub fn add_texture( - &mut self, - texture: TilemapTexture, - tile_size: TilemapTileSize, - tile_spacing: TilemapSpacing, - filtering: FilterMode, - format: TextureFormat, - image_assets: &Res>, - ) { - let (tile_count, texture_size) = match &texture { - TilemapTexture::Single(handle) => { - let image = image_assets.get(handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); - let texture_size: TilemapTextureSize = image.size().into(); - let tile_count_x = ((texture_size.x) / (tile_size.x + tile_spacing.x)).floor(); - let tile_count_y = ((texture_size.y) / (tile_size.y + tile_spacing.y)).floor(); - ((tile_count_x * tile_count_y) as u32, texture_size) - } - TilemapTexture::Vector(handles) => { - for handle in handles { - let image = image_assets.get(handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); - let this_tile_size: TilemapTileSize = image.size().try_into().unwrap(); - if this_tile_size != tile_size { - panic!( - "Expected all provided image assets to have size {tile_size:?}, \ - but found image with size: {this_tile_size:?}", - ); - } - } - (handles.len() as u32, tile_size.into()) - } - TilemapTexture::TextureContainer(handle) => { - let image = image_assets.get(handle).expect( - "Expected image to have finished loading if \ - it is being extracted as a texture!", - ); - let tile_size: TilemapTileSize = image.size().into(); - ( - image.texture_descriptor.array_layer_count(), - tile_size.into(), - ) - } - }; - - if !self.meta_data.contains_key(&texture) { - self.meta_data.insert( - texture.clone_weak(), - ( - tile_count, - tile_size, - texture_size, - tile_spacing, - filtering, - format, - ), - ); - self.prepare_queue.insert(texture.clone_weak()); - } - } - - pub fn get(&self, texture: &TilemapTexture) -> &GpuImage { - self.textures.get(texture).unwrap() - } - - pub fn contains(&self, texture: &TilemapTexture) -> bool { - self.textures.contains_key(texture) - } - - /// Prepares each texture array texture - pub fn prepare( - &mut self, - render_device: &RenderDevice, - render_images: &Res>, - ) { - let prepare_queue = self.prepare_queue.drain().collect::>(); - for texture in prepare_queue.iter() { - match texture { - TilemapTexture::Single(_) | TilemapTexture::Vector(_) => { - let (count, tile_size, _, _, filter, format) = - self.meta_data.get(texture).unwrap(); - - // Fixes weird cubemap bug. - let count = if *count == 6 { count + 1 } else { *count }; - - let gpu_texture = render_device.create_texture(&TextureDescriptor { - label: Some("texture_array"), - size: Extent3d { - width: tile_size.x as u32, - height: tile_size.y as u32, - depth_or_array_layers: count, - }, - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: *format, - usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }); - - let sampler = render_device.create_sampler(&SamplerDescriptor { - label: Some("texture_array_sampler"), - address_mode_u: AddressMode::ClampToEdge, - address_mode_v: AddressMode::ClampToEdge, - address_mode_w: AddressMode::ClampToEdge, - mag_filter: *filter, - min_filter: *filter, - mipmap_filter: *filter, - lod_min_clamp: 0.0, - lod_max_clamp: f32::MAX, - compare: None, - anisotropy_clamp: 1, - border_color: None, - }); - - let texture_view = gpu_texture.create_view(&TextureViewDescriptor { - label: Some("texture_array_view"), - format: None, - dimension: Some(TextureViewDimension::D2Array), - aspect: TextureAspect::All, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: 0, - array_layer_count: Some(count), - }); - - let mip_level_count = gpu_texture.mip_level_count(); - - let gpu_image = GpuImage { - texture_format: *format, - texture: gpu_texture, - sampler, - texture_view, - size: tile_size.into(), - mip_level_count, - }; - - self.textures.insert(texture.clone_weak(), gpu_image); - self.queue_queue.insert(texture.clone_weak()); - } - TilemapTexture::TextureContainer(handle) => { - if let Some(gpu_image) = render_images.get(handle) { - self.textures - .insert(texture.clone_weak(), gpu_image.clone()); - } else { - self.prepare_queue.insert(texture.clone_weak()); - } - } - } - } - } - - pub fn queue( - &mut self, - render_device: &RenderDevice, - render_queue: &RenderQueue, - render_images: &Res>, - ) { - let queue_queue = self.queue_queue.drain().collect::>(); - - for texture in queue_queue.iter() { - match &texture { - TilemapTexture::Single(handle) => { - let gpu_image = if let Some(gpu_image) = render_images.get(handle) { - gpu_image - } else { - self.prepare_queue.insert(texture.clone_weak()); - continue; - }; - - let (count, tile_size, texture_size, spacing, _, _) = - self.meta_data.get(texture).unwrap(); - let array_gpu_image = self.textures.get(texture).unwrap(); - let count = *count; - - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("create_texture_array_from_atlas"), - }); - - for i in 0..count { - let columns = (texture_size.x / (tile_size.x + spacing.x)).floor(); - let sprite_sheet_x: f32 = - (i as f32 % columns).floor() * (tile_size.x + spacing.x) + spacing.x; - let sprite_sheet_y: f32 = - (i as f32 / columns).floor() * (tile_size.y + spacing.y) + spacing.y; - - command_encoder.copy_texture_to_texture( - ImageCopyTexture { - texture: &gpu_image.texture, - mip_level: 0, - origin: Origin3d { - x: sprite_sheet_x as u32, - y: sprite_sheet_y as u32, - z: 0, - }, - aspect: TextureAspect::All, - }, - ImageCopyTexture { - texture: &array_gpu_image.texture, - mip_level: 0, - origin: Origin3d { x: 0, y: 0, z: i }, - aspect: TextureAspect::All, - }, - Extent3d { - width: tile_size.x as u32, - height: tile_size.y as u32, - depth_or_array_layers: 1, - }, - ); - } - - let command_buffer = command_encoder.finish(); - render_queue.submit(vec![command_buffer]); - } - TilemapTexture::Vector(handles) => { - let mut gpu_images = Vec::with_capacity(handles.len()); - for handle in handles { - if let Some(gpu_image) = render_images.get(handle) { - gpu_images.push(gpu_image) - } else { - self.prepare_queue.insert(texture.clone_weak()); - continue; - } - } - - let (count, tile_size, _, _, _, _) = self.meta_data.get(texture).unwrap(); - let array_gpu_image = self.textures.get(texture).unwrap(); - let count = *count; - - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("create_texture_array_from_handles_vec"), - }); - - for i in 0..count { - command_encoder.copy_texture_to_texture( - ImageCopyTexture { - texture: &gpu_images[i as usize].texture, - mip_level: 0, - origin: Origin3d { x: 0, y: 0, z: 0 }, - aspect: TextureAspect::All, - }, - ImageCopyTexture { - texture: &array_gpu_image.texture, - mip_level: 0, - origin: Origin3d { x: 0, y: 0, z: i }, - aspect: TextureAspect::All, - }, - Extent3d { - width: tile_size.x as u32, - height: tile_size.y as u32, - depth_or_array_layers: 1, - }, - ); - } - - let command_buffer = command_encoder.finish(); - render_queue.submit(vec![command_buffer]); - } - TilemapTexture::TextureContainer(_) => { - // do nothing, we already have the necessary GPU image - } - } - } - } -} diff --git a/crates/bevy_tiles_render/src/tiles.rs b/crates/bevy_tiles_render/src/tiles.rs new file mode 100644 index 0000000..f14141e --- /dev/null +++ b/crates/bevy_tiles_render/src/tiles.rs @@ -0,0 +1 @@ +use bevy::ecs::component::Component;