Compare commits
1244 Commits
e98093da94
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| aa727202af | |||
| ec6965b0dd | |||
| 82b8bd22cc | |||
| d0ce2d7883 | |||
| 65b3078c7f | |||
| f6fb396a41 | |||
| f064d6ed68 | |||
| 0afcaa0b3b | |||
| 00fa6fffa0 | |||
| ac836ae961 | |||
| 5edc4ed242 | |||
| d92afa27da | |||
| 8232f59276 | |||
| 16f994b5e5 | |||
| bc6fb25ff0 | |||
| f26b0024f2 | |||
| 3e5b7287c7 | |||
| 39c7ef5fdf | |||
| abf30ecfd3 | |||
| 61ecb7146d | |||
| 599f622ba4 | |||
| 3ea8ce81d6 | |||
| a67f8597ba | |||
| a02ff65651 | |||
| a3efcda550 | |||
| 86eb455ab9 | |||
| 5b0a1743d9 | |||
| 1e189ff558 | |||
| 9980aa9be5 | |||
| 1d171ea61c | |||
| e1734181a0 | |||
| c710063d92 | |||
| c4fe643427 | |||
| 23bdf9ed48 | |||
| 78556ea683 | |||
| 5a938935e1 | |||
| 882df1ae5a | |||
| e77dbe40b1 | |||
| 4c79554050 | |||
| 4fe456c1a2 | |||
| 3e6e997485 | |||
| 2a9264cfe4 | |||
| a4c48c1b3f | |||
| ee03f7035b | |||
| 3e56757910 | |||
| a91df8b4cd | |||
| 0060a348f6 | |||
| 2eaab2481f | |||
| c495581878 | |||
| 307259091e | |||
| 4b58df9a61 | |||
| 5de4848d70 | |||
| 3f871a4f45 | |||
| 804e5138d7 | |||
| af6de86647 | |||
| 91c62c6b14 | |||
| 4ee1bcc599 | |||
| 31a8125fc1 | |||
| 87bf83451b | |||
| 9950e0a44f | |||
| 8bd375cd24 | |||
| c0d62dc749 | |||
| c98b41f6f4 | |||
| 72d09a1c49 | |||
| ba91e0f5dd | |||
| 9064c2f5f2 | |||
| 5797a75619 | |||
| e83f911aef | |||
| dd2299c8b0 | |||
| b8d29e39f6 | |||
| 72914b3865 | |||
| e6950fa704 | |||
| 21b0530f7b | |||
| c3d443eb85 | |||
| d705cc839b | |||
| 0d6b8bf7d8 | |||
| 4362008b39 | |||
| 712f99e723 | |||
| 48daaa1bd0 | |||
| e462f7d6f7 | |||
| 5b89c2bb76 | |||
| f3fc34898a | |||
| d2140bf5cc | |||
| a0d5e84516 | |||
| 8ba05216fb | |||
| 0cc3d6da46 | |||
| adb6fe4659 | |||
| 95edf0435f | |||
| 82c55b3999 | |||
| 00875e0c90 | |||
| b7428b0ef1 | |||
| 1d6f2e290d | |||
| 2ee74e7761 | |||
| 64212a53c7 | |||
| 6f876678f5 | |||
| 2326857a43 | |||
| 0f60f0f657 | |||
| dd3731ba66 | |||
| 941034b387 | |||
| 89590242bd | |||
| a660fc489a | |||
| e86d260d64 | |||
| 347d08463b | |||
| 7ee28a7969 | |||
| b7ce8618d2 | |||
| 7ad4bfbb1c | |||
| 838f676fa6 | |||
| 0ff02150c0 | |||
| 8848cfd958 | |||
| 3e55f8c204 | |||
| 0a015b52ca | |||
| 030230eb1f | |||
| 8f71f99de4 | |||
| 443c56ed08 | |||
| 2958dcc491 | |||
| 35d3d6328b | |||
| c03c7379c8 | |||
| 0a2bdedc59 | |||
| 2fb6eca854 | |||
| c543ccf79c | |||
| 88a71a5426 | |||
| ff4e3f639a | |||
| 92d5cc61cf | |||
| b3acb5afc2 | |||
| 785377bc9b | |||
| 5200fca82f | |||
| 39632e1a04 | |||
| 3622bf3aa2 | |||
| fac6e588a8 | |||
| 5191bb1149 | |||
| d9bc0f1457 | |||
| 4080b2e5fe | |||
| be5dabd820 | |||
| 107b320aa7 | |||
| 15b42c248f | |||
| 977a4cf2a4 | |||
| b187c8970b | |||
| 1119af2e38 | |||
| 2338e306bf | |||
| 87ad489bfd | |||
| 503e6408ed | |||
| 8f5c342799 | |||
| 84faa585d5 | |||
| ac9388445c | |||
| 3561bf22bb | |||
| e6ac43b454 | |||
| 2f3a28ec3e | |||
| 737ccd2e0c | |||
| bdf0b9a16b | |||
| 0c52e0f640 | |||
| bb9a4d5ef4 | |||
| a990553ade | |||
| de2fc8be43 | |||
| effca78771 | |||
| 0602b34652 | |||
| 85b8b3e583 | |||
| cba3823ea2 | |||
| 89bbad2786 | |||
| 00cf3850d5 | |||
| 81d0d92aed | |||
| 2d8ada03a1 | |||
| 7fc7bb0a22 | |||
| 447977214e | |||
| 46fac8a215 | |||
| e2e4e08479 | |||
| e69240db49 | |||
| dd467d2468 | |||
| 71a27d7c9c | |||
| f401a54806 | |||
| f917040e9a | |||
| 66ae9ec919 | |||
| 6b90c2f6c3 | |||
| 4d8a51aee2 | |||
| 8cde4e0649 | |||
| 1f79afba3c | |||
| 57331c1c25 | |||
| ff6d6d31fe | |||
| 152997c409 | |||
| 54eb2415ff | |||
| f476890116 | |||
| f9bfd48479 | |||
| 131f46682b | |||
| 899442c64d | |||
| b5ba985831 | |||
| 4debbbea1f | |||
| 4111f841d4 | |||
| 34a32b73dd | |||
| 02a0e626fe | |||
| 4b47764f26 | |||
| 225f533c7c | |||
| 9b074e3020 | |||
| 3ed69aa82f | |||
| a545951a58 | |||
| 2bb511087f | |||
| 4aaac9887e | |||
| cb3d558aaa | |||
| b0625a30bd | |||
| d4bec254d1 | |||
| 7733d59ed1 | |||
| 681452c70e | |||
| 20b5c22a6a | |||
| 84db2e951d | |||
| 7e60bc568a | |||
| 66d0bc1d4f | |||
| ca2ce00e4a | |||
| 4f0fdbfced | |||
| 02779dbb4e | |||
| 17d331afb1 | |||
| 2084412010 | |||
| c48311eaaf | |||
| d46bf87970 | |||
| 23b23a56be | |||
| 9841d5667d | |||
| fde99a4d34 | |||
| b839fd98af | |||
| c6815fa809 | |||
| 6bf9203eec | |||
| bb0f4afe7d | |||
| 4728b09ae8 | |||
| 162f1cc12e | |||
| 31756847ab | |||
| 08c3278e10 | |||
| efdd6bd68f | |||
| 2c1c815072 | |||
| 6113ed92b0 | |||
| 7be3b2cc45 | |||
| 2e961295bf | |||
| 0a392e1311 | |||
| 805e07bf90 | |||
| 75defb0a49 | |||
| 6e6a98a022 | |||
| 077a6b0a51 | |||
| b5bc9d41cb | |||
| 23ff4004f4 | |||
| 69cb80ccd4 | |||
| 6289777c8e | |||
| 4c26b410cb | |||
| 7711a8151e | |||
| 253d6bbf85 | |||
| fe00aa1305 | |||
| 901a6ecc26 | |||
| 2f9b1696cd | |||
| 5a3232c099 | |||
| e22ce763c2 | |||
| 0308be1483 | |||
| 442565f176 | |||
| 1f140c4bab | |||
| b2ab516228 | |||
| 6bf61ad8e2 | |||
| e7669dc8c3 | |||
| 3cf4adf181 | |||
| 49e9b63a2d | |||
| d14fa6be07 | |||
| 247571b815 | |||
| fb8ef25ff3 | |||
| 3def94d0e0 | |||
| d3377708d2 | |||
| 35cd535b4c | |||
| 864438c508 | |||
| 1c87650fb3 | |||
| 0d6a4113e7 | |||
| 5d57101da3 | |||
| ce1995659a | |||
| f31fece2ce | |||
| ec06340f58 | |||
| 558b6438cf | |||
| 7f4b647394 | |||
| 5913462178 | |||
| 945420f3bd | |||
| 3c0dedcc5f | |||
| 044240d2f1 | |||
| a53f47e561 | |||
| 93ceb61483 | |||
| 5bfe484f5d | |||
| 7f0d1f0b08 | |||
| 8eeb7af56e | |||
| 5f9f3386ab | |||
| 503ffbc4ff | |||
| 6c90bb4eca | |||
| 3b2a05a098 | |||
| 998df9013a | |||
| 87533e08f6 | |||
| 7216ad9138 | |||
| 83f316a91f | |||
| 620717f8b4 | |||
| 9568cf0a16 | |||
| eea38d57d1 | |||
| c318f34f07 | |||
| 0761079b4c | |||
| 2a61f0b20a | |||
| f16620afc6 | |||
| 261dd44fd5 | |||
| a8b4da16a3 | |||
| 7acc397714 | |||
| 97e986b52c | |||
| f912e81ade | |||
| 4afeb19d25 | |||
| b68e514154 | |||
| 806ef74226 | |||
| 9015b461bb | |||
| eef5de7ee9 | |||
| be4c43f11e | |||
| 2ec24666c0 | |||
| 2d030a97da | |||
| 33bb84f650 | |||
| 6645d507d0 | |||
| b6132aec4d | |||
| 3a64c325bf | |||
| f0d3f251b4 | |||
| d51d8ca095 | |||
| ff49120ffe | |||
| 6a1ed4be68 | |||
| 2b70a2e309 | |||
| 0804052d6f | |||
| b14a4fb7bb | |||
| 3540dbc94d | |||
| 31d14abece | |||
| 5e489b61d2 | |||
| 3547597bd2 | |||
| c7dc8d7484 | |||
| dc17685099 | |||
| ae4c06d7b6 | |||
| f014ae6e6f | |||
| 66a6818b89 | |||
| 8151be0f45 | |||
| be2013f3c4 | |||
| 58f97c416b | |||
| 5342e447af | |||
| 1908e81e5c | |||
| 5ae1b03a9e | |||
| 4236427fca | |||
| c79274d6b4 | |||
| dc0e8b938f | |||
| 578f5dd99c | |||
| 7812b92992 | |||
| 05debc0499 | |||
| ba6c8eaae5 | |||
| 18f3f9f227 | |||
| af269b53ca | |||
| fabfcb9d40 | |||
| b693c079da | |||
| d52b85aff9 | |||
| 3dd8867a17 | |||
| 3c5529c4bc | |||
| cb40da83ff | |||
| 8904c3f5c6 | |||
| 1de8091b77 | |||
| 31693a83b6 | |||
| dabd4dd8b2 | |||
| 081773b8c9 | |||
| 15a1a5edd5 | |||
| 5fb2235eb7 | |||
| 8ced88e847 | |||
| a600e73fb2 | |||
| 3db09ea5d0 | |||
| 63b5f12b93 | |||
| 97f7c26eff | |||
| a94b005b76 | |||
| 5c75919b14 | |||
| 3968083da7 | |||
| 94482ab98c | |||
| 54a699aa26 | |||
| cda4a57756 | |||
| cbd888e745 | |||
| 74b1280aa6 | |||
| 712cc79c44 | |||
| 9bad86eccf | |||
| f03a8f63ec | |||
| 9db0d82082 | |||
| 18f53bd920 | |||
| 2c96f0d164 | |||
| f6da4d0eb6 | |||
| 050502cf78 | |||
| 6fd3ed434d | |||
| b05e76de0c | |||
| 231df6ee36 | |||
| daa54e0230 | |||
| 551eefbaa1 | |||
| e22d7d7f3d | |||
| ec96d2c7e5 | |||
| f407e2d15c | |||
| 10fda68694 | |||
| 8c238400a8 | |||
| f943a07862 | |||
| 56f596548d | |||
| a35adf14d3 | |||
| e5e9f348a3 | |||
| ec97445071 | |||
| bc6722e5ab | |||
| 68c4c80b06 | |||
| 511e94fd30 | |||
| 646e5855ce | |||
| d46dcbfa9e | |||
| edf434aa03 | |||
| a1b98abfbb | |||
| 1fd6163647 | |||
| 9d994dcdb1 | |||
| c6e0973fe7 | |||
| 585575a738 | |||
| 9525053624 | |||
| 6159eef3af | |||
| b4c95e4085 | |||
| da85109a31 | |||
| 0c24c7c611 | |||
| f185663259 | |||
| 6f591e6ce3 | |||
| 7b49a621cc | |||
| ff44b15396 | |||
| 264f8c7372 | |||
| d1cb0c874b | |||
| a7662a1d43 | |||
| 6dcf881967 | |||
| 01c54d017f | |||
| acdbcdd35b | |||
| ade5be31d6 | |||
| 67a28bdd4a | |||
| e23f469e5a | |||
| 6a5c23dce2 | |||
| 811958351e | |||
| cb7d60ec13 | |||
| b2774f1745 | |||
| 061e74d98f | |||
| 2ddbe24b82 | |||
| 96a44da2cb | |||
| 353d129613 | |||
| a548e0d0a9 | |||
| 781c3b9a78 | |||
| bcef1f145b | |||
| 013e5a73b9 | |||
| 61047866dc | |||
| 95c4a50186 | |||
| 75ded6f630 | |||
| 0c23509e1a | |||
| 341ce79231 | |||
| c2cb2e5914 | |||
| 611ca705c8 | |||
| a9bf9ef35c | |||
| 0807b5a753 | |||
| 5295ef3718 | |||
| dc5b5d2c48 | |||
| 6e58016009 | |||
| a833a95216 | |||
| c6b835a390 | |||
| 98b307bc6d | |||
| e0481dd6b5 | |||
| a74c25b5ae | |||
| 672f25f9b7 | |||
| ccbbb481f4 | |||
| b9cde699cc | |||
| 8d715eb40f | |||
| 65d55c8536 | |||
| ee8b5e8c44 | |||
| 24245decb5 | |||
| 9f8ab921bc | |||
| 1a236b866d | |||
| 9e8810e593 | |||
| c0124443cf | |||
| 0c52245055 | |||
| aac17a1684 | |||
| bd35b8b4e8 | |||
| a3ba08bb99 | |||
| 0ebd2d4979 | |||
| 038194b75a | |||
| b8de12b8e3 | |||
| e636abb76d | |||
| a920ca7a6a | |||
| c3680258e0 | |||
| b7523db30d | |||
| 8de68c0fc9 | |||
| d207043f8f | |||
| 64a2efe993 | |||
| 2abe0bbbd4 | |||
| 42e2e1b8f2 | |||
| 2620b5914b | |||
| 8abca3dec5 | |||
| 468dbfa7ac | |||
| 308b3b061c | |||
| 1ac2afb0bb | |||
| 27014e613e | |||
| 5d24fd0337 | |||
| 2de254a16f | |||
| e0e5c1fcaa | |||
| 73415915e6 | |||
| 03bd755e0a | |||
| 052ac28aa3 | |||
| 2b19b4bece | |||
| d4d825e1b1 | |||
| 0e04217662 | |||
| 545112b497 | |||
| 2c2e1fab1c | |||
| ee44e14960 | |||
| 7ab49fdbf3 | |||
| 76f1a8e9b2 | |||
| c7eb9857cb | |||
| aaeb885566 | |||
| d33520752b | |||
| 24a200e126 | |||
| f1981dd523 | |||
| 4616e6fd1f | |||
| e8f64407ba | |||
| 7cc4aa3b45 | |||
| 19bd38ab15 | |||
| d4afa022c1 | |||
| 0f51f553c8 | |||
| 5225faff1d | |||
| 9edf378085 | |||
| b882610bbc | |||
| 608e0bc9d8 | |||
| c04dbb07e0 | |||
| ed95aa1dc5 | |||
| e4a4e90592 | |||
| 73821ff73f | |||
| 62e0d8b46d | |||
| 30f6f527fa | |||
| efda9c23e2 | |||
| 77784fe6d0 | |||
| 4016b35214 | |||
| cbc653fc97 | |||
| da157de59a | |||
| a05d0b80a2 | |||
| ed8c27fde2 | |||
| 0a76d2150a | |||
| b3c5fb59a0 | |||
| 2138195b69 | |||
| 6636834b35 | |||
| b43d4048b8 | |||
| ca019157be | |||
| 1c8c28e32b | |||
| ffb62ddd9c | |||
| c63dcf2229 | |||
| 307e8dbd0e | |||
| da2782c0c0 | |||
| c9adc6ec5e | |||
| 19db2305d9 | |||
| 2f9b416b22 | |||
| cd03465cd0 | |||
| 5ff97b437a | |||
| 71923267e9 | |||
| 5c42f14d0e | |||
| 3f9e286637 | |||
| ec2891b16b | |||
| 00ce503762 | |||
| f7d7d08d99 | |||
| 8e362fc4c0 | |||
| 84e1ba3600 | |||
| 9ce779da43 | |||
| 1f29dfd611 | |||
| fb15d60be9 | |||
| e30f5d5ffa | |||
| b2d0570b1b | |||
| 11fb8f3585 | |||
| 9f7d8fd68d | |||
| 86144416af | |||
| dd08d8969e | |||
| 33f16597fa | |||
| 70ced2d91f | |||
| 35d9b1d465 | |||
| a5d2058818 | |||
| 617b11f801 | |||
| ec7a15d85b | |||
| 697deb4e41 | |||
| 0d3851204f | |||
| c080890c9d | |||
| 7404108a4b | |||
| 4c167bec0e | |||
| 619856ab22 | |||
| 569bc144e7 | |||
| 410b1e59c3 | |||
| fd39808c91 | |||
| d7e9e978d0 | |||
| 8cd7fc0ca1 | |||
| 0a0544cbe4 | |||
| 784af81b07 | |||
| 846666bb2c | |||
| 1fbbf318de | |||
| 1af958a272 | |||
| 6cd4cd9be9 | |||
| bc6e20de48 | |||
| d58962ad5e | |||
| 55b3021159 | |||
| 44771d3cc1 | |||
| 1ff2800b29 | |||
| 12b1081dd0 | |||
| b85571d9d4 | |||
| 409e08d03c | |||
| 67d5390121 | |||
| 6927b4b380 | |||
| 6e2711f9e8 | |||
| 3f18530396 | |||
| 4e8ad9a706 | |||
| e03f17146a | |||
| d85b2ca056 | |||
| a908e61ecb | |||
| f80fb9860e | |||
| 0fe02fd1b4 | |||
| 51736253e3 | |||
| 96a66d6f6d | |||
| 1af3cf87c4 | |||
| 618ebed05d | |||
| 3373119eee | |||
| ac2b7c1fa2 | |||
| 7ff5cd4cf2 | |||
| 6a38230f3f | |||
| c17fcd450f | |||
| e6076ecc5a | |||
| f85fa78dd1 | |||
| ad237cb81e | |||
| 6d3a90ef74 | |||
| be15bc2fc4 | |||
| b92f9bfa70 | |||
| 2a31628db1 | |||
| b489492af0 | |||
| 416aa72194 | |||
| 7aca8199be | |||
| c8f79dfb0f | |||
| 0ea1cb29e6 | |||
| 2651bad080 | |||
| b0427b7091 | |||
| 6683e3c299 | |||
| e5cb79f3ce | |||
| eb5de3e3d4 | |||
| a519fdab7d | |||
| 569f8ef725 | |||
| af2f30dad6 | |||
| da486075e1 | |||
| 001e45bf6b | |||
| 3cc823aebd | |||
| 3c45a051a2 | |||
| 2b255751b6 | |||
| 3b652ac1db | |||
| 6fcb6ac8fb | |||
| 519bc1dbf2 | |||
| ec1535ad25 | |||
| 1fa97dc246 | |||
| 359fe2adb3 | |||
| 14c7fd69ec | |||
| 4717b595c4 | |||
| 4a12e26860 | |||
| 94795cb34d | |||
| bce938ac58 | |||
| ae93952ce0 | |||
| 36097869c0 | |||
| 664fec73bc | |||
| 7015736fdf | |||
| c01944871d | |||
| e522bad582 | |||
| 6bde199393 | |||
| cd756ec9a3 | |||
| 93d16f3914 | |||
| f55c6d8ffb | |||
| 3d2fd5c8ee | |||
| 539df6a03a | |||
| 114985d698 | |||
| a1bc8f163e | |||
| b77615569c | |||
| 7d6032be23 | |||
| e546afa327 | |||
| d38c991688 | |||
| 6d0a61e70d | |||
| 837a4ce631 | |||
| ca6a4be994 | |||
| 6b3f5a9b93 | |||
| 291d1a7e41 | |||
| 0c8a3e90ec | |||
| 14e9190a35 | |||
| b1ddf77e51 | |||
| 77ff318558 | |||
| f37c9261d8 | |||
| e3ddf10d79 | |||
| c7ef9191e4 | |||
| 37a90b39e2 | |||
| a51e0f6f88 | |||
| 1ea00a1879 | |||
| 126860e79d | |||
| 3514e424de | |||
| 6aa0e73a05 | |||
| 5a49812ea9 | |||
| bf79bd344e | |||
| 53ac1dbc44 | |||
| 704d2067ce | |||
| cf8e669f75 | |||
| dbec62608c | |||
| a72f9f7f05 | |||
| 26035e3940 | |||
| 9e66a81514 | |||
| f0d6d4f41c | |||
| 9c94adb4a2 | |||
| fe56117d8e | |||
| 22ccdfb371 | |||
| 18fd09b36f | |||
| bea849646f | |||
| 2b0df52446 | |||
| 94c56dd279 | |||
| 3e2608a802 | |||
| 727b6ca249 | |||
| 4b21b5d3d1 | |||
| b06932724c | |||
| 134a80b334 | |||
| 79e7452245 | |||
| fd0b19fd11 | |||
| 4afe2f88ba | |||
| 9a5c187abc | |||
| 4b9a63098e | |||
| c33404767e | |||
| 90961f01aa | |||
| f68da2e3f9 | |||
| c97510ed5b | |||
| 6ec5f05601 | |||
| 3ebad63874 | |||
| 31675e00c8 | |||
| f87bc53875 | |||
| 8bdb1f34c7 | |||
| 5735e769b0 | |||
| 5c8042775c | |||
| f6286d432c | |||
| 6467d87b81 | |||
| 8f486611d5 | |||
| 9a2d77b81d | |||
| a78593e7e1 | |||
| 0921f2a459 | |||
| 5ca5ca1f19 | |||
| dc252502ac | |||
| ce2eee32e3 | |||
| ec4edb2689 | |||
| 7a814c724d | |||
| 93de4199d1 | |||
| 2e2316aa55 | |||
| 122495e581 | |||
| 6244b586bb | |||
| 45842e961e | |||
| e174862b8a | |||
| c479595bf5 | |||
| c40712a107 | |||
| 18fa150843 | |||
| 491fef940d | |||
| 733b573963 | |||
| 9218ea20b5 | |||
| 476a56724f | |||
| 36d2f479cd | |||
| d8e14df78a | |||
| 6f1cbbf305 | |||
| cb05472205 | |||
| b414bc5326 | |||
| d018a4c82c | |||
| 1ef3048da1 | |||
| c47e871c5a | |||
| 10ee1fa3fa | |||
| 48b73a5045 | |||
| a2765a282e | |||
| 32cf6b8ced | |||
| 4ca8773eef | |||
| 358dedeeeb | |||
| caa5d449fc | |||
| 582ab2c730 | |||
| 02e48b4766 | |||
| c2354530b9 | |||
| ab5ba1d57a | |||
| ca83267992 | |||
| c8da156352 | |||
| a1804f4cb0 | |||
| f778c76325 | |||
| 92939a85f8 | |||
| b849e9edc1 | |||
| 5c3566774b | |||
| 8df04c120f | |||
| 445876752c | |||
| 52d5e98f62 | |||
| 56ee358b5c | |||
| efe4f30d85 | |||
| 2e17c0019c | |||
| 4b7d05d22d | |||
| 5047d56080 | |||
| 1326860e5d | |||
| 1f97102f33 | |||
| fae7362e9f | |||
| 14ffde371e | |||
| f5a34f8adc | |||
| 7c3f304688 | |||
| 63d0271a5b | |||
| d34d040563 | |||
| 1cf744b755 | |||
| 0651666d8c | |||
| 39edb0b497 | |||
| c5605c2a32 | |||
| 9adac63b4c | |||
| 76c4c2ace2 | |||
| 605ef56e16 | |||
| 83cd2fa591 | |||
| 8f9a2d17a5 | |||
| 8fabdc603c | |||
| 2470451d96 | |||
| 1597181458 | |||
| aaf9cce418 | |||
| 268daf7bc9 | |||
| 30b5f93157 | |||
| 8f76564ded | |||
| a9b9a6ebfc | |||
| 04a80d10e7 | |||
| 6bbd35873b | |||
| 81532983f0 | |||
| 5ade399df2 | |||
| 712e975610 | |||
| 773d1aa38a | |||
| e323b45595 | |||
| 238ebb50f4 | |||
| d1b7fda816 | |||
| b11f59e144 | |||
| b0d0576763 | |||
| 6612330347 | |||
| 295459067f | |||
| 9c082c72fa | |||
| 621a135ef7 | |||
| 605d086bcc | |||
| 48d9e0a7d2 | |||
| d4c94907ba | |||
| 56116b62c3 | |||
| dc63808a65 | |||
| 16e2065c6c | |||
| b08f682e5c | |||
| 008fb98dee | |||
| 56ec2e9b85 | |||
| 3478fb414a | |||
| 720dd422d5 | |||
| da1f8cfb58 | |||
| 920d3be78b | |||
| fbbf5dca55 | |||
| 2ac32e1330 | |||
| 41cea4d1a2 | |||
| 2313124cc5 | |||
| cad6f586fb | |||
| 0834ccb7aa | |||
| 0948e0fdbe | |||
| f808f8d197 | |||
| ca0d73c197 | |||
| 32c04b86b7 | |||
| 600892bbe2 | |||
| c9e459c179 | |||
| dc970d215b | |||
| 6328ac8d37 | |||
| da48b808cd | |||
| a2fc8eca02 | |||
| f8a2507bdf | |||
| c6fe9547aa | |||
| c5c43ae7aa | |||
| 5eb731bc2d | |||
| 1e88beacb8 | |||
| 7a66913f2b | |||
| 9fae910854 | |||
| cab290b17d | |||
| 0f5d018c1a | |||
| 135fe9145b | |||
| c66ba2feb3 | |||
| 6ed033890a | |||
| 612342d170 | |||
| 4daed24a05 | |||
| 0dde7234b7 | |||
| ac5c98584a | |||
| d575532966 | |||
| 0b3423966d | |||
| 0345ce50cf | |||
| 50c0ffdb9e | |||
| b1829bcfc5 | |||
| 1d1eee823d | |||
| e2050e35ec | |||
| eb4840eac4 | |||
| 0a3fe842b9 | |||
| 86b6d6b042 | |||
| 92ab6f5484 | |||
| 512161bf81 | |||
| 3147adcc3e | |||
| 08c01dd143 | |||
| 92d817e16e | |||
| b8ce6870fe | |||
| c700e536c5 | |||
| 5811967679 | |||
| 36c1c8338f | |||
| 062984953e | |||
| 5267c61c2c | |||
| 89d13a4ae4 | |||
| 003d6ed630 | |||
| 0fa4f2e3a8 | |||
| bc6b47ffcf | |||
| df7764e972 | |||
| 3af24bb1bc | |||
| 6f6bb13da4 | |||
| 79422cfddd | |||
| 067c82c3a9 | |||
| bf44438934 | |||
| 6a7be5c6fe | |||
| 257706eddd | |||
| 0ff25a9fcc | |||
| 66f013a4fc | |||
| 140c8b8bcc | |||
| 6935a91a1f | |||
| 66df465661 | |||
| aa9b91342e | |||
| 84e7213e86 | |||
| 1f84b3bc3b | |||
| fb01beb959 | |||
| effc969ad3 | |||
| 1acea6bf69 | |||
| 36683b4bb3 | |||
| e9f4f2dc49 | |||
| 1cc545a91f | |||
| 81dc337262 | |||
| d58d98cb68 | |||
| 537808c64d | |||
| f427699ac6 | |||
| 0f0ab8922a | |||
| a4e7785751 | |||
| fa50892150 | |||
| 50b50d06a0 | |||
| 74adeb74a0 | |||
| 0eadc7cfd1 | |||
| 70cc86793f | |||
| 7d1362a41e | |||
| 6c92164a03 | |||
| fa2d2713d7 | |||
| cf9229fc21 | |||
| 8d4447915d | |||
| a980f2bd66 | |||
| 36d3decef6 | |||
| 6af872e9eb | |||
| 1797e7fe17 | |||
| 1f129ed20f | |||
| 7028027adc | |||
| 750ac95951 | |||
| a399eeec26 | |||
| 70571d11df | |||
| a9d5a68dd6 | |||
| d83ed56177 | |||
| 11919aad2f | |||
| f8e7edd2c1 | |||
| 3357de85c9 | |||
| 1358bb0a5a | |||
| 2432a646ce | |||
| 161a0896d5 | |||
| 6e5ed41fbf | |||
| a172d75e36 | |||
| 629455df07 | |||
| 7a6cd412c8 | |||
| a6c6482125 | |||
| c563db3123 | |||
| 91291b2075 | |||
| 36119e62aa | |||
| 00c2699542 | |||
| 2cc9d58edd | |||
| b68cde82b2 | |||
| dfc948fc89 | |||
| d786914552 | |||
| 85c106d5dd | |||
| 47808f5f90 | |||
| 00f70eccf1 | |||
| 93139813aa | |||
| 810b0861c5 | |||
| 394bec9db6 | |||
| 5201638bb1 | |||
| 60d11f3109 | |||
| 05a57addc7 | |||
| 80e47a0ab9 | |||
| f8573d2715 | |||
| 28bf76cb00 | |||
| d6ff7b6d1b | |||
| f1ad13430b | |||
| 761552273b | |||
| 572e0e9bd5 | |||
| 45fd25dce3 | |||
| 26fe3cd835 | |||
| 460a2477c3 | |||
| 34c04af6cb | |||
| 0017388498 | |||
| 0a19fdfb0f | |||
| 3cd3b04c7e | |||
| a40544344b | |||
| 376fa08e56 | |||
| c52b4ef35c | |||
| dba3dc23f2 | |||
| 33e9958751 | |||
| 31273fdac4 | |||
| 983d6d61cd | |||
| 9ad699cd6f | |||
| 9c5bd3c33f | |||
| b2228db3ee | |||
| e242b0f5a7 | |||
| fae520854e | |||
| 4c6e7af02e | |||
| b7d66a09de | |||
| e01dcfe6ed | |||
| ad0c265c4c | |||
| 77ef74bec6 | |||
| 070b444f8f | |||
| ea756c0177 | |||
| fd792b7df1 | |||
| c5b17239ca | |||
| e165dbea1c | |||
| 0c073db4e8 | |||
| a647f5e8ec | |||
| 7e1782e203 | |||
| 5257f3d75c | |||
| 8e85fd98b8 | |||
| b46d450429 | |||
| 2076a484d4 | |||
| 7e4c48d4f9 | |||
| 8c719418d0 | |||
| 94beec946b | |||
| 7332a2a592 | |||
| 12ae6f561a | |||
| 2141534995 | |||
| 452ccd4f8f | |||
| f436280aa5 | |||
| de4086dbfe | |||
| 3ac93ba2e8 | |||
| 7dd7858ef2 | |||
| 71413381af | |||
| a669ec819d | |||
| 086eb877b5 | |||
| bd530dc8b1 | |||
| 6a952473ce | |||
| 98c764bab9 | |||
| 5fc18eac6f | |||
| dae1a63a90 | |||
| 82cf147817 | |||
| 870cb3116e | |||
| a9f882f233 | |||
| dc850d7739 | |||
| d0e16962c8 | |||
| 9bad996ecf | |||
| fc7c8f6797 | |||
| 3196261e9b | |||
| 8344057886 | |||
| 87835c5f7a | |||
| 508d8b165b | |||
| 17e71218e7 | |||
| 65ce9c84c6 | |||
| 60c8461be3 | |||
| 83c2426830 | |||
| a532cabf92 | |||
| a220638298 | |||
| 70571316da | |||
| d2585f14b3 | |||
| bd69c3e124 | |||
| 02ca59edf6 | |||
| 640557cbb5 | |||
| e71edc2f46 | |||
| 8cc4322d22 | |||
| 2325b4ba7d | |||
| b7feca59c4 | |||
| 6fe21710e8 | |||
| 254f794cdc | |||
| 0b50a57239 | |||
| 6ca1487279 | |||
| 0e31cd804b | |||
| 417477c2ca | |||
| 967c64faf8 | |||
| 4710e6ba60 | |||
| 05c879a818 | |||
| 0a2f8050e5 | |||
| 94bf04f06c | |||
| e138fb2075 | |||
| a257ff2d8b | |||
| 20445999fc | |||
| 354b6a5cfc | |||
| 55865a0252 | |||
| 4638539f17 | |||
| 14fb51e61e | |||
| af718279ff | |||
| f046e17ad6 | |||
| e38d5ccede | |||
| f2ae95e0a7 | |||
| 7bf586f6fa | |||
| 4b14831c57 | |||
| 276a9c476a | |||
| 1d181f1109 | |||
| 745f3ab225 | |||
| f42a0795fb | |||
| f5031c89d4 | |||
| 19b33a3061 | |||
| 3f8805cde8 | |||
| 11ea2a4fc5 | |||
| 0049f8334d | |||
| 795cb10183 | |||
| dae540e97a | |||
| 8c6516183e | |||
| d1c7c23527 | |||
| 05c4dfb5eb | |||
| 5683b10e65 | |||
| 2342e3fbfc | |||
| 0418c61db6 | |||
| d75780f8c4 | |||
| be72e2f4a7 | |||
| 6126404e3f | |||
| 1de66b835d | |||
| 413f4c178f | |||
| a54666df11 | |||
| 56c32bfbde | |||
| 4c9c03e1a7 | |||
| 393a0c67f1 | |||
| ac9a62082e | |||
| 312699e262 | |||
| 271c05d8c7 | |||
| 9fe79681fb | |||
| f615a86aab | |||
| 07450e76a4 | |||
| 83e91b16c7 | |||
| 97653041bd | |||
| 9fda349fa1 | |||
| f4db1eafea | |||
| 3d6787b6a4 | |||
| 64bd8c5074 | |||
| 73627f62f4 | |||
| 988f94eb29 | |||
| 7874033379 | |||
| 210bc450fa | |||
| 4b41a4cca1 | |||
| 5bcf9d74a3 | |||
| 472f106a12 | |||
| 4a0f6d65d1 | |||
| 801c563eb5 | |||
| bf98fa0b89 | |||
| 3cd47ea4c8 | |||
| aee4ae88db | |||
| 377f43260b | |||
| fce3d2421c | |||
| 0be91748c2 | |||
| 430d23b719 | |||
| 220494c3c5 | |||
| fee738b0b9 | |||
| 434ba0f336 | |||
| 170df5506b | |||
| 6aaf89e603 | |||
| 9c29cfa0a6 | |||
| 722b6b86ba | |||
| 0ce312e648 | |||
| 0014c32fa5 | |||
| 11db594967 | |||
| 77a121fc4f | |||
| d2d45bd973 | |||
| 3a78065574 | |||
| 9314aae32d | |||
| 2b3ac27243 | |||
| 554c48448b | |||
| f4d94bda3d | |||
| 1d99ef4d31 | |||
| 8c1d68da57 | |||
| 068fea77f4 | |||
| 37750fda7d | |||
| f231e3dc18 | |||
| f063eb9329 | |||
| 999c12a00e | |||
| fb2b794156 | |||
| dfbd218435 | |||
| 4af4326767 | |||
| 93bfba073c | |||
| d52028e196 | |||
| 2a5fc4f0d4 | |||
| 3e6388c221 | |||
| eab95df004 | |||
| 041d9ea422 | |||
| b9285f37b1 | |||
| a557ed75bf | |||
| e1bbe24f0d | |||
| 13818fe641 | |||
| 38e23e45c4 | |||
| 632cba821d | |||
| 70d3879687 | |||
| ff5dfc21db | |||
| 3959f74908 | |||
| a1f0de4e4d | |||
| f1cbf4e3a6 | |||
| 42c17ee106 | |||
| c62dc58157 | |||
| f25672c7d6 | |||
| c3feeda5d4 | |||
| db8e8633c8 | |||
| 017bbf281d | |||
| 2a8f50134c | |||
| b98f588afd | |||
| 2bdd6d3199 | |||
| ee7e710141 | |||
| 95fd1400bc | |||
| 88cd65d082 | |||
| 80a11d1836 | |||
| f187aa3b37 | |||
| 407fe0fd32 | |||
| 7f064e9e71 | |||
| ddd3140114 | |||
| 7050e88c49 | |||
| bf37b1c00c | |||
| 58341c9daf | |||
| e807dbcd96 | |||
| 3600e0e74b | |||
| 182025be6a | |||
| 4881aee70a | |||
| c79533c436 | |||
| 60db4b77f8 | |||
| c59b8c501c | |||
| 3767f3a6c5 | |||
| c6d7ef4c39 | |||
| e7b32e55f7 | |||
| 549178de35 | |||
| 3d285fa98a | |||
| 1a5bcd75d9 | |||
| 17c8ea46c5 | |||
| 8fb11dc650 | |||
| cba4f9c838 | |||
| b2c7627a1b | |||
| be6abd7bf6 | |||
| 3e89489078 | |||
| f427eb2588 | |||
| 9932d860a7 | |||
| 977054164c | |||
| d8882ab93f | |||
| 9a7a6102b5 | |||
| 827a0c6302 | |||
| 15f42a1af5 | |||
| ab29013c01 | |||
| 3ad317afb2 | |||
| 5f12393424 | |||
| 6a0dfb150d | |||
| 83fd517974 | |||
| dc9b0751cb | |||
| fa3e48b0fc | |||
| 34c75e7129 | |||
| 508ee0bdc8 | |||
| 7c54a62f9e | |||
| 5efa171050 | |||
| faf99dfc68 | |||
| 7d3b05573d | |||
| de9d9dfa1c | |||
| 4887bdd7fe | |||
| 909f430c7a | |||
| 1fc84b9538 | |||
| 51251f08a4 | |||
| 0c02aa2ae6 | |||
| 6e12efb67e | |||
| da7f19e9e8 | |||
| 65908526da | |||
| 4bcd1055dd | |||
| c1473d2d39 | |||
| f8fed72cb7 | |||
| 64dd8339dd | |||
| a2f3db8718 | |||
| 7d3e3b464d | |||
| 44880f03c0 |
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
MVS/3DGS-Unity/*.ply filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,8 +1,14 @@
|
||||
build/
|
||||
build2/
|
||||
bin/
|
||||
!engine/third_party/assimp/bin/
|
||||
Release/
|
||||
project/Library/
|
||||
project/.xceditor/thumbs/
|
||||
参考/
|
||||
*.exe
|
||||
*.obj
|
||||
!tests/fixtures/Resources/Mesh/*.obj
|
||||
*.tlog
|
||||
*.log
|
||||
*.cmake
|
||||
@@ -12,3 +18,9 @@ Release/
|
||||
CMakeCache.txt
|
||||
cmake_install.cmake
|
||||
Res/NanoVDB/
|
||||
*.rdc
|
||||
*.pyd
|
||||
*.dll
|
||||
!engine/third_party/assimp/bin/*.dll
|
||||
工作/
|
||||
.trae/
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"C_Cpp.errorSquiggles": "disabled"
|
||||
}
|
||||
363
AGENT.md
Normal file
363
AGENT.md
Normal file
@@ -0,0 +1,363 @@
|
||||
# XCEngine Agent Guide
|
||||
|
||||
这个文件面向在当前仓库里工作的 coding agent / 开发者。它不负责介绍项目卖点,而是给出当前 checkout 的真实工程状态、优先入口、硬约束和推荐验证方式。
|
||||
|
||||
如果 README、旧文档和当前文件树 / `CMakeLists.txt` / 测试 target 冲突,以当前 checkout 为准,并在本次工作里顺手修正文档。
|
||||
|
||||
## 1. 开工顺序
|
||||
|
||||
进入仓库后,优先按下面顺序建立上下文:
|
||||
|
||||
1. [AGENT.md](AGENT.md)
|
||||
2. [README.md](README.md)
|
||||
3. 相关模块的 `CMakeLists.txt`
|
||||
4. 对应模块最近的测试目录与聚合 target
|
||||
5. 对应设计文档
|
||||
|
||||
当前最常用的设计入口:
|
||||
|
||||
- [docs/plan/end/RHI模块设计与实现/RHI模块总览.md](docs/plan/end/RHI模块设计与实现/RHI模块总览.md)
|
||||
- [docs/plan/Library启动预热与运行时异步加载混合重构计划_2026-04-04.md](docs/plan/Library启动预热与运行时异步加载混合重构计划_2026-04-04.md)
|
||||
- [docs/plan/Library启动预热与运行时异步加载混合重构计划_进度更新_2026-04-04.md](docs/plan/Library启动预热与运行时异步加载混合重构计划_进度更新_2026-04-04.md)
|
||||
- [docs/plan/Editor架构说明.md](docs/plan/Editor架构说明.md)
|
||||
- [docs/plan/Unity风格模型导入与Model资产架构重构计划_2026-04-10.md](docs/plan/Unity风格模型导入与Model资产架构重构计划_2026-04-10.md)
|
||||
- [docs/plan/NanoVDB体积云加载阻塞与Runtime上传修复计划_2026-04-10.md](docs/plan/NanoVDB体积云加载阻塞与Runtime上传修复计划_2026-04-10.md)
|
||||
- [docs/plan/3DGS专用PLY导入器与GaussianSplat资源缓存正式化计划_2026-04-10.md](docs/plan/3DGS专用PLY导入器与GaussianSplat资源缓存正式化计划_2026-04-10.md)
|
||||
- [docs/plan/XCUI_NewEditor主线重建计划_2026-04-07.md](docs/plan/XCUI_NewEditor主线重建计划_2026-04-07.md)
|
||||
- [docs/plan/XCUI完整架构设计与执行计划.md](docs/plan/XCUI完整架构设计与执行计划.md)
|
||||
- [docs/plan/C#脚本模块下一阶段计划.md](docs/plan/C%23脚本模块下一阶段计划.md)
|
||||
- [tests/TEST_SPEC.md](tests/TEST_SPEC.md)
|
||||
- [tests/UI/TEST_SPEC.md](tests/UI/TEST_SPEC.md)
|
||||
|
||||
已归档但当前仍常用的背景文档:
|
||||
|
||||
- [Library资产导入与缓存系统收口计划(归档)](docs/used/Library资产导入与缓存系统收口计划_完成归档_2026-04-03.md)
|
||||
- [API 文档第三轮任务池(归档基线)](docs/used/API文档实时同步任务池_2026-04-03.md)
|
||||
- [XCUI Phase Status 2026-04-05(归档)](docs/used/XCUI_Phase_Status_2026-04-05.md)
|
||||
- [Shader与Material系统下一阶段计划(归档)](docs/used/Shader与Material系统下一阶段计划_完成归档_2026-04-04.md)
|
||||
- [Unity 风格 Shader 体系正式化计划(归档)](docs/used/Renderer下一阶段_Unity风格Shader体系正式化计划_完成归档_2026-04-07.md)
|
||||
- [Renderer 当前阶段正式收口(阶段归档)](docs/used/Renderer当前阶段正式收口计划_阶段归档_2026-04-10.md)
|
||||
- [NanoVDB 后续正式化(阶段归档)](docs/used/NanoVDB稀疏体积渲染后续正式化计划_阶段归档_2026-04-10.md)
|
||||
- [SceneViewport Overlay / Gizmo 重构计划(归档)](docs/used/SceneViewport_Overlay_Gizmo_Rework_Plan_完成归档_2026-04-04.md)
|
||||
- [Unity式 SceneView Gizmo 正式化方案(归档)](docs/used/Unity式SceneView_Gizmo系统完整审查与正式化重构方案_完成归档_2026-04-06.md)
|
||||
- [NanoVDB 第一阶段完成归档](docs/used/NanoVDB稀疏体积渲染正式集成计划_第一阶段完成归档_2026-04-09.md)
|
||||
|
||||
如果任务落在 API 文档:
|
||||
|
||||
1. 先检查 `docs/plan/` 下最新的 API 相关计划或并行任务板。当前工作树里已经存在多份 2026-04-09 的活跃文件,例如:
|
||||
- [docs/plan/API文档目录重构计划_2026-04-09.md](docs/plan/API文档目录重构计划_2026-04-09.md)
|
||||
- [docs/plan/API文档目录结构重大重构并行任务板_2026-04-09.md](docs/plan/API文档目录结构重大重构并行任务板_2026-04-09.md)
|
||||
- [docs/plan/API文档目录结构第二轮重构计划_2026-04-09.md](docs/plan/API文档目录结构第二轮重构计划_2026-04-09.md)
|
||||
- [docs/plan/API文档目录结构第二轮并行任务板_2026-04-09.md](docs/plan/API文档目录结构第二轮并行任务板_2026-04-09.md)
|
||||
- [docs/plan/API文档目录结构重构并行任务板_2026-04-09_第二轮.md](docs/plan/API文档目录结构重构并行任务板_2026-04-09_第二轮.md)
|
||||
2. 如果这些活跃文件都不覆盖当前问题,再回看 [docs/used/API文档实时同步任务池_2026-04-03.md](docs/used/API文档实时同步任务池_2026-04-03.md) 作为最近一轮完成基线。
|
||||
3. 再看 [docs/api-skill.md](docs/api-skill.md)。
|
||||
4. 再看 `docs/api/main.md` 和 `docs/api/_meta/rebuild-status.md`,确认当前问题落在 `XCEngine` 还是 `XCEditor` 根树。
|
||||
5. 一次只认领一个任务块,先改状态为 `DOING`,只写自己任务块允许的范围。
|
||||
|
||||
## 2. 当前工程事实
|
||||
|
||||
- 顶层 `CMakeLists.txt` 当前纳入 `engine/`、`editor/`、`new_editor/`、`managed/`、`mvs/RenderDoc/` 和 `tests/`。
|
||||
- `engine/` 构建静态库 `XCEngine`;`editor/` 构建 `XCEditor`,但输出文件名仍是 `editor/bin/<Config>/XCEngine.exe`。
|
||||
- `editor/` 目前继续保留为当前正式编辑器、行为对照和视觉基线来源。
|
||||
- `new_editor/` 当前构建 `XCUIEditorLib`、`XCUIEditorHost`;启用 `XCENGINE_BUILD_XCUI_EDITOR_APP` 时会输出 `new_editor/bin/<Config>/XCUIEditor.exe`,并被视为未来正式编辑器主线,而不再只是临时 sandbox。
|
||||
- editor 默认把仓库内的 `project/` 识别为工程根目录,也支持 `--project <path>` 覆盖。
|
||||
- 当前工程真实使用 `Assets/ + .meta + Library/` 的项目布局;`project/Library/` 是当前 workflow 的一部分,不是可随手忽略的垃圾目录。
|
||||
- Mono 运行时与 editor 的脚本类发现都从 `<project>/Library/ScriptAssemblies/` 加载程序集。
|
||||
- 当前根目录里没有 `native` 占位项;更新目录树或迁移旧文档时,不要继续传播这条过期事实。
|
||||
- 当前工作机是 Windows 文件系统;文档里统一写 `tests/Editor/`、`tests/Core/`、`tests/Scripting/` 等真实目录名,不要制造大小写噪音。
|
||||
- `engine/CMakeLists.txt` 当前对 Vulkan 是硬依赖;`editor/` 和 `tests/` 首次配置会拉取 `ImGui` 与 `googletest`。
|
||||
|
||||
## 3. 子系统现状
|
||||
|
||||
### 3.1 Core / Asset / Resources
|
||||
|
||||
- `Core/Asset/AssetDatabase`、`AssetImportService`、`ProjectAssetIndex`、`ResourceManager` 已形成正式导入与运行时加载链路。
|
||||
- 当前真实职责包括:
|
||||
- 扫描 `Assets/`
|
||||
- 为资源生成 `.meta`
|
||||
- 维护 `Library/SourceAssetDB/assets.db`
|
||||
- 维护 `Library/ArtifactDB/artifacts.db`
|
||||
- 写入 `Library/Artifacts/`
|
||||
- 最新活跃设计文档已经把下一阶段目标收口到“启动阶段前置恢复索引 / 缓存状态,运行时按需异步加载 payload”。
|
||||
- `BootstrapProject()` / `BootstrapProjectAssets()` 已正式接入启动链路;接下来重点是指标固化、UI 状态拆分和剩余同步兜底点审计。
|
||||
- 当前还不要把 `AssetImportService`、`ProjectAssetIndex`、`ResourceManager` 的职责重新揉回一团;先看 [docs/plan/Library启动预热与运行时异步加载混合重构计划_2026-04-04.md](docs/plan/Library启动预热与运行时异步加载混合重构计划_2026-04-04.md)。
|
||||
- 材质 artifact 当前是 schema v2:
|
||||
- `kMaterialArtifactSchemaVersion = 2`
|
||||
- magic `XCMAT02`
|
||||
- texture binding 序列化三元组是 `binding name + encoded AssetRef + optional path`
|
||||
- `Material` 当前同时维护 loaded handle、稳定 `AssetRef` 与 path 元数据;`GetTexture()` 首次访问时可触发懒加载,`GetTextureBindingLoadedTexture()` 不触发加载。
|
||||
|
||||
### 3.2 Rendering
|
||||
|
||||
- 当前正式主链是 `SceneRenderer -> CameraRenderer -> RenderPipeline`。
|
||||
- 现有正式能力包括:
|
||||
- forward 主几何渲染
|
||||
- `ObjectId` 渲染与 editor picking
|
||||
- `BuiltinInfiniteGridPass`
|
||||
- `BuiltinObjectIdOutlinePass`
|
||||
- `directional shadow`
|
||||
- `skybox`
|
||||
- `CameraRenderRequest::postScenePasses`
|
||||
- `CameraRenderRequest::overlayPasses`
|
||||
- `post-process / final color` 当前处于正式化收口阶段,不再是纯预留接口。
|
||||
- `NanoVDB` 体积渲染已进入当前正式运行链路,但 Vulkan / OpenGL 的 rollout 和多后端能力边界仍在继续收口。
|
||||
- 当前资源与导入主线正在继续向 `Model` 资产架构与 `3DGS GaussianSplat` 资源链扩展,不要再把 `.obj/.fbx/.ply` 简化理解为“文件直读后立刻渲染”的旧 sample 流程。
|
||||
- 当前主线不是 render graph,而是 shader / material contract、builtin pass contract 和 renderer-owned feature contract。
|
||||
|
||||
### 3.3 Editor
|
||||
|
||||
- editor 仍然是 `D3D12` 宿主应用。
|
||||
- Scene/Game viewport 已通过引擎 `Rendering + RHI` 输出离屏纹理,再由 editor 宿主接入 ImGui。
|
||||
- 当前 Scene View 主链已经显式拆成:
|
||||
- `SceneViewportChrome`
|
||||
- `SceneViewportInteractionFrame`
|
||||
- `SceneViewportNavigation`
|
||||
- `SceneViewportTransformGizmoCoordinator`
|
||||
- `ViewportHostService`
|
||||
- `editor/src/Viewport/` 当前稳定存在的关键入口包括:
|
||||
- `SceneViewportCameraController`
|
||||
- `SceneViewportChrome`
|
||||
- `SceneViewportEditorModes`
|
||||
- `SceneViewportHudOverlay`
|
||||
- `SceneViewportInteractionActions`
|
||||
- `SceneViewportInteractionFrame`
|
||||
- `SceneViewportInteractionResolver`
|
||||
- `SceneViewportMoveGizmo`
|
||||
- `SceneViewportNavigation`
|
||||
- `SceneViewportOverlayBuilder`
|
||||
- `SceneViewportOverlayFrameCache`
|
||||
- `SceneViewportOverlayProviders`
|
||||
- `SceneViewportOverlaySpriteResources`
|
||||
- `SceneViewportPassSpecs`
|
||||
- `SceneViewportPicker`
|
||||
- `SceneViewportResourcePaths`
|
||||
- `SceneViewportRotateGizmo`
|
||||
- `SceneViewportScaleGizmo`
|
||||
- `SceneViewportShaderPaths`
|
||||
- `SceneViewportTransformGizmoCoordinator`
|
||||
- `SceneViewportTransformGizmoFrameBuilder`
|
||||
- `ViewportHostRenderFlowUtils`
|
||||
- `ViewportHostRenderTargets`
|
||||
- `ViewportHostSurfaceUtils`
|
||||
- `ViewportObjectIdPicker`
|
||||
- `SceneViewportShaderPaths.h` 当前主要是兼容 include;路径真实 owner 已转到 `SceneViewportResourcePaths.h`。
|
||||
- `tests/Editor/` 已有 `test_scene_viewport_chrome.cpp`、`test_scene_viewport_interaction_frame.cpp`、`test_scene_viewport_navigation.cpp` 与 `test_scene_viewport_transform_gizmo_coordinator.cpp`,不要再按“这些 helper 还没落地”理解当前 editor。
|
||||
|
||||
### 3.4 XCUI / New Editor
|
||||
|
||||
- `new_editor/` 是未来正式编辑器主线,不再只是 sandbox;旧 `editor/` 当前继续保留为正式编辑器、行为对照和视觉基线。
|
||||
- 当前宿主分层是:
|
||||
- `XCUIEditorLib`
|
||||
- `XCUIEditorHost`
|
||||
- `XCUIEditorApp`(可选应用壳)
|
||||
- 共享 UI core、runtime screen host 与 widget 基础能力主要沉淀在 `engine/include/XCEngine/UI/` 与 `engine/src/UI/`;`new_editor/` 负责 XCUI editor 壳、宿主与产品装配。
|
||||
- `tests/UI/` 是当前 XCUI `Core / Editor / Runtime` 三层的唯一正式基础层验证入口;`new_editor/` 不承担测试堆场职责。
|
||||
|
||||
### 3.5 Scripting
|
||||
|
||||
- 当前脚本链路由三部分组成:
|
||||
- `managed/XCEngine.ScriptCore/`
|
||||
- `managed/GameScripts/`
|
||||
- `project/Assets/**/*.cs`
|
||||
- 构建结果分两类:
|
||||
- `xcengine_managed_assemblies` 生成引擎示例程序集
|
||||
- `xcengine_project_managed_assemblies` 生成项目脚本程序集,并复制到 `project/Library/ScriptAssemblies/`
|
||||
- `project/Library/ScriptAssemblies/` 里的当前快照可能落后于目标态;判断“成功构建后应有哪些程序集”时,以 `managed/CMakeLists.txt`、`editor/src/Scripting/EditorScriptAssemblyBuilder.cpp` 与对应测试为准,而不是只看工作树里此刻存着什么。
|
||||
- `ScriptEngine` 当前已具备脚本类发现、字段元数据读取、默认值读取、stored override 管理和运行时 managed field 同步。
|
||||
- Inspector 侧已经存在 `ScriptComponentEditor`;脚本相关改动通常同时影响 `engine/src/Scripting/`、`managed/`、`project/Assets/Scripts/`、`editor/src/ComponentEditors/` 与 `tests/Scripting/` / `tests/Editor/`。
|
||||
|
||||
### 3.6 Tests
|
||||
|
||||
当前测试主目录包括:
|
||||
|
||||
- `tests/Components/`
|
||||
- `tests/Core/`
|
||||
- `tests/Debug/`
|
||||
- `tests/Editor/`
|
||||
- `tests/Fixtures/`
|
||||
- `tests/Input/`
|
||||
- `tests/Memory/`
|
||||
- `tests/NewEditor/`
|
||||
- `tests/Rendering/`
|
||||
- `tests/Resources/`
|
||||
- `tests/RHI/`
|
||||
- `tests/Scene/`
|
||||
- `tests/Scripting/`
|
||||
- `tests/Threading/`
|
||||
- `tests/UI/`
|
||||
|
||||
需要特别记住的聚合 target:
|
||||
|
||||
- `rhi_all_tests`
|
||||
- `rendering_all_tests`
|
||||
- `rendering_phase_regression`
|
||||
- `editor_tests`
|
||||
- `core_ui_tests`
|
||||
- `editor_ui_tests`
|
||||
- `runtime_ui_tests`
|
||||
- `scripting_tests`
|
||||
|
||||
## 4. 不可忽视的硬约束
|
||||
|
||||
### 4.1 文档服从真实 checkout
|
||||
|
||||
如果文档与当前目录结构、target 名称、代码事实冲突:
|
||||
|
||||
- 先信当前 checkout
|
||||
- 再更新文档
|
||||
|
||||
不要沿用“计划中但未落地”的旧说法。
|
||||
|
||||
### 4.2 不要随手清空 `project/Library/` 或删 `.meta`
|
||||
|
||||
`project/Library/` 虽然可重建,但它在当前 workflow 里承载:
|
||||
|
||||
- `SourceAssetDB`
|
||||
- `ArtifactDB`
|
||||
- `Artifacts`
|
||||
- `ScriptAssemblies`
|
||||
|
||||
涉及资源导入、artifact、脚本程序集发现时,不要把删库重建当作默认修复手段。
|
||||
|
||||
### 4.3 RHI 抽象层与后端层必须分层
|
||||
|
||||
`tests/RHI/unit/` 与 `tests/RHI/integration/` 只能依赖公共 RHI 抽象。
|
||||
|
||||
不要为了让抽象层测试通过而:
|
||||
|
||||
- include 后端私有头
|
||||
- 直接使用原生句柄
|
||||
- 给单一后端写抽象层特判
|
||||
|
||||
如果必须这么做,优先修 RHI,而不是污染测试边界。
|
||||
|
||||
### 4.4 Editor 是宿主,不是第二套渲染器
|
||||
|
||||
如果 viewport、outline、picking 或 gizmo 有问题,优先判断:
|
||||
|
||||
- 是 `Rendering` 模块问题
|
||||
- 是 `RenderSurface` / RHI 输出问题
|
||||
- 还是 editor 宿主接线问题
|
||||
|
||||
不要因为 editor 当前是 D3D12 host,就把问题草率塞回 editor 私有渲染逻辑。
|
||||
|
||||
### 4.5 不要再把新逻辑堆回旧的 ImGui world overlay
|
||||
|
||||
新的世界空间 overlay / gizmo 逻辑如果仍然直接堆在:
|
||||
|
||||
- `SceneViewPanel.cpp`
|
||||
- panel 层临时拼的 immediate draw / ad-hoc overlay 路径
|
||||
|
||||
通常就是逆着当前架构方向在走。优先入口是:
|
||||
|
||||
- `CameraRenderRequest::overlayPasses`
|
||||
- `SceneViewportOverlayBuilder`
|
||||
- `SceneViewportEditorOverlayPass`
|
||||
- `SceneViewportTransformGizmoCoordinator`
|
||||
|
||||
### 4.6 `backpack` 导入行为必须统一
|
||||
|
||||
这是仓库里已经踩过的真实坑。`backpack` 相关资源在以下路径中的导入行为必须保持一致:
|
||||
|
||||
- editor
|
||||
- runtime
|
||||
- rendering tests
|
||||
- rhi abstraction tests
|
||||
|
||||
不要只在局部路径里额外加 `MeshImportFlags::FlipUVs` 之类的补丁。
|
||||
|
||||
### 4.7 `mvs/` 不是长期主线模块
|
||||
|
||||
`mvs/` 里有样例、研究和工具,但当前正式引擎逻辑的长期落点应优先是:
|
||||
|
||||
- `engine/`
|
||||
- `editor/`
|
||||
- `managed/`
|
||||
- `tests/`
|
||||
|
||||
不要把正式渲染逻辑重新堆回 sample 子树长期存活。
|
||||
|
||||
### 4.8 API 文档任务必须看最新计划并复跑审计
|
||||
|
||||
只要任务涉及 `docs/api/`:
|
||||
|
||||
1. 先读 `docs/plan/API文档目录*.md` 里日期最新的 API 计划 / 并行任务板。
|
||||
2. 再看 [docs/api-skill.md](docs/api-skill.md) 和 `docs/api/_meta/rebuild-status.md`。
|
||||
3. 如果活跃计划没有覆盖当前问题,再回看 [docs/used/API文档实时同步任务池_2026-04-03.md](docs/used/API文档实时同步任务池_2026-04-03.md) 作为最近一轮归档基线。
|
||||
4. 改完必须重新执行:
|
||||
|
||||
```powershell
|
||||
python -B docs/api/_tools/audit_api_docs.py
|
||||
```
|
||||
|
||||
如果审计没回绿,不算完成。
|
||||
当前审计口径已经同时覆盖:
|
||||
|
||||
- `engine/include/XCEngine/**`
|
||||
- `new_editor/include/XCEditor/**`
|
||||
- `editor/src/**`
|
||||
|
||||
## 5. 推荐构建与验证入口
|
||||
|
||||
### 5.1 配置
|
||||
|
||||
```bash
|
||||
cmake -S . -B build -A x64
|
||||
```
|
||||
|
||||
如果当前任务不需要 Mono:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build -A x64 -DXCENGINE_ENABLE_MONO_SCRIPTING=OFF
|
||||
```
|
||||
|
||||
### 5.2 常用构建 target
|
||||
|
||||
```bash
|
||||
cmake --build build --config Debug --target XCEngine
|
||||
cmake --build build --config Debug --target XCEditor
|
||||
cmake --build build --config Debug --target xcengine_managed_assemblies
|
||||
cmake --build build --config Debug --target xcengine_project_managed_assemblies
|
||||
cmake --build build --config Debug --target rhi_all_tests
|
||||
cmake --build build --config Debug --target rendering_all_tests
|
||||
cmake --build build --config Debug --target rendering_phase_regression
|
||||
cmake --build build --config Debug --target editor_tests
|
||||
cmake --build build --config Debug --target scripting_tests
|
||||
```
|
||||
|
||||
### 5.3 按改动类型选择验证
|
||||
|
||||
- 改 `engine/RHI`:先跑 `rhi_abstraction_tests` 或 `rhi_backend_tests`,再决定是否扩展到 `rhi_all_tests`
|
||||
- 改 `engine/Rendering`:先跑 `rendering_unit_tests` 和最相关的 `rendering_integration_*`,必要时再跑 `rendering_phase_regression`
|
||||
- 改 `editor/Viewport`、`editor/UI` 或 Inspector:先跑 `editor_tests`
|
||||
- 改 `engine/Scripting`、`managed/`、`project/Assets/Scripts/`:先构建 `xcengine_project_managed_assemblies`,再跑 `scripting_tests`
|
||||
- 改资源导入、`.meta`、artifact 相关逻辑:优先跑 `tests/Resources/` 里的对应 target
|
||||
|
||||
### 5.4 全量测试入口
|
||||
|
||||
```bash
|
||||
ctest --test-dir build -C Debug --output-on-failure
|
||||
```
|
||||
|
||||
## 6. 按任务找入口
|
||||
|
||||
- RHI 抽象与后端:`engine/include/XCEngine/RHI/`、`engine/src/RHI/`、`tests/RHI/`
|
||||
- Rendering 主链与 pass:`engine/include/XCEngine/Rendering/`、`engine/src/Rendering/`、`tests/Rendering/`
|
||||
- 资源导入与工程布局:`engine/include/XCEngine/Core/Asset/`、`engine/src/Core/Asset/`、`editor/src/Managers/ProjectManager.cpp`、`project/Assets/`、`project/Library/`
|
||||
- Material / shader / artifact:`engine/include/XCEngine/Resources/Material/`、`engine/src/Resources/Material/`、`engine/include/XCEngine/Core/Asset/ArtifactFormats.h`、`tests/Resources/Material/`
|
||||
- Editor viewport / gizmo / picking:`editor/src/Viewport/`、`editor/src/panels/SceneViewPanel.cpp`、`tests/Editor/`
|
||||
- XCUI / new_editor:`engine/include/XCEngine/UI/`、`engine/src/UI/`、`new_editor/include/XCEditor/`、`new_editor/src/`、`tests/UI/`
|
||||
- Editor actions / project routing:`editor/src/Actions/`、`editor/src/Commands/`、`editor/src/Core/`、`editor/src/Managers/`、`tests/Editor/test_action_routing.cpp`
|
||||
- 脚本运行时与程序集:`engine/include/XCEngine/Scripting/`、`engine/src/Scripting/`、`managed/`、`project/Assets/Scripts/`、`tests/Scripting/`
|
||||
- API 文档:`docs/api/main.md`、`docs/api/XCEngine/`、`docs/api/XCEditor/`、`docs/api/_guides/`、`docs/api/_tools/audit_api_docs.py`、`docs/plan/API文档目录*.md`、`docs/used/API文档实时同步任务池_2026-04-03.md`
|
||||
|
||||
## 7. 适合当前仓库的工作方式
|
||||
|
||||
1. 先读当前模块的 `CMakeLists.txt`、最近测试和设计文档,再动代码。
|
||||
2. 优先在既有模块边界里解决问题,不要绕开系统回到 sample 式实现。
|
||||
3. 先跑与改动最相关的最小验证,再决定是否扩大全量验证。
|
||||
4. 目录、target、入口、文档名改了,就同步更新 README / AGENT / 相关说明。
|
||||
5. 如果任务会有意重建 `project/Library/`、脚本程序集或 `.meta`,在结果里明确说明哪些文件是有意生成的。
|
||||
|
||||
这份文档的作用,是给 agent 一条“当前真实工程长什么样”的基线。它本身也必须随着工程演进一起维护,不能再落回旧状态说明。
|
||||
107
CMakeLists.txt
107
CMakeLists.txt
@@ -1,56 +1,65 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(XCVolumeRenderer VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
BattleFireDirect.cpp
|
||||
StaticMeshComponent.cpp
|
||||
Utils.cpp
|
||||
stbi/stb_image.cpp
|
||||
NanoVDBLoader.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
BattleFireDirect.h
|
||||
StaticMeshComponent.h
|
||||
Utils.h
|
||||
stbi/stb_image.h
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} WIN32 ${SOURCES} ${HEADERS})
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE UNICODE _UNICODE)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /utf-8 /MT)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
d3d12.lib
|
||||
dxgi.lib
|
||||
d3dcompiler.lib
|
||||
winmm.lib
|
||||
kernel32.lib
|
||||
user32.lib
|
||||
)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE _DEBUG)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE NDEBUG)
|
||||
if(POLICY CMP0141)
|
||||
cmake_policy(SET CMP0141 NEW)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
project(XCEngine)
|
||||
|
||||
if(MSVC)
|
||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
|
||||
"$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>")
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:C,CXX>:/MP>")
|
||||
if(CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
set(CMAKE_VS_GLOBALS "UseMultiToolTask=true")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(XCENGINE_NANOVDB_INCLUDE_HINTS
|
||||
"${CMAKE_SOURCE_DIR}/engine/third_party/nanovdb/include"
|
||||
"$ENV{VCPKG_ROOT}/installed/x64-windows/include"
|
||||
"D:/vcpkg/installed/x64-windows/include"
|
||||
)
|
||||
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/Res
|
||||
$<TARGET_FILE_DIR:${PROJECT_NAME}>/Res
|
||||
find_path(
|
||||
XCENGINE_NANOVDB_INCLUDE_DIR
|
||||
NAMES nanovdb/io/IO.h
|
||||
HINTS ${XCENGINE_NANOVDB_INCLUDE_HINTS}
|
||||
)
|
||||
|
||||
if(XCENGINE_NANOVDB_INCLUDE_DIR)
|
||||
set(XCENGINE_HAS_NANOVDB ON)
|
||||
message(STATUS "NanoVDB headers found: ${XCENGINE_NANOVDB_INCLUDE_DIR}")
|
||||
else()
|
||||
set(XCENGINE_HAS_NANOVDB OFF)
|
||||
message(STATUS "NanoVDB headers not found; .nvdb source-file support will be disabled")
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
|
||||
option(XCENGINE_ENABLE_MONO_SCRIPTING "Build the Mono-based C# scripting runtime" ON)
|
||||
option(XCENGINE_BUILD_XCUI_EDITOR_APP "Build the XCUI editor shell app" ON)
|
||||
set(
|
||||
XCENGINE_MONO_ROOT_DIR
|
||||
"${CMAKE_SOURCE_DIR}/参考/Fermion/Fermion/external/mono"
|
||||
CACHE PATH
|
||||
"Path to the bundled Mono distribution used by the scripting runtime")
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/engine/third_party/mono/binary/mscorlib.dll")
|
||||
set(
|
||||
XCENGINE_MONO_ROOT_DIR
|
||||
"${CMAKE_SOURCE_DIR}/engine/third_party/mono"
|
||||
CACHE PATH
|
||||
"Path to the bundled Mono distribution used by the scripting runtime"
|
||||
FORCE)
|
||||
endif()
|
||||
|
||||
add_subdirectory(engine)
|
||||
add_subdirectory(managed)
|
||||
add_subdirectory(editor)
|
||||
add_subdirectory(new_editor)
|
||||
add_subdirectory(mvs/RenderDoc)
|
||||
add_subdirectory(tests)
|
||||
|
||||
158
MVS/3DGS-D3D12/CMakeLists.txt
Normal file
158
MVS/3DGS-D3D12/CMakeLists.txt
Normal file
@@ -0,0 +1,158 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(XC3DGSD3D12MVS LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_program(XC_DXC_EXECUTABLE NAMES dxc)
|
||||
if(NOT XC_DXC_EXECUTABLE)
|
||||
message(FATAL_ERROR "dxc is required to build the 3DGS D3D12 MVS sort shaders.")
|
||||
endif()
|
||||
|
||||
get_filename_component(XCENGINE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
|
||||
set(XCENGINE_BUILD_DIR "${XCENGINE_ROOT}/build")
|
||||
set(XCENGINE_INCLUDE_DIR "${XCENGINE_ROOT}/engine/include")
|
||||
set(XCENGINE_LIBRARY_DEBUG "${XCENGINE_BUILD_DIR}/engine/Debug/XCEngine.lib")
|
||||
|
||||
if(NOT EXISTS "${XCENGINE_LIBRARY_DEBUG}")
|
||||
message(FATAL_ERROR "Prebuilt XCEngine library was not found: ${XCENGINE_LIBRARY_DEBUG}")
|
||||
endif()
|
||||
|
||||
add_library(XCEngine STATIC IMPORTED GLOBAL)
|
||||
set_target_properties(XCEngine PROPERTIES
|
||||
IMPORTED_CONFIGURATIONS "Debug;Release;RelWithDebInfo;MinSizeRel"
|
||||
IMPORTED_LOCATION_DEBUG "${XCENGINE_LIBRARY_DEBUG}"
|
||||
IMPORTED_LOCATION_RELEASE "${XCENGINE_LIBRARY_DEBUG}"
|
||||
IMPORTED_LOCATION_RELWITHDEBINFO "${XCENGINE_LIBRARY_DEBUG}"
|
||||
IMPORTED_LOCATION_MINSIZEREL "${XCENGINE_LIBRARY_DEBUG}"
|
||||
)
|
||||
|
||||
add_executable(xc_3dgs_d3d12_mvs
|
||||
WIN32
|
||||
src/main.cpp
|
||||
src/App.cpp
|
||||
src/GaussianPlyLoader.cpp
|
||||
include/XC3DGSD3D12/App.h
|
||||
include/XC3DGSD3D12/GaussianPlyLoader.h
|
||||
shaders/PreparedSplatView.hlsli
|
||||
shaders/PrepareGaussiansCS.hlsl
|
||||
shaders/BuildSortKeysCS.hlsl
|
||||
shaders/SortCommon.hlsl
|
||||
shaders/DeviceRadixSort.hlsl
|
||||
shaders/DebugPointsVS.hlsl
|
||||
shaders/DebugPointsPS.hlsl
|
||||
shaders/CompositeVS.hlsl
|
||||
shaders/CompositePS.hlsl
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
shaders/PreparedSplatView.hlsli
|
||||
shaders/PrepareGaussiansCS.hlsl
|
||||
shaders/BuildSortKeysCS.hlsl
|
||||
shaders/SortCommon.hlsl
|
||||
shaders/DeviceRadixSort.hlsl
|
||||
shaders/DebugPointsVS.hlsl
|
||||
shaders/DebugPointsPS.hlsl
|
||||
shaders/CompositeVS.hlsl
|
||||
shaders/CompositePS.hlsl
|
||||
PROPERTIES
|
||||
HEADER_FILE_ONLY TRUE
|
||||
)
|
||||
|
||||
target_include_directories(xc_3dgs_d3d12_mvs PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${XCENGINE_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_compile_definitions(xc_3dgs_d3d12_mvs PRIVATE
|
||||
UNICODE
|
||||
_UNICODE
|
||||
NOMINMAX
|
||||
WIN32_LEAN_AND_MEAN
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(xc_3dgs_d3d12_mvs PRIVATE /utf-8)
|
||||
endif()
|
||||
|
||||
target_link_libraries(xc_3dgs_d3d12_mvs PRIVATE
|
||||
XCEngine
|
||||
d3d12
|
||||
dxgi
|
||||
dxguid
|
||||
d3dcompiler
|
||||
winmm
|
||||
delayimp
|
||||
bcrypt
|
||||
opengl32
|
||||
)
|
||||
|
||||
set_target_properties(xc_3dgs_d3d12_mvs PROPERTIES
|
||||
VS_DEBUGGER_WORKING_DIRECTORY "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>"
|
||||
)
|
||||
|
||||
add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/room.ply"
|
||||
"$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/room.ply"
|
||||
)
|
||||
|
||||
add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders"
|
||||
"$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders"
|
||||
)
|
||||
|
||||
add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
|
||||
COMMAND "${XC_DXC_EXECUTABLE}"
|
||||
-T cs_6_6
|
||||
-E MainCS
|
||||
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/BuildSortKeysCS.dxil"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/BuildSortKeysCS.hlsl"
|
||||
COMMAND "${XC_DXC_EXECUTABLE}"
|
||||
-T cs_6_6
|
||||
-E InitDeviceRadixSort
|
||||
-D KEY_UINT=1
|
||||
-D PAYLOAD_UINT=1
|
||||
-D SORT_PAIRS=1
|
||||
-D SHOULD_ASCEND=1
|
||||
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixInit.dxil"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
|
||||
COMMAND "${XC_DXC_EXECUTABLE}"
|
||||
-T cs_6_6
|
||||
-E Upsweep
|
||||
-D KEY_UINT=1
|
||||
-D PAYLOAD_UINT=1
|
||||
-D SORT_PAIRS=1
|
||||
-D SHOULD_ASCEND=1
|
||||
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixUpsweep.dxil"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
|
||||
COMMAND "${XC_DXC_EXECUTABLE}"
|
||||
-T cs_6_6
|
||||
-E BuildGlobalHistogram
|
||||
-D KEY_UINT=1
|
||||
-D PAYLOAD_UINT=1
|
||||
-D SORT_PAIRS=1
|
||||
-D SHOULD_ASCEND=1
|
||||
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixGlobalHistogram.dxil"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
|
||||
COMMAND "${XC_DXC_EXECUTABLE}"
|
||||
-T cs_6_6
|
||||
-E Scan
|
||||
-D KEY_UINT=1
|
||||
-D PAYLOAD_UINT=1
|
||||
-D SORT_PAIRS=1
|
||||
-D SHOULD_ASCEND=1
|
||||
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixScan.dxil"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
|
||||
COMMAND "${XC_DXC_EXECUTABLE}"
|
||||
-T cs_6_6
|
||||
-E Downsweep
|
||||
-D KEY_UINT=1
|
||||
-D PAYLOAD_UINT=1
|
||||
-D SORT_PAIRS=1
|
||||
-D SHOULD_ASCEND=1
|
||||
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixDownsweep.dxil"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
|
||||
)
|
||||
162
MVS/3DGS-D3D12/include/XC3DGSD3D12/App.h
Normal file
162
MVS/3DGS-D3D12/include/XC3DGSD3D12/App.h
Normal file
@@ -0,0 +1,162 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include "XC3DGSD3D12/GaussianPlyLoader.h"
|
||||
#include "XCEngine/RHI/RHIEnums.h"
|
||||
#include "XCEngine/RHI/RHITypes.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12CommandAllocator.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12Buffer.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12CommandList.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12CommandQueue.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12DescriptorHeap.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12Device.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12ResourceView.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12SwapChain.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12Texture.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
class RHIDescriptorPool;
|
||||
class RHIDescriptorSet;
|
||||
class RHIPipelineLayout;
|
||||
class RHIPipelineState;
|
||||
} // namespace RHI
|
||||
} // namespace XCEngine
|
||||
|
||||
namespace XC3DGSD3D12 {
|
||||
|
||||
struct PreparedSplatView {
|
||||
float clipPosition[4] = {};
|
||||
float axis1[2] = {};
|
||||
float axis2[2] = {};
|
||||
uint32_t packedColor[2] = {};
|
||||
};
|
||||
|
||||
class App {
|
||||
public:
|
||||
App();
|
||||
~App();
|
||||
|
||||
bool Initialize(HINSTANCE instance, int showCommand);
|
||||
int Run();
|
||||
void SetFrameLimit(unsigned int frameLimit);
|
||||
void SetGaussianScenePath(std::wstring scenePath);
|
||||
void SetSummaryPath(std::wstring summaryPath);
|
||||
void SetScreenshotPath(std::wstring screenshotPath);
|
||||
const std::wstring& GetLastErrorMessage() const;
|
||||
|
||||
private:
|
||||
static constexpr int kBackBufferCount = 2;
|
||||
static constexpr int kDefaultWidth = 1280;
|
||||
static constexpr int kDefaultHeight = 720;
|
||||
|
||||
static LRESULT CALLBACK StaticWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
LRESULT WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
bool RegisterWindowClass(HINSTANCE instance);
|
||||
bool CreateMainWindow(HINSTANCE instance, int showCommand);
|
||||
bool LoadGaussianScene();
|
||||
bool InitializeRhi();
|
||||
bool InitializeGaussianGpuResources();
|
||||
bool InitializePreparePassResources();
|
||||
bool InitializeSortResources();
|
||||
bool InitializeDebugDrawResources();
|
||||
bool InitializeCompositeResources();
|
||||
void ShutdownGaussianGpuResources();
|
||||
void ShutdownPreparePassResources();
|
||||
void ShutdownSortResources();
|
||||
void ShutdownDebugDrawResources();
|
||||
void ShutdownCompositeResources();
|
||||
void Shutdown();
|
||||
bool CaptureSortSnapshot();
|
||||
bool CapturePass3HistogramDebug();
|
||||
void RenderFrame(bool captureScreenshot);
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
HINSTANCE m_instance = nullptr;
|
||||
int m_width = kDefaultWidth;
|
||||
int m_height = kDefaultHeight;
|
||||
bool m_running = false;
|
||||
bool m_isInitialized = false;
|
||||
bool m_hasRenderedAtLeastOneFrame = false;
|
||||
unsigned int m_frameLimit = 0;
|
||||
unsigned int m_renderedFrameCount = 0;
|
||||
std::wstring m_gaussianScenePath = L"room.ply";
|
||||
std::wstring m_summaryPath;
|
||||
std::wstring m_screenshotPath = L"phase3_debug_points.ppm";
|
||||
std::wstring m_sortKeySnapshotPath = L"phase3_sortkeys.txt";
|
||||
std::wstring m_lastErrorMessage;
|
||||
GaussianSplatRuntimeData m_gaussianSceneData;
|
||||
XCEngine::RHI::D3D12Buffer m_gaussianPositionBuffer;
|
||||
XCEngine::RHI::D3D12Buffer m_gaussianOtherBuffer;
|
||||
XCEngine::RHI::D3D12Buffer m_gaussianShBuffer;
|
||||
XCEngine::RHI::D3D12Texture m_gaussianColorTexture;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianPositionView;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianOtherView;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianShView;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianColorView;
|
||||
std::vector<Microsoft::WRL::ComPtr<ID3D12Resource>> m_gaussianUploadBuffers;
|
||||
XCEngine::RHI::D3D12Buffer* m_preparedViewBuffer = nullptr;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_preparedViewSrv;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_preparedViewUav;
|
||||
XCEngine::RHI::RHIPipelineLayout* m_preparePipelineLayout = nullptr;
|
||||
XCEngine::RHI::RHIPipelineState* m_preparePipelineState = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorPool* m_prepareDescriptorPool = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorSet* m_prepareDescriptorSet = nullptr;
|
||||
XCEngine::RHI::D3D12Buffer* m_sortKeyBuffer = nullptr;
|
||||
XCEngine::RHI::D3D12Buffer* m_sortKeyScratchBuffer = nullptr;
|
||||
XCEngine::RHI::D3D12Buffer* m_orderBuffer = nullptr;
|
||||
XCEngine::RHI::D3D12Buffer* m_orderScratchBuffer = nullptr;
|
||||
XCEngine::RHI::D3D12Buffer* m_passHistogramBuffer = nullptr;
|
||||
XCEngine::RHI::D3D12Buffer* m_globalHistogramBuffer = nullptr;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_sortKeyUav;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_sortKeyScratchUav;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_orderBufferSrv;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_orderBufferUav;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_orderScratchUav;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_passHistogramUav;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_globalHistogramUav;
|
||||
XCEngine::RHI::RHIPipelineLayout* m_buildSortKeyPipelineLayout = nullptr;
|
||||
XCEngine::RHI::RHIPipelineState* m_buildSortKeyPipelineState = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorPool* m_buildSortKeyDescriptorPool = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorSet* m_buildSortKeyDescriptorSet = nullptr;
|
||||
XCEngine::RHI::RHIPipelineLayout* m_radixSortPipelineLayout = nullptr;
|
||||
XCEngine::RHI::RHIPipelineState* m_radixSortInitPipelineState = nullptr;
|
||||
XCEngine::RHI::RHIPipelineState* m_radixSortUpsweepPipelineState = nullptr;
|
||||
XCEngine::RHI::RHIPipelineState* m_radixSortGlobalHistogramPipelineState = nullptr;
|
||||
XCEngine::RHI::RHIPipelineState* m_radixSortScanPipelineState = nullptr;
|
||||
XCEngine::RHI::RHIPipelineState* m_radixSortDownsweepPipelineState = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorPool* m_radixSortDescriptorPool = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorSet* m_radixSortDescriptorSetPrimaryToScratch = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorSet* m_radixSortDescriptorSetScratchToPrimary = nullptr;
|
||||
XCEngine::RHI::RHIPipelineLayout* m_debugPipelineLayout = nullptr;
|
||||
XCEngine::RHI::RHIPipelineState* m_debugPipelineState = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorPool* m_debugDescriptorPool = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorSet* m_debugDescriptorSet = nullptr;
|
||||
XCEngine::RHI::D3D12Texture m_splatRenderTargetTexture;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_splatRenderTargetRtv;
|
||||
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_splatRenderTargetSrv;
|
||||
XCEngine::RHI::RHIPipelineLayout* m_compositePipelineLayout = nullptr;
|
||||
XCEngine::RHI::RHIPipelineState* m_compositePipelineState = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorPool* m_compositeDescriptorPool = nullptr;
|
||||
XCEngine::RHI::RHIDescriptorSet* m_compositeDescriptorSet = nullptr;
|
||||
|
||||
XCEngine::RHI::D3D12Device m_device;
|
||||
XCEngine::RHI::D3D12CommandQueue m_commandQueue;
|
||||
XCEngine::RHI::D3D12SwapChain m_swapChain;
|
||||
XCEngine::RHI::D3D12CommandAllocator m_commandAllocator;
|
||||
XCEngine::RHI::D3D12CommandList m_commandList;
|
||||
XCEngine::RHI::D3D12Texture m_depthStencil;
|
||||
XCEngine::RHI::D3D12DescriptorHeap m_rtvHeap;
|
||||
XCEngine::RHI::D3D12DescriptorHeap m_dsvHeap;
|
||||
XCEngine::RHI::D3D12ResourceView m_rtvs[kBackBufferCount];
|
||||
XCEngine::RHI::D3D12ResourceView m_dsv;
|
||||
};
|
||||
|
||||
} // namespace XC3DGSD3D12
|
||||
46
MVS/3DGS-D3D12/include/XC3DGSD3D12/GaussianPlyLoader.h
Normal file
46
MVS/3DGS-D3D12/include/XC3DGSD3D12/GaussianPlyLoader.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XC3DGSD3D12 {
|
||||
|
||||
struct Float3 {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
};
|
||||
|
||||
struct GaussianSplatRuntimeData {
|
||||
static constexpr uint32_t kColorTextureWidth = 2048;
|
||||
static constexpr uint32_t kPositionStride = sizeof(float) * 3;
|
||||
static constexpr uint32_t kOtherStride = sizeof(uint32_t) + sizeof(float) * 3;
|
||||
static constexpr uint32_t kColorStride = sizeof(float) * 4;
|
||||
static constexpr uint32_t kShCoefficientCount = 15;
|
||||
static constexpr uint32_t kShStride = sizeof(float) * 3 * 16;
|
||||
|
||||
uint32_t splatCount = 0;
|
||||
uint32_t colorTextureWidth = kColorTextureWidth;
|
||||
uint32_t colorTextureHeight = 0;
|
||||
Float3 boundsMin = {};
|
||||
Float3 boundsMax = {};
|
||||
std::vector<std::byte> positionData;
|
||||
std::vector<std::byte> otherData;
|
||||
std::vector<std::byte> colorData;
|
||||
std::vector<std::byte> shData;
|
||||
};
|
||||
|
||||
bool LoadGaussianSceneFromPly(
|
||||
const std::filesystem::path& filePath,
|
||||
GaussianSplatRuntimeData& outData,
|
||||
std::string& outErrorMessage);
|
||||
|
||||
bool WriteGaussianSceneSummary(
|
||||
const std::filesystem::path& filePath,
|
||||
const GaussianSplatRuntimeData& data,
|
||||
std::string& outErrorMessage);
|
||||
|
||||
} // namespace XC3DGSD3D12
|
||||
43
MVS/3DGS-D3D12/shaders/BuildSortKeysCS.hlsl
Normal file
43
MVS/3DGS-D3D12/shaders/BuildSortKeysCS.hlsl
Normal file
@@ -0,0 +1,43 @@
|
||||
#define GROUP_SIZE 64
|
||||
|
||||
cbuffer FrameConstants : register(b0)
|
||||
{
|
||||
float4x4 gViewProjection;
|
||||
float4x4 gView;
|
||||
float4x4 gProjection;
|
||||
float4 gCameraWorldPos;
|
||||
float4 gScreenParams;
|
||||
float4 gSettings;
|
||||
};
|
||||
|
||||
ByteAddressBuffer gPositions : register(t0);
|
||||
StructuredBuffer<uint> gOrderBuffer : register(t1);
|
||||
RWStructuredBuffer<uint> gSortKeys : register(u0);
|
||||
|
||||
float3 LoadFloat3(ByteAddressBuffer buffer, uint byteOffset)
|
||||
{
|
||||
return asfloat(buffer.Load3(byteOffset));
|
||||
}
|
||||
|
||||
uint FloatToSortableUint(float value)
|
||||
{
|
||||
uint bits = asuint(value);
|
||||
uint mask = (0u - (bits >> 31)) | 0x80000000u;
|
||||
return bits ^ mask;
|
||||
}
|
||||
|
||||
[numthreads(GROUP_SIZE, 1, 1)]
|
||||
void MainCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
||||
{
|
||||
uint index = dispatchThreadId.x;
|
||||
uint splatCount = (uint)gSettings.x;
|
||||
if (index >= splatCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint splatIndex = gOrderBuffer[index];
|
||||
float3 position = LoadFloat3(gPositions, splatIndex * 12);
|
||||
float3 viewPosition = mul(float4(position, 1.0), gView).xyz;
|
||||
gSortKeys[index] = FloatToSortableUint(viewPosition.z);
|
||||
}
|
||||
12
MVS/3DGS-D3D12/shaders/CompositePS.hlsl
Normal file
12
MVS/3DGS-D3D12/shaders/CompositePS.hlsl
Normal file
@@ -0,0 +1,12 @@
|
||||
Texture2D<float4> gSplatTexture : register(t0);
|
||||
|
||||
struct PixelInput
|
||||
{
|
||||
float4 position : SV_Position;
|
||||
};
|
||||
|
||||
float4 MainPS(PixelInput input) : SV_Target0
|
||||
{
|
||||
float4 color = gSplatTexture.Load(int3(int2(input.position.xy), 0));
|
||||
return float4(color.rgb, color.a);
|
||||
}
|
||||
12
MVS/3DGS-D3D12/shaders/CompositeVS.hlsl
Normal file
12
MVS/3DGS-D3D12/shaders/CompositeVS.hlsl
Normal file
@@ -0,0 +1,12 @@
|
||||
struct VertexOutput
|
||||
{
|
||||
float4 position : SV_Position;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(uint vertexId : SV_VertexID)
|
||||
{
|
||||
VertexOutput output = (VertexOutput)0;
|
||||
float2 quadPosition = float2(vertexId & 1, (vertexId >> 1) & 1) * 4.0 - 1.0;
|
||||
output.position = float4(quadPosition, 1.0, 1.0);
|
||||
return output;
|
||||
}
|
||||
19
MVS/3DGS-D3D12/shaders/DebugPointsPS.hlsl
Normal file
19
MVS/3DGS-D3D12/shaders/DebugPointsPS.hlsl
Normal file
@@ -0,0 +1,19 @@
|
||||
struct PixelInput
|
||||
{
|
||||
float4 position : SV_Position;
|
||||
float4 color : COLOR0;
|
||||
float2 localPosition : TEXCOORD0;
|
||||
};
|
||||
|
||||
float4 MainPS(PixelInput input) : SV_Target0
|
||||
{
|
||||
float alpha = exp(-dot(input.localPosition, input.localPosition));
|
||||
alpha = saturate(alpha * input.color.a);
|
||||
|
||||
if (alpha < (1.0 / 255.0))
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
return float4(input.color.rgb * alpha, alpha);
|
||||
}
|
||||
48
MVS/3DGS-D3D12/shaders/DebugPointsVS.hlsl
Normal file
48
MVS/3DGS-D3D12/shaders/DebugPointsVS.hlsl
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "PreparedSplatView.hlsli"
|
||||
|
||||
cbuffer FrameConstants : register(b0)
|
||||
{
|
||||
float4x4 gViewProjection;
|
||||
float4x4 gView;
|
||||
float4x4 gProjection;
|
||||
float4 gCameraWorldPos;
|
||||
float4 gScreenParams;
|
||||
float4 gSettings;
|
||||
};
|
||||
|
||||
StructuredBuffer<PreparedSplatView> gPreparedViews : register(t0);
|
||||
StructuredBuffer<uint> gOrderBuffer : register(t1);
|
||||
|
||||
struct VertexOutput
|
||||
{
|
||||
float4 position : SV_Position;
|
||||
float4 color : COLOR0;
|
||||
float2 localPosition : TEXCOORD0;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
|
||||
{
|
||||
VertexOutput output = (VertexOutput)0;
|
||||
uint splatIndex = gOrderBuffer[instanceId];
|
||||
PreparedSplatView view = gPreparedViews[splatIndex];
|
||||
float4 color = UnpackPreparedColor(view);
|
||||
|
||||
if (view.clipPosition.w <= 0.0)
|
||||
{
|
||||
const float nanValue = asfloat(0x7fc00000);
|
||||
output.position = float4(nanValue, nanValue, nanValue, nanValue);
|
||||
return output;
|
||||
}
|
||||
|
||||
float2 quadPosition = float2(vertexId & 1, (vertexId >> 1) & 1) * 2.0 - 1.0;
|
||||
quadPosition *= 2.0;
|
||||
|
||||
float2 deltaScreenPosition =
|
||||
(quadPosition.x * view.axis1 + quadPosition.y * view.axis2) * 2.0 / gScreenParams.xy;
|
||||
|
||||
output.position = view.clipPosition;
|
||||
output.position.xy += deltaScreenPosition * view.clipPosition.w;
|
||||
output.color = color;
|
||||
output.localPosition = quadPosition;
|
||||
return output;
|
||||
}
|
||||
477
MVS/3DGS-D3D12/shaders/DeviceRadixSort.hlsl
Normal file
477
MVS/3DGS-D3D12/shaders/DeviceRadixSort.hlsl
Normal file
@@ -0,0 +1,477 @@
|
||||
/******************************************************************************
|
||||
* DeviceRadixSort
|
||||
* Device Level 8-bit LSD Radix Sort using reduce then scan
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* Copyright Thomas Smith 5/17/2024
|
||||
* https://github.com/b0nes164/GPUSorting
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
******************************************************************************/
|
||||
#include "SortCommon.hlsl"
|
||||
|
||||
#define US_DIM 128U //The number of threads in a Upsweep threadblock
|
||||
#define SCAN_DIM 128U //The number of threads in a Scan threadblock
|
||||
|
||||
RWStructuredBuffer<uint> b_globalHist : register(u5); //buffer holding device level offsets for each binning pass
|
||||
RWStructuredBuffer<uint> b_passHist : register(u4); //buffer used to store reduced sums of partition tiles
|
||||
|
||||
groupshared uint g_us[RADIX * 2]; //Shared memory for upsweep
|
||||
groupshared uint g_scan[SCAN_DIM]; //Shared memory for the scan
|
||||
|
||||
//*****************************************************************************
|
||||
//INIT KERNEL
|
||||
//*****************************************************************************
|
||||
//Clear the global histogram, as we will be adding to it atomically
|
||||
[numthreads(1024, 1, 1)]
|
||||
void InitDeviceRadixSort(int3 id : SV_DispatchThreadID)
|
||||
{
|
||||
b_globalHist[id.x] = 0;
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//UPSWEEP KERNEL
|
||||
//*****************************************************************************
|
||||
//histogram, 64 threads to a histogram
|
||||
inline void HistogramDigitCounts(uint gtid, uint gid)
|
||||
{
|
||||
const uint histOffset = gtid / 64 * RADIX;
|
||||
const uint partitionEnd = gid == e_threadBlocks - 1 ?
|
||||
e_numKeys : (gid + 1) * PART_SIZE;
|
||||
for (uint i = gtid + gid * PART_SIZE; i < partitionEnd; i += US_DIM)
|
||||
{
|
||||
#if defined(KEY_UINT)
|
||||
InterlockedAdd(g_us[ExtractDigit(b_sort[i]) + histOffset], 1);
|
||||
#elif defined(KEY_INT)
|
||||
InterlockedAdd(g_us[ExtractDigit(IntToUint(b_sort[i])) + histOffset], 1);
|
||||
#elif defined(KEY_FLOAT)
|
||||
InterlockedAdd(g_us[ExtractDigit(FloatToUint(b_sort[i])) + histOffset], 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//reduce and pass to tile histogram
|
||||
inline void ReduceWriteDigitCounts(uint gtid, uint gid)
|
||||
{
|
||||
for (uint i = gtid; i < RADIX; i += US_DIM)
|
||||
{
|
||||
g_us[i] += g_us[i + RADIX];
|
||||
b_passHist[i * e_threadBlocks + gid] = g_us[i];
|
||||
}
|
||||
}
|
||||
|
||||
//Build the per-pass 256-bin exclusive prefix from the reduced pass histogram.
|
||||
inline void BuildGlobalHistogramExclusive(uint gtid)
|
||||
{
|
||||
uint digitIndices[2];
|
||||
uint digitTotals[2];
|
||||
uint digitCount = 0;
|
||||
for (uint i = gtid; i < RADIX; i += US_DIM)
|
||||
{
|
||||
uint total = 0u;
|
||||
const uint baseOffset = i * e_threadBlocks;
|
||||
for (uint blockIndex = 0; blockIndex < e_threadBlocks; ++blockIndex)
|
||||
{
|
||||
total += b_passHist[baseOffset + blockIndex];
|
||||
}
|
||||
|
||||
g_us[i] = total;
|
||||
digitIndices[digitCount] = i;
|
||||
digitTotals[digitCount] = total;
|
||||
++digitCount;
|
||||
}
|
||||
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
for (uint offset = 1; offset < RADIX; offset <<= 1)
|
||||
{
|
||||
for (uint i = gtid; i < RADIX; i += US_DIM)
|
||||
{
|
||||
g_us[i + RADIX] = g_us[i] + (i >= offset ? g_us[i - offset] : 0u);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
for (uint i = gtid; i < RADIX; i += US_DIM)
|
||||
{
|
||||
g_us[i] = g_us[i + RADIX];
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
|
||||
const uint globalHistOffset = GlobalHistOffset();
|
||||
for (uint localIndex = 0; localIndex < digitCount; ++localIndex)
|
||||
{
|
||||
const uint digitIndex = digitIndices[localIndex];
|
||||
b_globalHist[digitIndex + globalHistOffset] = g_us[digitIndex] - digitTotals[localIndex];
|
||||
}
|
||||
}
|
||||
|
||||
[numthreads(US_DIM, 1, 1)]
|
||||
void Upsweep(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
|
||||
{
|
||||
//get the wave size
|
||||
const uint waveSize = getWaveSize();
|
||||
|
||||
//clear shared memory
|
||||
const uint histsEnd = RADIX * 2;
|
||||
for (uint i = gtid.x; i < histsEnd; i += US_DIM)
|
||||
g_us[i] = 0;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
HistogramDigitCounts(gtid.x, gid.x);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
ReduceWriteDigitCounts(gtid.x, gid.x);
|
||||
}
|
||||
|
||||
[numthreads(US_DIM, 1, 1)]
|
||||
void BuildGlobalHistogram(uint3 gtid : SV_GroupThreadID)
|
||||
{
|
||||
const uint histsEnd = RADIX * 2;
|
||||
for (uint i = gtid.x; i < histsEnd; i += US_DIM)
|
||||
g_us[i] = 0;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
BuildGlobalHistogramExclusive(gtid.x);
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//SCAN KERNEL
|
||||
//*****************************************************************************
|
||||
inline void ExclusiveThreadBlockScanFullWGE16(
|
||||
uint gtid,
|
||||
uint laneMask,
|
||||
uint circularLaneShift,
|
||||
uint partEnd,
|
||||
uint deviceOffset,
|
||||
uint waveSize,
|
||||
inout uint reduction)
|
||||
{
|
||||
for (uint i = gtid; i < partEnd; i += SCAN_DIM)
|
||||
{
|
||||
g_scan[gtid] = b_passHist[i + deviceOffset];
|
||||
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gtid < SCAN_DIM / waveSize)
|
||||
{
|
||||
g_scan[(gtid + 1) * waveSize - 1] +=
|
||||
WavePrefixSum(g_scan[(gtid + 1) * waveSize - 1]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
uint t = (WaveGetLaneIndex() != laneMask ? g_scan[gtid] : 0) + reduction;
|
||||
if (gtid >= waveSize)
|
||||
t += WaveReadLaneAt(g_scan[gtid - 1], 0);
|
||||
b_passHist[circularLaneShift + (i & ~laneMask) + deviceOffset] = t;
|
||||
|
||||
reduction += g_scan[SCAN_DIM - 1];
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
}
|
||||
|
||||
inline void ExclusiveThreadBlockScanPartialWGE16(
|
||||
uint gtid,
|
||||
uint laneMask,
|
||||
uint circularLaneShift,
|
||||
uint partEnd,
|
||||
uint deviceOffset,
|
||||
uint waveSize,
|
||||
uint reduction)
|
||||
{
|
||||
uint i = gtid + partEnd;
|
||||
if (i < e_threadBlocks)
|
||||
g_scan[gtid] = b_passHist[deviceOffset + i];
|
||||
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gtid < SCAN_DIM / waveSize)
|
||||
{
|
||||
g_scan[(gtid + 1) * waveSize - 1] +=
|
||||
WavePrefixSum(g_scan[(gtid + 1) * waveSize - 1]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
const uint index = circularLaneShift + (i & ~laneMask);
|
||||
if (index < e_threadBlocks)
|
||||
{
|
||||
uint t = (WaveGetLaneIndex() != laneMask ? g_scan[gtid] : 0) + reduction;
|
||||
if (gtid >= waveSize)
|
||||
t += g_scan[(gtid & ~laneMask) - 1];
|
||||
b_passHist[index + deviceOffset] = t;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ExclusiveThreadBlockScanWGE16(uint gtid, uint gid, uint waveSize)
|
||||
{
|
||||
uint reduction = 0;
|
||||
const uint laneMask = waveSize - 1;
|
||||
const uint circularLaneShift = WaveGetLaneIndex() + 1 & laneMask;
|
||||
const uint partionsEnd = e_threadBlocks / SCAN_DIM * SCAN_DIM;
|
||||
const uint deviceOffset = gid * e_threadBlocks;
|
||||
|
||||
ExclusiveThreadBlockScanFullWGE16(
|
||||
gtid,
|
||||
laneMask,
|
||||
circularLaneShift,
|
||||
partionsEnd,
|
||||
deviceOffset,
|
||||
waveSize,
|
||||
reduction);
|
||||
|
||||
ExclusiveThreadBlockScanPartialWGE16(
|
||||
gtid,
|
||||
laneMask,
|
||||
circularLaneShift,
|
||||
partionsEnd,
|
||||
deviceOffset,
|
||||
waveSize,
|
||||
reduction);
|
||||
}
|
||||
|
||||
inline void ExclusiveThreadBlockScanFullWLT16(
|
||||
uint gtid,
|
||||
uint partitions,
|
||||
uint deviceOffset,
|
||||
uint laneLog,
|
||||
uint circularLaneShift,
|
||||
uint waveSize,
|
||||
inout uint reduction)
|
||||
{
|
||||
for (uint k = 0; k < partitions; ++k)
|
||||
{
|
||||
g_scan[gtid] = b_passHist[gtid + k * SCAN_DIM + deviceOffset];
|
||||
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (gtid < waveSize)
|
||||
{
|
||||
b_passHist[circularLaneShift + k * SCAN_DIM + deviceOffset] =
|
||||
(circularLaneShift ? g_scan[gtid] : 0) + reduction;
|
||||
}
|
||||
|
||||
uint offset = laneLog;
|
||||
uint j = waveSize;
|
||||
for (; j < (SCAN_DIM >> 1); j <<= laneLog)
|
||||
{
|
||||
if (gtid < (SCAN_DIM >> offset))
|
||||
{
|
||||
g_scan[((gtid + 1) << offset) - 1] +=
|
||||
WavePrefixSum(g_scan[((gtid + 1) << offset) - 1]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if ((gtid & ((j << laneLog) - 1)) >= j)
|
||||
{
|
||||
if (gtid < (j << laneLog))
|
||||
{
|
||||
b_passHist[gtid + k * SCAN_DIM + deviceOffset] =
|
||||
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0) +
|
||||
((gtid & (j - 1)) ? g_scan[gtid - 1] : 0) + reduction;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((gtid + 1) & (j - 1))
|
||||
{
|
||||
g_scan[gtid] +=
|
||||
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += laneLog;
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
//If SCAN_DIM is not a power of lanecount
|
||||
for (uint i = gtid + j; i < SCAN_DIM; i += SCAN_DIM)
|
||||
{
|
||||
b_passHist[i + k * SCAN_DIM + deviceOffset] =
|
||||
WaveReadLaneAt(g_scan[((i >> offset) << offset) - 1], 0) +
|
||||
((i & (j - 1)) ? g_scan[i - 1] : 0) + reduction;
|
||||
}
|
||||
|
||||
reduction += WaveReadLaneAt(g_scan[SCAN_DIM - 1], 0) +
|
||||
WaveReadLaneAt(g_scan[(((SCAN_DIM - 1) >> offset) << offset) - 1], 0);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
}
|
||||
|
||||
inline void ExclusiveThreadBlockScanParitalWLT16(
|
||||
uint gtid,
|
||||
uint partitions,
|
||||
uint deviceOffset,
|
||||
uint laneLog,
|
||||
uint circularLaneShift,
|
||||
uint waveSize,
|
||||
uint reduction)
|
||||
{
|
||||
const uint finalPartSize = e_threadBlocks - partitions * SCAN_DIM;
|
||||
if (gtid < finalPartSize)
|
||||
{
|
||||
g_scan[gtid] = b_passHist[gtid + partitions * SCAN_DIM + deviceOffset];
|
||||
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (gtid < waveSize && circularLaneShift < finalPartSize)
|
||||
{
|
||||
b_passHist[circularLaneShift + partitions * SCAN_DIM + deviceOffset] =
|
||||
(circularLaneShift ? g_scan[gtid] : 0) + reduction;
|
||||
}
|
||||
|
||||
uint offset = laneLog;
|
||||
for (uint j = waveSize; j < finalPartSize; j <<= laneLog)
|
||||
{
|
||||
if (gtid < (finalPartSize >> offset))
|
||||
{
|
||||
g_scan[((gtid + 1) << offset) - 1] +=
|
||||
WavePrefixSum(g_scan[((gtid + 1) << offset) - 1]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if ((gtid & ((j << laneLog) - 1)) >= j && gtid < finalPartSize)
|
||||
{
|
||||
if (gtid < (j << laneLog))
|
||||
{
|
||||
b_passHist[gtid + partitions * SCAN_DIM + deviceOffset] =
|
||||
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0) +
|
||||
((gtid & (j - 1)) ? g_scan[gtid - 1] : 0) + reduction;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((gtid + 1) & (j - 1))
|
||||
{
|
||||
g_scan[gtid] +=
|
||||
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += laneLog;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ExclusiveThreadBlockScanWLT16(uint gtid, uint gid, uint waveSize)
|
||||
{
|
||||
uint reduction = 0;
|
||||
const uint partitions = e_threadBlocks / SCAN_DIM;
|
||||
const uint deviceOffset = gid * e_threadBlocks;
|
||||
const uint laneLog = countbits(waveSize - 1);
|
||||
const uint circularLaneShift = WaveGetLaneIndex() + 1 & waveSize - 1;
|
||||
|
||||
ExclusiveThreadBlockScanFullWLT16(
|
||||
gtid,
|
||||
partitions,
|
||||
deviceOffset,
|
||||
laneLog,
|
||||
circularLaneShift,
|
||||
waveSize,
|
||||
reduction);
|
||||
|
||||
ExclusiveThreadBlockScanParitalWLT16(
|
||||
gtid,
|
||||
partitions,
|
||||
deviceOffset,
|
||||
laneLog,
|
||||
circularLaneShift,
|
||||
waveSize,
|
||||
reduction);
|
||||
}
|
||||
|
||||
//Scan does not need flattening of gids
|
||||
[numthreads(SCAN_DIM, 1, 1)]
|
||||
void Scan(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
|
||||
{
|
||||
if (gtid.x != 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const uint deviceOffset = gid.x * e_threadBlocks;
|
||||
uint runningOffset = 0u;
|
||||
for (uint blockIndex = 0u; blockIndex < e_threadBlocks; ++blockIndex)
|
||||
{
|
||||
const uint index = deviceOffset + blockIndex;
|
||||
const uint count = b_passHist[index];
|
||||
b_passHist[index] = runningOffset;
|
||||
runningOffset += count;
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//DOWNSWEEP KERNEL
|
||||
//*****************************************************************************
|
||||
inline void LoadThreadBlockReductions(uint gtid, uint gid, uint exclusiveHistReduction)
|
||||
{
|
||||
if (gtid < RADIX)
|
||||
{
|
||||
g_d[gtid + PART_SIZE] = b_globalHist[gtid + GlobalHistOffset()] +
|
||||
b_passHist[gtid * e_threadBlocks + gid] - exclusiveHistReduction;
|
||||
}
|
||||
}
|
||||
|
||||
[numthreads(D_DIM, 1, 1)]
|
||||
void Downsweep(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
|
||||
{
|
||||
if (gtid.x != 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const uint partitionStart = gid.x * PART_SIZE;
|
||||
const uint partitionEnd = min(partitionStart + PART_SIZE, e_numKeys);
|
||||
uint digitOffsets[RADIX];
|
||||
const uint globalHistOffset = GlobalHistOffset();
|
||||
for (uint digit = 0u; digit < RADIX; ++digit)
|
||||
{
|
||||
digitOffsets[digit] =
|
||||
b_globalHist[globalHistOffset + digit] +
|
||||
b_passHist[digit * e_threadBlocks + gid.x];
|
||||
}
|
||||
|
||||
for (uint index = partitionStart; index < partitionEnd; ++index)
|
||||
{
|
||||
uint key;
|
||||
#if defined(KEY_UINT)
|
||||
key = b_sort[index];
|
||||
#elif defined(KEY_INT)
|
||||
key = IntToUint(b_sort[index]);
|
||||
#elif defined(KEY_FLOAT)
|
||||
key = FloatToUint(b_sort[index]);
|
||||
#endif
|
||||
|
||||
const uint digit = ExtractDigit(key);
|
||||
const uint destinationIndex = digitOffsets[digit]++;
|
||||
|
||||
#if defined(KEY_UINT)
|
||||
b_alt[destinationIndex] = key;
|
||||
#elif defined(KEY_INT)
|
||||
b_alt[destinationIndex] = UintToInt(key);
|
||||
#elif defined(KEY_FLOAT)
|
||||
b_alt[destinationIndex] = UintToFloat(key);
|
||||
#endif
|
||||
|
||||
#if defined(SORT_PAIRS)
|
||||
#if defined(PAYLOAD_UINT)
|
||||
b_altPayload[destinationIndex] = b_sortPayload[index];
|
||||
#elif defined(PAYLOAD_INT)
|
||||
b_altPayload[destinationIndex] = b_sortPayload[index];
|
||||
#elif defined(PAYLOAD_FLOAT)
|
||||
b_altPayload[destinationIndex] = b_sortPayload[index];
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
272
MVS/3DGS-D3D12/shaders/PrepareGaussiansCS.hlsl
Normal file
272
MVS/3DGS-D3D12/shaders/PrepareGaussiansCS.hlsl
Normal file
@@ -0,0 +1,272 @@
|
||||
#include "PreparedSplatView.hlsli"
|
||||
|
||||
#define GROUP_SIZE 64
|
||||
|
||||
cbuffer FrameConstants : register(b0)
|
||||
{
|
||||
float4x4 gViewProjection;
|
||||
float4x4 gView;
|
||||
float4x4 gProjection;
|
||||
float4 gCameraWorldPos;
|
||||
float4 gScreenParams;
|
||||
float4 gSettings;
|
||||
};
|
||||
|
||||
ByteAddressBuffer gPositions : register(t0);
|
||||
ByteAddressBuffer gOther : register(t1);
|
||||
Texture2D<float4> gColor : register(t2);
|
||||
ByteAddressBuffer gSh : register(t3);
|
||||
RWStructuredBuffer<PreparedSplatView> gPreparedViews : register(u0);
|
||||
|
||||
static const float SH_C1 = 0.4886025;
|
||||
static const float SH_C2[] = { 1.0925484, -1.0925484, 0.3153916, -1.0925484, 0.5462742 };
|
||||
static const float SH_C3[] = { -0.5900436, 2.8906114, -0.4570458, 0.3731763, -0.4570458, 1.4453057, -0.5900436 };
|
||||
static const uint kColorTextureWidth = 2048;
|
||||
static const uint kOtherStride = 16;
|
||||
static const uint kShStride = 192;
|
||||
|
||||
struct SplatSHData
|
||||
{
|
||||
float3 col;
|
||||
float3 sh[15];
|
||||
};
|
||||
|
||||
float3 LoadFloat3(ByteAddressBuffer buffer, uint byteOffset)
|
||||
{
|
||||
return asfloat(buffer.Load3(byteOffset));
|
||||
}
|
||||
|
||||
uint EncodeMorton2D_16x16(uint2 c)
|
||||
{
|
||||
uint t = ((c.y & 0xF) << 8) | (c.x & 0xF);
|
||||
t = (t ^ (t << 2)) & 0x3333;
|
||||
t = (t ^ (t << 1)) & 0x5555;
|
||||
return (t | (t >> 7)) & 0xFF;
|
||||
}
|
||||
|
||||
uint2 DecodeMorton2D_16x16(uint t)
|
||||
{
|
||||
t = (t & 0xFF) | ((t & 0xFE) << 7);
|
||||
t &= 0x5555;
|
||||
t = (t ^ (t >> 1)) & 0x3333;
|
||||
t = (t ^ (t >> 2)) & 0x0F0F;
|
||||
return uint2(t & 0xF, t >> 8);
|
||||
}
|
||||
|
||||
uint3 SplatIndexToPixelIndex(uint index)
|
||||
{
|
||||
uint2 xy = DecodeMorton2D_16x16(index);
|
||||
uint tileWidth = kColorTextureWidth / 16;
|
||||
index >>= 8;
|
||||
|
||||
uint3 result;
|
||||
result.x = (index % tileWidth) * 16 + xy.x;
|
||||
result.y = (index / tileWidth) * 16 + xy.y;
|
||||
result.z = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
float4 DecodePacked_10_10_10_2(uint encoded)
|
||||
{
|
||||
return float4(
|
||||
(encoded & 1023) / 1023.0,
|
||||
((encoded >> 10) & 1023) / 1023.0,
|
||||
((encoded >> 20) & 1023) / 1023.0,
|
||||
((encoded >> 30) & 3) / 3.0);
|
||||
}
|
||||
|
||||
float4 DecodeRotation(float4 packedRotation)
|
||||
{
|
||||
uint droppedIndex = (uint)round(packedRotation.w * 3.0);
|
||||
float4 rotation;
|
||||
rotation.xyz = packedRotation.xyz * sqrt(2.0) - (1.0 / sqrt(2.0));
|
||||
rotation.w = sqrt(1.0 - saturate(dot(rotation.xyz, rotation.xyz)));
|
||||
|
||||
if (droppedIndex == 0)
|
||||
{
|
||||
rotation = rotation.wxyz;
|
||||
}
|
||||
if (droppedIndex == 1)
|
||||
{
|
||||
rotation = rotation.xwyz;
|
||||
}
|
||||
if (droppedIndex == 2)
|
||||
{
|
||||
rotation = rotation.xywz;
|
||||
}
|
||||
|
||||
return rotation;
|
||||
}
|
||||
|
||||
float3x3 CalcMatrixFromRotationScale(float4 rotation, float3 scale)
|
||||
{
|
||||
float3x3 scaleMatrix = float3x3(
|
||||
scale.x, 0, 0,
|
||||
0, scale.y, 0,
|
||||
0, 0, scale.z);
|
||||
|
||||
float x = rotation.x;
|
||||
float y = rotation.y;
|
||||
float z = rotation.z;
|
||||
float w = rotation.w;
|
||||
|
||||
float3x3 rotationMatrix = float3x3(
|
||||
1 - 2 * (y * y + z * z), 2 * (x * y - w * z), 2 * (x * z + w * y),
|
||||
2 * (x * y + w * z), 1 - 2 * (x * x + z * z), 2 * (y * z - w * x),
|
||||
2 * (x * z - w * y), 2 * (y * z + w * x), 1 - 2 * (x * x + y * y));
|
||||
|
||||
return mul(rotationMatrix, scaleMatrix);
|
||||
}
|
||||
|
||||
void CalcCovariance3D(float3x3 rotationScaleMatrix, out float3 sigma0, out float3 sigma1)
|
||||
{
|
||||
float3x3 sigma = mul(rotationScaleMatrix, transpose(rotationScaleMatrix));
|
||||
sigma0 = float3(sigma._m00, sigma._m01, sigma._m02);
|
||||
sigma1 = float3(sigma._m11, sigma._m12, sigma._m22);
|
||||
}
|
||||
|
||||
float3 CalcCovariance2D(float3 worldPosition, float3 covariance0, float3 covariance1)
|
||||
{
|
||||
float3 viewPosition = mul(float4(worldPosition, 1.0), gView).xyz;
|
||||
|
||||
float aspect = gProjection._m00 / gProjection._m11;
|
||||
float tanFovX = rcp(gProjection._m00);
|
||||
float tanFovY = rcp(gProjection._m11 * aspect);
|
||||
float clampX = 1.3 * tanFovX;
|
||||
float clampY = 1.3 * tanFovY;
|
||||
viewPosition.x = clamp(viewPosition.x / viewPosition.z, -clampX, clampX) * viewPosition.z;
|
||||
viewPosition.y = clamp(viewPosition.y / viewPosition.z, -clampY, clampY) * viewPosition.z;
|
||||
|
||||
float focal = gScreenParams.x * gProjection._m00 * 0.5;
|
||||
|
||||
float3x3 jacobian = float3x3(
|
||||
focal / viewPosition.z, 0, -(focal * viewPosition.x) / (viewPosition.z * viewPosition.z),
|
||||
0, focal / viewPosition.z, -(focal * viewPosition.y) / (viewPosition.z * viewPosition.z),
|
||||
0, 0, 0);
|
||||
float3x3 worldToView = transpose((float3x3)gView);
|
||||
float3x3 transform = mul(jacobian, worldToView);
|
||||
float3x3 covariance = float3x3(
|
||||
covariance0.x, covariance0.y, covariance0.z,
|
||||
covariance0.y, covariance1.x, covariance1.y,
|
||||
covariance0.z, covariance1.y, covariance1.z);
|
||||
float3x3 projected = mul(transform, mul(covariance, transpose(transform)));
|
||||
projected._m00 += 0.3;
|
||||
projected._m11 += 0.3;
|
||||
return float3(projected._m00, projected._m01, projected._m11);
|
||||
}
|
||||
|
||||
void DecomposeCovariance(float3 covariance2D, out float2 axis1, out float2 axis2)
|
||||
{
|
||||
float diagonal0 = covariance2D.x;
|
||||
float diagonal1 = covariance2D.z;
|
||||
float offDiagonal = covariance2D.y;
|
||||
float mid = 0.5 * (diagonal0 + diagonal1);
|
||||
float radius = length(float2((diagonal0 - diagonal1) * 0.5, offDiagonal));
|
||||
float lambda0 = mid + radius;
|
||||
float lambda1 = max(mid - radius, 0.1);
|
||||
float2 diagonalVector = normalize(float2(offDiagonal, lambda0 - diagonal0));
|
||||
diagonalVector.y = -diagonalVector.y;
|
||||
const float maxSize = 4096.0;
|
||||
axis1 = min(sqrt(2.0 * lambda0), maxSize) * diagonalVector;
|
||||
axis2 = min(sqrt(2.0 * lambda1), maxSize) * float2(diagonalVector.y, -diagonalVector.x);
|
||||
}
|
||||
|
||||
SplatSHData LoadSplatSH(uint index)
|
||||
{
|
||||
SplatSHData sh;
|
||||
const uint shBaseOffset = index * kShStride;
|
||||
sh.col = gColor.Load(int3(SplatIndexToPixelIndex(index).xy, 0)).rgb;
|
||||
|
||||
[unroll]
|
||||
for (uint coefficientIndex = 0; coefficientIndex < 15; ++coefficientIndex)
|
||||
{
|
||||
sh.sh[coefficientIndex] = LoadFloat3(gSh, shBaseOffset + coefficientIndex * 12);
|
||||
}
|
||||
|
||||
return sh;
|
||||
}
|
||||
|
||||
float3 ShadeSH(SplatSHData sh, float3 direction, int shOrder)
|
||||
{
|
||||
direction *= -1.0;
|
||||
float x = direction.x;
|
||||
float y = direction.y;
|
||||
float z = direction.z;
|
||||
|
||||
float3 result = sh.col;
|
||||
if (shOrder >= 1)
|
||||
{
|
||||
result += SH_C1 * (-sh.sh[0] * y + sh.sh[1] * z - sh.sh[2] * x);
|
||||
if (shOrder >= 2)
|
||||
{
|
||||
float xx = x * x;
|
||||
float yy = y * y;
|
||||
float zz = z * z;
|
||||
float xy = x * y;
|
||||
float yz = y * z;
|
||||
float xz = x * z;
|
||||
result +=
|
||||
(SH_C2[0] * xy) * sh.sh[3] +
|
||||
(SH_C2[1] * yz) * sh.sh[4] +
|
||||
(SH_C2[2] * (2 * zz - xx - yy)) * sh.sh[5] +
|
||||
(SH_C2[3] * xz) * sh.sh[6] +
|
||||
(SH_C2[4] * (xx - yy)) * sh.sh[7];
|
||||
if (shOrder >= 3)
|
||||
{
|
||||
result +=
|
||||
(SH_C3[0] * y * (3 * xx - yy)) * sh.sh[8] +
|
||||
(SH_C3[1] * xy * z) * sh.sh[9] +
|
||||
(SH_C3[2] * y * (4 * zz - xx - yy)) * sh.sh[10] +
|
||||
(SH_C3[3] * z * (2 * zz - 3 * xx - 3 * yy)) * sh.sh[11] +
|
||||
(SH_C3[4] * x * (4 * zz - xx - yy)) * sh.sh[12] +
|
||||
(SH_C3[5] * z * (xx - yy)) * sh.sh[13] +
|
||||
(SH_C3[6] * x * (xx - 3 * yy)) * sh.sh[14];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return max(result, 0.0);
|
||||
}
|
||||
|
||||
[numthreads(GROUP_SIZE, 1, 1)]
|
||||
void MainCS(uint3 dispatchThreadId : SV_DispatchThreadID)
|
||||
{
|
||||
uint index = dispatchThreadId.x;
|
||||
uint splatCount = (uint)gSettings.x;
|
||||
if (index >= splatCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PreparedSplatView view = (PreparedSplatView)0;
|
||||
|
||||
float3 position = LoadFloat3(gPositions, index * 12);
|
||||
uint packedRotation = gOther.Load(index * kOtherStride);
|
||||
float4 rotation = DecodeRotation(DecodePacked_10_10_10_2(packedRotation));
|
||||
float3 scale = LoadFloat3(gOther, index * kOtherStride + 4);
|
||||
float4 colorOpacity = gColor.Load(int3(SplatIndexToPixelIndex(index).xy, 0));
|
||||
|
||||
view.clipPosition = mul(float4(position, 1.0), gViewProjection);
|
||||
if (view.clipPosition.w > 0.0)
|
||||
{
|
||||
float3x3 rotationScale = CalcMatrixFromRotationScale(rotation, scale);
|
||||
float3 covariance0;
|
||||
float3 covariance1;
|
||||
CalcCovariance3D(rotationScale, covariance0, covariance1);
|
||||
float splatScaleSquared = gSettings.w * gSettings.w;
|
||||
covariance0 *= splatScaleSquared;
|
||||
covariance1 *= splatScaleSquared;
|
||||
float3 covariance2D = CalcCovariance2D(position, covariance0, covariance1);
|
||||
DecomposeCovariance(covariance2D, view.axis1, view.axis2);
|
||||
|
||||
SplatSHData sh = LoadSplatSH(index);
|
||||
float3 viewDirection = normalize(gCameraWorldPos.xyz - position);
|
||||
float3 shadedColor = ShadeSH(sh, viewDirection, (int)gSettings.z);
|
||||
float opacity = saturate(colorOpacity.a * gSettings.y);
|
||||
|
||||
view.packedColor.x = (f32tof16(shadedColor.r) << 16) | f32tof16(shadedColor.g);
|
||||
view.packedColor.y = (f32tof16(shadedColor.b) << 16) | f32tof16(opacity);
|
||||
}
|
||||
|
||||
gPreparedViews[index] = view;
|
||||
}
|
||||
22
MVS/3DGS-D3D12/shaders/PreparedSplatView.hlsli
Normal file
22
MVS/3DGS-D3D12/shaders/PreparedSplatView.hlsli
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef PREPARED_SPLAT_VIEW_HLSLI
|
||||
#define PREPARED_SPLAT_VIEW_HLSLI
|
||||
|
||||
struct PreparedSplatView
|
||||
{
|
||||
float4 clipPosition;
|
||||
float2 axis1;
|
||||
float2 axis2;
|
||||
uint2 packedColor;
|
||||
};
|
||||
|
||||
float4 UnpackPreparedColor(PreparedSplatView view)
|
||||
{
|
||||
float4 color;
|
||||
color.r = f16tof32((view.packedColor.x >> 16) & 0xFFFF);
|
||||
color.g = f16tof32(view.packedColor.x & 0xFFFF);
|
||||
color.b = f16tof32((view.packedColor.y >> 16) & 0xFFFF);
|
||||
color.a = f16tof32(view.packedColor.y & 0xFFFF);
|
||||
return color;
|
||||
}
|
||||
|
||||
#endif
|
||||
959
MVS/3DGS-D3D12/shaders/SortCommon.hlsl
Normal file
959
MVS/3DGS-D3D12/shaders/SortCommon.hlsl
Normal file
@@ -0,0 +1,959 @@
|
||||
/******************************************************************************
|
||||
* SortCommon
|
||||
* Common functions for GPUSorting
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* Copyright Thomas Smith 5/17/2024
|
||||
* https://github.com/b0nes164/GPUSorting
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
******************************************************************************/
|
||||
#define KEYS_PER_THREAD 15U
|
||||
#define D_DIM 256U
|
||||
#define PART_SIZE 3840U
|
||||
#define D_TOTAL_SMEM 4096U
|
||||
|
||||
#define RADIX 256U //Number of digit bins
|
||||
#define RADIX_MASK 255U //Mask of digit bins
|
||||
#define HALF_RADIX 128U //For smaller waves where bit packing is necessary
|
||||
#define HALF_MASK 127U // ''
|
||||
#define RADIX_LOG 8U //log2(RADIX)
|
||||
#define RADIX_PASSES 4U //(Key width) / RADIX_LOG
|
||||
|
||||
cbuffer cbGpuSorting : register(b0)
|
||||
{
|
||||
uint e_numKeys;
|
||||
uint e_radixShift;
|
||||
uint e_threadBlocks;
|
||||
uint padding;
|
||||
};
|
||||
|
||||
#if defined(KEY_UINT)
|
||||
RWStructuredBuffer<uint> b_sort : register(u0);
|
||||
RWStructuredBuffer<uint> b_alt : register(u1);
|
||||
#elif defined(KEY_INT)
|
||||
RWStructuredBuffer<int> b_sort : register(u0);
|
||||
RWStructuredBuffer<int> b_alt : register(u1);
|
||||
#elif defined(KEY_FLOAT)
|
||||
RWStructuredBuffer<float> b_sort : register(u0);
|
||||
RWStructuredBuffer<float> b_alt : register(u1);
|
||||
#endif
|
||||
|
||||
#if defined(PAYLOAD_UINT)
|
||||
RWStructuredBuffer<uint> b_sortPayload : register(u2);
|
||||
RWStructuredBuffer<uint> b_altPayload : register(u3);
|
||||
#elif defined(PAYLOAD_INT)
|
||||
RWStructuredBuffer<int> b_sortPayload : register(u2);
|
||||
RWStructuredBuffer<int> b_altPayload : register(u3);
|
||||
#elif defined(PAYLOAD_FLOAT)
|
||||
RWStructuredBuffer<float> b_sortPayload : register(u2);
|
||||
RWStructuredBuffer<float> b_altPayload : register(u3);
|
||||
#endif
|
||||
|
||||
groupshared uint g_d[D_TOTAL_SMEM]; //Shared memory for DigitBinningPass and DownSweep kernels
|
||||
|
||||
struct KeyStruct
|
||||
{
|
||||
uint k[KEYS_PER_THREAD];
|
||||
};
|
||||
|
||||
struct OffsetStruct
|
||||
{
|
||||
#if defined(ENABLE_16_BIT)
|
||||
uint16_t o[KEYS_PER_THREAD];
|
||||
#else
|
||||
uint o[KEYS_PER_THREAD];
|
||||
#endif
|
||||
};
|
||||
|
||||
struct DigitStruct
|
||||
{
|
||||
#if defined(ENABLE_16_BIT)
|
||||
uint16_t d[KEYS_PER_THREAD];
|
||||
#else
|
||||
uint d[KEYS_PER_THREAD];
|
||||
#endif
|
||||
};
|
||||
|
||||
//*****************************************************************************
|
||||
//HELPER FUNCTIONS
|
||||
//*****************************************************************************
|
||||
//Due to a bug with SPIRV pre 1.6, we cannot use WaveGetLaneCount() to get the currently active wavesize
|
||||
inline uint getWaveSize()
|
||||
{
|
||||
#if defined(VULKAN)
|
||||
GroupMemoryBarrierWithGroupSync(); //Make absolutely sure the wave is not diverged here
|
||||
return dot(countbits(WaveActiveBallot(true)), uint4(1, 1, 1, 1));
|
||||
#else
|
||||
return WaveGetLaneCount();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline uint getWaveIndex(uint gtid, uint waveSize)
|
||||
{
|
||||
return gtid / waveSize;
|
||||
}
|
||||
|
||||
//Radix Tricks by Michael Herf
|
||||
//http://stereopsis.com/radix.html
|
||||
inline uint FloatToUint(float f)
|
||||
{
|
||||
uint mask = -((int) (asuint(f) >> 31)) | 0x80000000;
|
||||
return asuint(f) ^ mask;
|
||||
}
|
||||
|
||||
inline float UintToFloat(uint u)
|
||||
{
|
||||
uint mask = ((u >> 31) - 1) | 0x80000000;
|
||||
return asfloat(u ^ mask);
|
||||
}
|
||||
|
||||
inline uint IntToUint(int i)
|
||||
{
|
||||
return asuint(i ^ 0x80000000);
|
||||
}
|
||||
|
||||
inline int UintToInt(uint u)
|
||||
{
|
||||
return asint(u ^ 0x80000000);
|
||||
}
|
||||
|
||||
inline uint getWaveCountPass(uint waveSize)
|
||||
{
|
||||
return D_DIM / waveSize;
|
||||
}
|
||||
|
||||
inline uint ExtractDigit(uint key)
|
||||
{
|
||||
return key >> e_radixShift & RADIX_MASK;
|
||||
}
|
||||
|
||||
inline uint ExtractDigit(uint key, uint shift)
|
||||
{
|
||||
return key >> shift & RADIX_MASK;
|
||||
}
|
||||
|
||||
inline uint ExtractPackedIndex(uint key)
|
||||
{
|
||||
return key >> (e_radixShift + 1) & HALF_MASK;
|
||||
}
|
||||
|
||||
inline uint ExtractPackedShift(uint key)
|
||||
{
|
||||
return (key >> e_radixShift & 1) ? 16 : 0;
|
||||
}
|
||||
|
||||
inline uint ExtractPackedValue(uint packed, uint key)
|
||||
{
|
||||
return packed >> ExtractPackedShift(key) & 0xffff;
|
||||
}
|
||||
|
||||
inline uint SubPartSizeWGE16(uint waveSize)
|
||||
{
|
||||
return KEYS_PER_THREAD * waveSize;
|
||||
}
|
||||
|
||||
inline uint SharedOffsetWGE16(uint gtid, uint waveSize)
|
||||
{
|
||||
return WaveGetLaneIndex() + getWaveIndex(gtid, waveSize) * SubPartSizeWGE16(waveSize);
|
||||
}
|
||||
|
||||
inline uint SubPartSizeWLT16(uint waveSize, uint _serialIterations)
|
||||
{
|
||||
return KEYS_PER_THREAD * waveSize * _serialIterations;
|
||||
}
|
||||
|
||||
inline uint SharedOffsetWLT16(uint gtid, uint waveSize, uint _serialIterations)
|
||||
{
|
||||
return WaveGetLaneIndex() +
|
||||
(getWaveIndex(gtid, waveSize) / _serialIterations * SubPartSizeWLT16(waveSize, _serialIterations)) +
|
||||
(getWaveIndex(gtid, waveSize) % _serialIterations * waveSize);
|
||||
}
|
||||
|
||||
inline uint DeviceOffsetWGE16(uint gtid, uint waveSize, uint partIndex)
|
||||
{
|
||||
return SharedOffsetWGE16(gtid, waveSize) + partIndex * PART_SIZE;
|
||||
}
|
||||
|
||||
inline uint DeviceOffsetWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
|
||||
{
|
||||
return SharedOffsetWLT16(gtid, waveSize, serialIterations) + partIndex * PART_SIZE;
|
||||
}
|
||||
|
||||
inline uint GlobalHistOffset()
|
||||
{
|
||||
return e_radixShift << 5;
|
||||
}
|
||||
|
||||
inline uint WaveHistsSizeWGE16(uint waveSize)
|
||||
{
|
||||
return D_DIM / waveSize * RADIX;
|
||||
}
|
||||
|
||||
inline uint WaveHistsSizeWLT16()
|
||||
{
|
||||
return D_TOTAL_SMEM;
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//FUNCTIONS COMMON TO THE DOWNSWEEP / DIGIT BINNING PASS
|
||||
//*****************************************************************************
|
||||
//If the size of a wave is too small, we do not have enough space in
|
||||
//shared memory to assign a histogram to each wave, so instead,
|
||||
//some operations are peformed serially.
|
||||
inline uint SerialIterations(uint waveSize)
|
||||
{
|
||||
return (D_DIM / waveSize + 31) >> 5;
|
||||
}
|
||||
|
||||
inline void ClearWaveHists(uint gtid, uint waveSize)
|
||||
{
|
||||
const uint histsEnd = waveSize >= 16 ?
|
||||
WaveHistsSizeWGE16(waveSize) : WaveHistsSizeWLT16();
|
||||
for (uint i = gtid; i < histsEnd; i += D_DIM)
|
||||
g_d[i] = 0;
|
||||
}
|
||||
|
||||
inline void LoadKey(inout uint key, uint index)
|
||||
{
|
||||
#if defined(KEY_UINT)
|
||||
key = b_sort[index];
|
||||
#elif defined(KEY_INT)
|
||||
key = UintToInt(b_sort[index]);
|
||||
#elif defined(KEY_FLOAT)
|
||||
key = FloatToUint(b_sort[index]);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void LoadDummyKey(inout uint key)
|
||||
{
|
||||
key = 0xffffffff;
|
||||
}
|
||||
|
||||
inline KeyStruct LoadKeysWGE16(uint gtid, uint waveSize, uint partIndex)
|
||||
{
|
||||
KeyStruct keys;
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize)
|
||||
{
|
||||
LoadKey(keys.k[i], t);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
inline KeyStruct LoadKeysWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
|
||||
{
|
||||
KeyStruct keys;
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize * serialIterations)
|
||||
{
|
||||
LoadKey(keys.k[i], t);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
inline KeyStruct LoadKeysPartialWGE16(uint gtid, uint waveSize, uint partIndex)
|
||||
{
|
||||
KeyStruct keys;
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize)
|
||||
{
|
||||
if (t < e_numKeys)
|
||||
LoadKey(keys.k[i], t);
|
||||
else
|
||||
LoadDummyKey(keys.k[i]);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
inline KeyStruct LoadKeysPartialWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
|
||||
{
|
||||
KeyStruct keys;
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize * serialIterations)
|
||||
{
|
||||
if (t < e_numKeys)
|
||||
LoadKey(keys.k[i], t);
|
||||
else
|
||||
LoadDummyKey(keys.k[i]);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
inline uint WaveFlagsWGE16(uint waveSize)
|
||||
{
|
||||
return (waveSize & 31) ? (1U << waveSize) - 1 : 0xffffffff;
|
||||
}
|
||||
|
||||
inline uint WaveFlagsWLT16(uint waveSize)
|
||||
{
|
||||
return (1U << waveSize) - 1;;
|
||||
}
|
||||
|
||||
inline void WarpLevelMultiSplitWGE16(uint key, inout uint4 waveFlags)
|
||||
{
|
||||
[unroll]
|
||||
for (uint k = 0; k < RADIX_LOG; ++k)
|
||||
{
|
||||
const uint currentBit = 1U << (k + e_radixShift);
|
||||
const bool t = (key & currentBit) != 0;
|
||||
GroupMemoryBarrierWithGroupSync(); //Play on the safe side, throw in a barrier for convergence
|
||||
const uint4 ballot = WaveActiveBallot(t);
|
||||
if(t)
|
||||
waveFlags &= ballot;
|
||||
else
|
||||
waveFlags &= (~ballot);
|
||||
}
|
||||
}
|
||||
|
||||
inline uint2 CountBitsWGE16(uint waveSize, uint ltMask, uint4 waveFlags)
|
||||
{
|
||||
uint2 count = uint2(0, 0);
|
||||
|
||||
for(uint wavePart = 0; wavePart < waveSize; wavePart += 32)
|
||||
{
|
||||
uint t = countbits(waveFlags[wavePart >> 5]);
|
||||
if (WaveGetLaneIndex() >= wavePart)
|
||||
{
|
||||
if (WaveGetLaneIndex() >= wavePart + 32)
|
||||
count.x += t;
|
||||
else
|
||||
count.x += countbits(waveFlags[wavePart >> 5] & ltMask);
|
||||
}
|
||||
count.y += t;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
inline void WarpLevelMultiSplitWLT16(uint key, inout uint waveFlags)
|
||||
{
|
||||
[unroll]
|
||||
for (uint k = 0; k < RADIX_LOG; ++k)
|
||||
{
|
||||
const bool t = key >> (k + e_radixShift) & 1;
|
||||
waveFlags &= (t ? 0 : 0xffffffff) ^ (uint) WaveActiveBallot(t);
|
||||
}
|
||||
}
|
||||
|
||||
inline OffsetStruct RankKeysWGE16(
|
||||
uint waveSize,
|
||||
uint waveOffset,
|
||||
KeyStruct keys)
|
||||
{
|
||||
OffsetStruct offsets;
|
||||
const uint initialFlags = WaveFlagsWGE16(waveSize);
|
||||
const uint ltMask = (1U << (WaveGetLaneIndex() & 31)) - 1;
|
||||
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
{
|
||||
uint4 waveFlags = initialFlags;
|
||||
WarpLevelMultiSplitWGE16(keys.k[i], waveFlags);
|
||||
|
||||
const uint index = ExtractDigit(keys.k[i]) + waveOffset;
|
||||
const uint2 bitCount = CountBitsWGE16(waveSize, ltMask, waveFlags);
|
||||
|
||||
offsets.o[i] = g_d[index] + bitCount.x;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (bitCount.x == 0)
|
||||
g_d[index] += bitCount.y;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
inline OffsetStruct RankKeysWLT16(uint waveSize, uint waveIndex, KeyStruct keys, uint serialIterations)
|
||||
{
|
||||
OffsetStruct offsets;
|
||||
const uint ltMask = (1U << WaveGetLaneIndex()) - 1;
|
||||
const uint initialFlags = WaveFlagsWLT16(waveSize);
|
||||
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
{
|
||||
uint waveFlags = initialFlags;
|
||||
WarpLevelMultiSplitWLT16(keys.k[i], waveFlags);
|
||||
|
||||
const uint index = ExtractPackedIndex(keys.k[i]) +
|
||||
(waveIndex / serialIterations * HALF_RADIX);
|
||||
|
||||
const uint peerBits = countbits(waveFlags & ltMask);
|
||||
for (uint k = 0; k < serialIterations; ++k)
|
||||
{
|
||||
if (waveIndex % serialIterations == k)
|
||||
offsets.o[i] = ExtractPackedValue(g_d[index], keys.k[i]) + peerBits;
|
||||
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (waveIndex % serialIterations == k && peerBits == 0)
|
||||
{
|
||||
InterlockedAdd(g_d[index],
|
||||
countbits(waveFlags) << ExtractPackedShift(keys.k[i]));
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
inline uint WaveHistInclusiveScanCircularShiftWGE16(uint gtid, uint waveSize)
|
||||
{
|
||||
uint histReduction = g_d[gtid];
|
||||
for (uint i = gtid + RADIX; i < WaveHistsSizeWGE16(waveSize); i += RADIX)
|
||||
{
|
||||
histReduction += g_d[i];
|
||||
g_d[i] = histReduction - g_d[i];
|
||||
}
|
||||
return histReduction;
|
||||
}
|
||||
|
||||
inline uint WaveHistInclusiveScanCircularShiftWLT16(uint gtid)
|
||||
{
|
||||
uint histReduction = g_d[gtid];
|
||||
for (uint i = gtid + HALF_RADIX; i < WaveHistsSizeWLT16(); i += HALF_RADIX)
|
||||
{
|
||||
histReduction += g_d[i];
|
||||
g_d[i] = histReduction - g_d[i];
|
||||
}
|
||||
return histReduction;
|
||||
}
|
||||
|
||||
inline void WaveHistReductionExclusiveScanWGE16(uint gtid, uint waveSize, uint histReduction)
|
||||
{
|
||||
if (gtid < RADIX)
|
||||
{
|
||||
const uint laneMask = waveSize - 1;
|
||||
g_d[((WaveGetLaneIndex() + 1) & laneMask) + (gtid & ~laneMask)] = histReduction;
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gtid < RADIX / waveSize)
|
||||
{
|
||||
g_d[gtid * waveSize] =
|
||||
WavePrefixSum(g_d[gtid * waveSize]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
uint t = WaveReadLaneAt(g_d[gtid], 0);
|
||||
if (gtid < RADIX && WaveGetLaneIndex())
|
||||
g_d[gtid] += t;
|
||||
}
|
||||
|
||||
//inclusive/exclusive prefix sum up the histograms,
|
||||
//use a blelloch scan for in place packed exclusive
|
||||
inline void WaveHistReductionExclusiveScanWLT16(uint gtid)
|
||||
{
|
||||
uint shift = 1;
|
||||
for (uint j = RADIX >> 2; j > 0; j >>= 1)
|
||||
{
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (gtid < j)
|
||||
{
|
||||
g_d[((((gtid << 1) + 2) << shift) - 1) >> 1] +=
|
||||
g_d[((((gtid << 1) + 1) << shift) - 1) >> 1] & 0xffff0000;
|
||||
}
|
||||
shift++;
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gtid == 0)
|
||||
g_d[HALF_RADIX - 1] &= 0xffff;
|
||||
|
||||
for (uint j = 1; j < RADIX >> 1; j <<= 1)
|
||||
{
|
||||
--shift;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (gtid < j)
|
||||
{
|
||||
const uint t = ((((gtid << 1) + 1) << shift) - 1) >> 1;
|
||||
const uint t2 = ((((gtid << 1) + 2) << shift) - 1) >> 1;
|
||||
const uint t3 = g_d[t];
|
||||
g_d[t] = (g_d[t] & 0xffff) | (g_d[t2] & 0xffff0000);
|
||||
g_d[t2] += t3 & 0xffff0000;
|
||||
}
|
||||
}
|
||||
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (gtid < HALF_RADIX)
|
||||
{
|
||||
const uint t = g_d[gtid];
|
||||
g_d[gtid] = (t >> 16) + (t << 16) + (t & 0xffff0000);
|
||||
}
|
||||
}
|
||||
|
||||
inline void UpdateOffsetsWGE16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
inout OffsetStruct offsets,
|
||||
KeyStruct keys)
|
||||
{
|
||||
if (gtid >= waveSize)
|
||||
{
|
||||
const uint t = getWaveIndex(gtid, waveSize) * RADIX;
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
{
|
||||
const uint t2 = ExtractDigit(keys.k[i]);
|
||||
offsets.o[i] += g_d[t2 + t] + g_d[t2];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
offsets.o[i] += g_d[ExtractDigit(keys.k[i])];
|
||||
}
|
||||
}
|
||||
|
||||
inline void UpdateOffsetsWLT16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint serialIterations,
|
||||
inout OffsetStruct offsets,
|
||||
KeyStruct keys)
|
||||
{
|
||||
if (gtid >= waveSize * serialIterations)
|
||||
{
|
||||
const uint t = getWaveIndex(gtid, waveSize) / serialIterations * HALF_RADIX;
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
{
|
||||
const uint t2 = ExtractPackedIndex(keys.k[i]);
|
||||
offsets.o[i] += ExtractPackedValue(g_d[t2 + t] + g_d[t2], keys.k[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
offsets.o[i] += ExtractPackedValue(g_d[ExtractPackedIndex(keys.k[i])], keys.k[i]);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterKeysShared(OffsetStruct offsets, KeyStruct keys)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
g_d[offsets.o[i]] = keys.k[i];
|
||||
}
|
||||
|
||||
inline uint DescendingIndex(uint deviceIndex)
|
||||
{
|
||||
return e_numKeys - deviceIndex - 1;
|
||||
}
|
||||
|
||||
inline void WriteKey(uint deviceIndex, uint groupSharedIndex)
|
||||
{
|
||||
#if defined(KEY_UINT)
|
||||
b_alt[deviceIndex] = g_d[groupSharedIndex];
|
||||
#elif defined(KEY_INT)
|
||||
b_alt[deviceIndex] = UintToInt(g_d[groupSharedIndex]);
|
||||
#elif defined(KEY_FLOAT)
|
||||
b_alt[deviceIndex] = UintToFloat(g_d[groupSharedIndex]);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void LoadPayload(inout uint payload, uint deviceIndex)
|
||||
{
|
||||
#if defined(PAYLOAD_UINT)
|
||||
payload = b_sortPayload[deviceIndex];
|
||||
#elif defined(PAYLOAD_INT) || defined(PAYLOAD_FLOAT)
|
||||
payload = asuint(b_sortPayload[deviceIndex]);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ScatterPayloadsShared(OffsetStruct offsets, KeyStruct payloads)
|
||||
{
|
||||
ScatterKeysShared(offsets, payloads);
|
||||
}
|
||||
|
||||
inline void WritePayload(uint deviceIndex, uint groupSharedIndex)
|
||||
{
|
||||
#if defined(PAYLOAD_UINT)
|
||||
b_altPayload[deviceIndex] = g_d[groupSharedIndex];
|
||||
#elif defined(PAYLOAD_INT)
|
||||
b_altPayload[deviceIndex] = asint(g_d[groupSharedIndex]);
|
||||
#elif defined(PAYLOAD_FLOAT)
|
||||
b_altPayload[deviceIndex] = asfloat(g_d[groupSharedIndex]);
|
||||
#endif
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//SCATTERING: FULL PARTITIONS
|
||||
//*****************************************************************************
|
||||
//KEYS ONLY
|
||||
inline void ScatterKeysOnlyDeviceAscending(uint gtid)
|
||||
{
|
||||
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
|
||||
WriteKey(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i, i);
|
||||
}
|
||||
|
||||
inline void ScatterKeysOnlyDeviceDescending(uint gtid)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
|
||||
WriteKey(DescendingIndex(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i), i);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterKeysOnlyDeviceAscending(gtid);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterKeysOnlyDevice(uint gtid)
|
||||
{
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterKeysOnlyDeviceAscending(gtid);
|
||||
#else
|
||||
ScatterKeysOnlyDeviceDescending(gtid);
|
||||
#endif
|
||||
}
|
||||
|
||||
//KEY VALUE PAIRS
|
||||
inline void ScatterPairsKeyPhaseAscending(
|
||||
uint gtid,
|
||||
inout DigitStruct digits)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
digits.d[i] = ExtractDigit(g_d[t]);
|
||||
WriteKey(g_d[digits.d[i] + PART_SIZE] + t, t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPairsKeyPhaseDescending(
|
||||
uint gtid,
|
||||
inout DigitStruct digits)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
digits.d[i] = ExtractDigit(g_d[t]);
|
||||
WriteKey(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterPairsKeyPhaseAscending(gtid, digits);
|
||||
}
|
||||
}
|
||||
|
||||
inline void LoadPayloadsWGE16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
inout KeyStruct payloads)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize)
|
||||
{
|
||||
LoadPayload(payloads.k[i], t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void LoadPayloadsWLT16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
uint serialIterations,
|
||||
inout KeyStruct payloads)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize * serialIterations)
|
||||
{
|
||||
LoadPayload(payloads.k[i], t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPayloadsAscending(uint gtid, DigitStruct digits)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
WritePayload(g_d[digits.d[i] + PART_SIZE] + t, t);
|
||||
}
|
||||
|
||||
inline void ScatterPayloadsDescending(uint gtid, DigitStruct digits)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
WritePayload(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterPayloadsAscending(gtid, digits);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPairsDevice(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
OffsetStruct offsets)
|
||||
{
|
||||
DigitStruct digits;
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterPairsKeyPhaseAscending(gtid, digits);
|
||||
#else
|
||||
ScatterPairsKeyPhaseDescending(gtid, digits);
|
||||
#endif
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
KeyStruct payloads;
|
||||
if (waveSize >= 16)
|
||||
LoadPayloadsWGE16(gtid, waveSize, partIndex, payloads);
|
||||
else
|
||||
LoadPayloadsWLT16(gtid, waveSize, partIndex, SerialIterations(waveSize), payloads);
|
||||
ScatterPayloadsShared(offsets, payloads);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterPayloadsAscending(gtid, digits);
|
||||
#else
|
||||
ScatterPayloadsDescending(gtid, digits);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ScatterDevice(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
OffsetStruct offsets)
|
||||
{
|
||||
#if defined(SORT_PAIRS)
|
||||
ScatterPairsDevice(
|
||||
gtid,
|
||||
waveSize,
|
||||
partIndex,
|
||||
offsets);
|
||||
#else
|
||||
ScatterKeysOnlyDevice(gtid);
|
||||
#endif
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//SCATTERING: PARTIAL PARTITIONS
|
||||
//*****************************************************************************
|
||||
//KEYS ONLY
|
||||
inline void ScatterKeysOnlyDevicePartialAscending(uint gtid, uint finalPartSize)
|
||||
{
|
||||
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
|
||||
{
|
||||
if (i < finalPartSize)
|
||||
WriteKey(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i, i);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterKeysOnlyDevicePartialDescending(uint gtid, uint finalPartSize)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
|
||||
{
|
||||
if (i < finalPartSize)
|
||||
WriteKey(DescendingIndex(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i), i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterKeysOnlyDevicePartialAscending(gtid, finalPartSize);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterKeysOnlyDevicePartial(uint gtid, uint partIndex)
|
||||
{
|
||||
const uint finalPartSize = e_numKeys - partIndex * PART_SIZE;
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterKeysOnlyDevicePartialAscending(gtid, finalPartSize);
|
||||
#else
|
||||
ScatterKeysOnlyDevicePartialDescending(gtid, finalPartSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
//KEY VALUE PAIRS
|
||||
inline void ScatterPairsKeyPhaseAscendingPartial(
|
||||
uint gtid,
|
||||
uint finalPartSize,
|
||||
inout DigitStruct digits)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
if (t < finalPartSize)
|
||||
{
|
||||
digits.d[i] = ExtractDigit(g_d[t]);
|
||||
WriteKey(g_d[digits.d[i] + PART_SIZE] + t, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPairsKeyPhaseDescendingPartial(
|
||||
uint gtid,
|
||||
uint finalPartSize,
|
||||
inout DigitStruct digits)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
if (t < finalPartSize)
|
||||
{
|
||||
digits.d[i] = ExtractDigit(g_d[t]);
|
||||
WriteKey(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterPairsKeyPhaseAscendingPartial(gtid, finalPartSize, digits);
|
||||
}
|
||||
}
|
||||
|
||||
inline void LoadPayloadsPartialWGE16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
inout KeyStruct payloads)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize)
|
||||
{
|
||||
if (t < e_numKeys)
|
||||
LoadPayload(payloads.k[i], t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void LoadPayloadsPartialWLT16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
uint serialIterations,
|
||||
inout KeyStruct payloads)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize * serialIterations)
|
||||
{
|
||||
if (t < e_numKeys)
|
||||
LoadPayload(payloads.k[i], t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPayloadsAscendingPartial(
|
||||
uint gtid,
|
||||
uint finalPartSize,
|
||||
DigitStruct digits)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
if (t < finalPartSize)
|
||||
WritePayload(g_d[digits.d[i] + PART_SIZE] + t, t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPayloadsDescendingPartial(
|
||||
uint gtid,
|
||||
uint finalPartSize,
|
||||
DigitStruct digits)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
if (t < finalPartSize)
|
||||
WritePayload(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterPayloadsAscendingPartial(gtid, finalPartSize, digits);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPairsDevicePartial(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
OffsetStruct offsets)
|
||||
{
|
||||
DigitStruct digits;
|
||||
const uint finalPartSize = e_numKeys - partIndex * PART_SIZE;
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterPairsKeyPhaseAscendingPartial(gtid, finalPartSize, digits);
|
||||
#else
|
||||
ScatterPairsKeyPhaseDescendingPartial(gtid, finalPartSize, digits);
|
||||
#endif
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
KeyStruct payloads;
|
||||
if (waveSize >= 16)
|
||||
LoadPayloadsPartialWGE16(gtid, waveSize, partIndex, payloads);
|
||||
else
|
||||
LoadPayloadsPartialWLT16(gtid, waveSize, partIndex, SerialIterations(waveSize), payloads);
|
||||
ScatterPayloadsShared(offsets, payloads);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterPayloadsAscendingPartial(gtid, finalPartSize, digits);
|
||||
#else
|
||||
ScatterPayloadsDescendingPartial(gtid, finalPartSize, digits);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ScatterDevicePartial(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
OffsetStruct offsets)
|
||||
{
|
||||
#if defined(SORT_PAIRS)
|
||||
ScatterPairsDevicePartial(
|
||||
gtid,
|
||||
waveSize,
|
||||
partIndex,
|
||||
offsets);
|
||||
#else
|
||||
ScatterKeysOnlyDevicePartial(gtid, partIndex);
|
||||
#endif
|
||||
}
|
||||
2193
MVS/3DGS-D3D12/src/App.cpp
Normal file
2193
MVS/3DGS-D3D12/src/App.cpp
Normal file
File diff suppressed because it is too large
Load Diff
617
MVS/3DGS-D3D12/src/GaussianPlyLoader.cpp
Normal file
617
MVS/3DGS-D3D12/src/GaussianPlyLoader.cpp
Normal file
@@ -0,0 +1,617 @@
|
||||
#include "XC3DGSD3D12/GaussianPlyLoader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace XC3DGSD3D12 {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kSHC0 = 0.2820948f;
|
||||
|
||||
enum class PlyPropertyType {
|
||||
None,
|
||||
Float32,
|
||||
Float64,
|
||||
UInt8,
|
||||
};
|
||||
|
||||
struct PlyProperty {
|
||||
std::string name;
|
||||
PlyPropertyType type = PlyPropertyType::None;
|
||||
uint32_t offset = 0;
|
||||
uint32_t size = 0;
|
||||
};
|
||||
|
||||
struct PlyHeader {
|
||||
uint32_t vertexCount = 0;
|
||||
uint32_t vertexStride = 0;
|
||||
std::vector<PlyProperty> properties;
|
||||
};
|
||||
|
||||
struct Float4 {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
float w = 0.0f;
|
||||
};
|
||||
|
||||
struct RawGaussianSplat {
|
||||
Float3 position = {};
|
||||
Float3 dc0 = {};
|
||||
std::array<Float3, GaussianSplatRuntimeData::kShCoefficientCount> sh = {};
|
||||
float opacity = 0.0f;
|
||||
Float3 scale = {};
|
||||
Float4 rotation = {};
|
||||
};
|
||||
|
||||
struct GaussianPlyPropertyLayout {
|
||||
const PlyProperty* position[3] = {};
|
||||
const PlyProperty* dc0[3] = {};
|
||||
const PlyProperty* opacity = nullptr;
|
||||
const PlyProperty* scale[3] = {};
|
||||
const PlyProperty* rotation[4] = {};
|
||||
std::array<const PlyProperty*, GaussianSplatRuntimeData::kShCoefficientCount * 3> sh = {};
|
||||
};
|
||||
|
||||
std::string TrimTrailingCarriageReturn(std::string line) {
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
uint32_t PropertyTypeSize(PlyPropertyType type) {
|
||||
switch (type) {
|
||||
case PlyPropertyType::Float32:
|
||||
return 4;
|
||||
case PlyPropertyType::Float64:
|
||||
return 8;
|
||||
case PlyPropertyType::UInt8:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool ParsePropertyType(const std::string& token, PlyPropertyType& outType) {
|
||||
if (token == "float") {
|
||||
outType = PlyPropertyType::Float32;
|
||||
return true;
|
||||
}
|
||||
if (token == "double") {
|
||||
outType = PlyPropertyType::Float64;
|
||||
return true;
|
||||
}
|
||||
if (token == "uchar") {
|
||||
outType = PlyPropertyType::UInt8;
|
||||
return true;
|
||||
}
|
||||
|
||||
outType = PlyPropertyType::None;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParsePlyHeader(std::ifstream& input, PlyHeader& outHeader, std::string& outErrorMessage) {
|
||||
std::string line;
|
||||
if (!std::getline(input, line)) {
|
||||
outErrorMessage = "Failed to read PLY magic line.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TrimTrailingCarriageReturn(line) != "ply") {
|
||||
outErrorMessage = "Input file is not a valid PLY file.";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sawFormat = false;
|
||||
std::string currentElement;
|
||||
|
||||
while (std::getline(input, line)) {
|
||||
line = TrimTrailingCarriageReturn(line);
|
||||
if (line == "end_header") {
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::istringstream stream(line);
|
||||
std::string token;
|
||||
stream >> token;
|
||||
|
||||
if (token == "comment") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "format") {
|
||||
std::string formatName;
|
||||
std::string version;
|
||||
stream >> formatName >> version;
|
||||
if (formatName != "binary_little_endian") {
|
||||
outErrorMessage = "Only binary_little_endian PLY files are supported.";
|
||||
return false;
|
||||
}
|
||||
sawFormat = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "element") {
|
||||
stream >> currentElement;
|
||||
if (currentElement == "vertex") {
|
||||
stream >> outHeader.vertexCount;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "property" && currentElement == "vertex") {
|
||||
std::string typeToken;
|
||||
std::string name;
|
||||
stream >> typeToken >> name;
|
||||
|
||||
PlyPropertyType propertyType = PlyPropertyType::None;
|
||||
if (!ParsePropertyType(typeToken, propertyType)) {
|
||||
outErrorMessage = "Unsupported PLY vertex property type: " + typeToken;
|
||||
return false;
|
||||
}
|
||||
|
||||
PlyProperty property;
|
||||
property.name = name;
|
||||
property.type = propertyType;
|
||||
property.offset = outHeader.vertexStride;
|
||||
property.size = PropertyTypeSize(propertyType);
|
||||
outHeader.vertexStride += property.size;
|
||||
outHeader.properties.push_back(property);
|
||||
}
|
||||
}
|
||||
|
||||
if (!sawFormat) {
|
||||
outErrorMessage = "PLY header is missing a valid format declaration.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outHeader.vertexCount == 0) {
|
||||
outErrorMessage = "PLY file does not contain any vertex data.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outHeader.vertexStride == 0 || outHeader.properties.empty()) {
|
||||
outErrorMessage = "PLY vertex layout is empty.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadPropertyAsFloat(
|
||||
const std::byte* vertexBytes,
|
||||
const PlyProperty& property,
|
||||
float& outValue) {
|
||||
const std::byte* propertyPtr = vertexBytes + property.offset;
|
||||
switch (property.type) {
|
||||
case PlyPropertyType::Float32: {
|
||||
std::memcpy(&outValue, propertyPtr, sizeof(float));
|
||||
return true;
|
||||
}
|
||||
case PlyPropertyType::Float64: {
|
||||
double value = 0.0;
|
||||
std::memcpy(&value, propertyPtr, sizeof(double));
|
||||
outValue = static_cast<float>(value);
|
||||
return true;
|
||||
}
|
||||
case PlyPropertyType::UInt8: {
|
||||
uint8_t value = 0;
|
||||
std::memcpy(&value, propertyPtr, sizeof(uint8_t));
|
||||
outValue = static_cast<float>(value);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool BuildPropertyMap(
|
||||
const PlyHeader& header,
|
||||
std::unordered_map<std::string_view, const PlyProperty*>& outMap,
|
||||
std::string& outErrorMessage) {
|
||||
outMap.clear();
|
||||
outMap.reserve(header.properties.size());
|
||||
|
||||
for (const PlyProperty& property : header.properties) {
|
||||
const auto [it, inserted] = outMap.emplace(property.name, &property);
|
||||
if (!inserted) {
|
||||
outErrorMessage = "Duplicate PLY vertex property found: " + property.name;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RequireProperty(
|
||||
const std::unordered_map<std::string_view, const PlyProperty*>& propertyMap,
|
||||
std::string_view name,
|
||||
const PlyProperty*& outProperty,
|
||||
std::string& outErrorMessage) {
|
||||
const auto iterator = propertyMap.find(name);
|
||||
if (iterator == propertyMap.end()) {
|
||||
outErrorMessage = "Missing required PLY property: " + std::string(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
outProperty = iterator->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuildGaussianPlyPropertyLayout(
|
||||
const std::unordered_map<std::string_view, const PlyProperty*>& propertyMap,
|
||||
GaussianPlyPropertyLayout& outLayout,
|
||||
std::string& outErrorMessage) {
|
||||
outLayout = {};
|
||||
|
||||
if (!RequireProperty(propertyMap, "x", outLayout.position[0], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "y", outLayout.position[1], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "z", outLayout.position[2], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "f_dc_0", outLayout.dc0[0], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "f_dc_1", outLayout.dc0[1], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "f_dc_2", outLayout.dc0[2], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "opacity", outLayout.opacity, outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "scale_0", outLayout.scale[0], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "scale_1", outLayout.scale[1], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "scale_2", outLayout.scale[2], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "rot_0", outLayout.rotation[0], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "rot_1", outLayout.rotation[1], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "rot_2", outLayout.rotation[2], outErrorMessage) ||
|
||||
!RequireProperty(propertyMap, "rot_3", outLayout.rotation[3], outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t index = 0; index < outLayout.sh.size(); ++index) {
|
||||
const std::string propertyName = "f_rest_" + std::to_string(index);
|
||||
if (!RequireProperty(propertyMap, propertyName, outLayout.sh[index], outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Float3 Min(const Float3& a, const Float3& b) {
|
||||
return {
|
||||
std::min(a.x, b.x),
|
||||
std::min(a.y, b.y),
|
||||
std::min(a.z, b.z),
|
||||
};
|
||||
}
|
||||
|
||||
Float3 Max(const Float3& a, const Float3& b) {
|
||||
return {
|
||||
std::max(a.x, b.x),
|
||||
std::max(a.y, b.y),
|
||||
std::max(a.z, b.z),
|
||||
};
|
||||
}
|
||||
|
||||
float Dot(const Float4& a, const Float4& b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
||||
}
|
||||
|
||||
Float4 NormalizeSwizzleRotation(const Float4& wxyz) {
|
||||
const float lengthSquared = Dot(wxyz, wxyz);
|
||||
if (lengthSquared <= std::numeric_limits<float>::epsilon()) {
|
||||
return { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
}
|
||||
|
||||
const float inverseLength = 1.0f / std::sqrt(lengthSquared);
|
||||
return {
|
||||
wxyz.y * inverseLength,
|
||||
wxyz.z * inverseLength,
|
||||
wxyz.w * inverseLength,
|
||||
wxyz.x * inverseLength,
|
||||
};
|
||||
}
|
||||
|
||||
Float4 PackSmallest3Rotation(Float4 rotation) {
|
||||
const Float4 absoluteRotation = {
|
||||
std::fabs(rotation.x),
|
||||
std::fabs(rotation.y),
|
||||
std::fabs(rotation.z),
|
||||
std::fabs(rotation.w),
|
||||
};
|
||||
|
||||
int largestIndex = 0;
|
||||
float largestValue = absoluteRotation.x;
|
||||
if (absoluteRotation.y > largestValue) {
|
||||
largestIndex = 1;
|
||||
largestValue = absoluteRotation.y;
|
||||
}
|
||||
if (absoluteRotation.z > largestValue) {
|
||||
largestIndex = 2;
|
||||
largestValue = absoluteRotation.z;
|
||||
}
|
||||
if (absoluteRotation.w > largestValue) {
|
||||
largestIndex = 3;
|
||||
largestValue = absoluteRotation.w;
|
||||
}
|
||||
|
||||
if (largestIndex == 0) {
|
||||
rotation = { rotation.y, rotation.z, rotation.w, rotation.x };
|
||||
} else if (largestIndex == 1) {
|
||||
rotation = { rotation.x, rotation.z, rotation.w, rotation.y };
|
||||
} else if (largestIndex == 2) {
|
||||
rotation = { rotation.x, rotation.y, rotation.w, rotation.z };
|
||||
}
|
||||
|
||||
const float sign = rotation.w >= 0.0f ? 1.0f : -1.0f;
|
||||
const float invSqrt2 = std::sqrt(2.0f) * 0.5f;
|
||||
const Float3 encoded = {
|
||||
(rotation.x * sign * std::sqrt(2.0f)) * 0.5f + 0.5f,
|
||||
(rotation.y * sign * std::sqrt(2.0f)) * 0.5f + 0.5f,
|
||||
(rotation.z * sign * std::sqrt(2.0f)) * 0.5f + 0.5f,
|
||||
};
|
||||
|
||||
(void)invSqrt2;
|
||||
return { encoded.x, encoded.y, encoded.z, static_cast<float>(largestIndex) / 3.0f };
|
||||
}
|
||||
|
||||
uint32_t EncodeQuatToNorm10(const Float4& packedRotation) {
|
||||
const auto saturate = [](float value) {
|
||||
return std::clamp(value, 0.0f, 1.0f);
|
||||
};
|
||||
|
||||
const uint32_t x = static_cast<uint32_t>(saturate(packedRotation.x) * 1023.5f);
|
||||
const uint32_t y = static_cast<uint32_t>(saturate(packedRotation.y) * 1023.5f);
|
||||
const uint32_t z = static_cast<uint32_t>(saturate(packedRotation.z) * 1023.5f);
|
||||
const uint32_t w = static_cast<uint32_t>(saturate(packedRotation.w) * 3.5f);
|
||||
return x | (y << 10) | (z << 20) | (w << 30);
|
||||
}
|
||||
|
||||
Float3 LinearScale(const Float3& logarithmicScale) {
|
||||
return {
|
||||
std::fabs(std::exp(logarithmicScale.x)),
|
||||
std::fabs(std::exp(logarithmicScale.y)),
|
||||
std::fabs(std::exp(logarithmicScale.z)),
|
||||
};
|
||||
}
|
||||
|
||||
Float3 SH0ToColor(const Float3& dc0) {
|
||||
return {
|
||||
dc0.x * kSHC0 + 0.5f,
|
||||
dc0.y * kSHC0 + 0.5f,
|
||||
dc0.z * kSHC0 + 0.5f,
|
||||
};
|
||||
}
|
||||
|
||||
float Sigmoid(float value) {
|
||||
return 1.0f / (1.0f + std::exp(-value));
|
||||
}
|
||||
|
||||
std::array<uint32_t, 2> DecodeMorton2D16x16(uint32_t value) {
|
||||
value = (value & 0xFFu) | ((value & 0xFEu) << 7u);
|
||||
value &= 0x5555u;
|
||||
value = (value ^ (value >> 1u)) & 0x3333u;
|
||||
value = (value ^ (value >> 2u)) & 0x0F0Fu;
|
||||
return { value & 0xFu, value >> 8u };
|
||||
}
|
||||
|
||||
uint32_t SplatIndexToTextureIndex(uint32_t index) {
|
||||
const std::array<uint32_t, 2> morton = DecodeMorton2D16x16(index);
|
||||
const uint32_t widthInBlocks = GaussianSplatRuntimeData::kColorTextureWidth / 16u;
|
||||
index >>= 8u;
|
||||
const uint32_t x = (index % widthInBlocks) * 16u + morton[0];
|
||||
const uint32_t y = (index / widthInBlocks) * 16u + morton[1];
|
||||
return y * GaussianSplatRuntimeData::kColorTextureWidth + x;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void WriteValue(std::vector<std::byte>& bytes, size_t offset, const T& value) {
|
||||
std::memcpy(bytes.data() + offset, &value, sizeof(T));
|
||||
}
|
||||
|
||||
void WriteFloat3(std::vector<std::byte>& bytes, size_t offset, const Float3& value) {
|
||||
WriteValue(bytes, offset + 0, value.x);
|
||||
WriteValue(bytes, offset + 4, value.y);
|
||||
WriteValue(bytes, offset + 8, value.z);
|
||||
}
|
||||
|
||||
void WriteFloat4(std::vector<std::byte>& bytes, size_t offset, float x, float y, float z, float w) {
|
||||
WriteValue(bytes, offset + 0, x);
|
||||
WriteValue(bytes, offset + 4, y);
|
||||
WriteValue(bytes, offset + 8, z);
|
||||
WriteValue(bytes, offset + 12, w);
|
||||
}
|
||||
|
||||
bool ReadGaussianSplat(
|
||||
const std::byte* vertexBytes,
|
||||
const GaussianPlyPropertyLayout& propertyLayout,
|
||||
RawGaussianSplat& outSplat,
|
||||
std::string& outErrorMessage) {
|
||||
auto readFloat = [&](const PlyProperty* property, float& outValue) -> bool {
|
||||
if (property == nullptr) {
|
||||
outErrorMessage = "Gaussian PLY property layout is incomplete.";
|
||||
return false;
|
||||
}
|
||||
return ReadPropertyAsFloat(vertexBytes, *property, outValue);
|
||||
};
|
||||
|
||||
if (!readFloat(propertyLayout.position[0], outSplat.position.x) ||
|
||||
!readFloat(propertyLayout.position[1], outSplat.position.y) ||
|
||||
!readFloat(propertyLayout.position[2], outSplat.position.z) ||
|
||||
!readFloat(propertyLayout.dc0[0], outSplat.dc0.x) ||
|
||||
!readFloat(propertyLayout.dc0[1], outSplat.dc0.y) ||
|
||||
!readFloat(propertyLayout.dc0[2], outSplat.dc0.z) ||
|
||||
!readFloat(propertyLayout.opacity, outSplat.opacity) ||
|
||||
!readFloat(propertyLayout.scale[0], outSplat.scale.x) ||
|
||||
!readFloat(propertyLayout.scale[1], outSplat.scale.y) ||
|
||||
!readFloat(propertyLayout.scale[2], outSplat.scale.z) ||
|
||||
!readFloat(propertyLayout.rotation[0], outSplat.rotation.x) ||
|
||||
!readFloat(propertyLayout.rotation[1], outSplat.rotation.y) ||
|
||||
!readFloat(propertyLayout.rotation[2], outSplat.rotation.z) ||
|
||||
!readFloat(propertyLayout.rotation[3], outSplat.rotation.w)) {
|
||||
if (outErrorMessage.empty()) {
|
||||
outErrorMessage = "Failed to read required Gaussian splat PLY properties.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<float, GaussianSplatRuntimeData::kShCoefficientCount * 3> shRaw = {};
|
||||
for (uint32_t index = 0; index < shRaw.size(); ++index) {
|
||||
if (!readFloat(propertyLayout.sh[index], shRaw[index])) {
|
||||
if (outErrorMessage.empty()) {
|
||||
outErrorMessage = "Failed to read SH rest coefficients from PLY.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t coefficientIndex = 0; coefficientIndex < GaussianSplatRuntimeData::kShCoefficientCount; ++coefficientIndex) {
|
||||
outSplat.sh[coefficientIndex] = {
|
||||
shRaw[coefficientIndex + 0],
|
||||
shRaw[coefficientIndex + GaussianSplatRuntimeData::kShCoefficientCount],
|
||||
shRaw[coefficientIndex + GaussianSplatRuntimeData::kShCoefficientCount * 2],
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LinearizeGaussianSplat(RawGaussianSplat& splat) {
|
||||
const Float4 normalizedQuaternion = NormalizeSwizzleRotation(splat.rotation);
|
||||
const Float4 packedQuaternion = PackSmallest3Rotation(normalizedQuaternion);
|
||||
splat.rotation = packedQuaternion;
|
||||
splat.scale = LinearScale(splat.scale);
|
||||
splat.dc0 = SH0ToColor(splat.dc0);
|
||||
splat.opacity = Sigmoid(splat.opacity);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool LoadGaussianSceneFromPly(
|
||||
const std::filesystem::path& filePath,
|
||||
GaussianSplatRuntimeData& outData,
|
||||
std::string& outErrorMessage) {
|
||||
outData = {};
|
||||
outErrorMessage.clear();
|
||||
|
||||
std::ifstream input(filePath, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
outErrorMessage = "Failed to open PLY file: " + filePath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
PlyHeader header;
|
||||
if (!ParsePlyHeader(input, header, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string_view, const PlyProperty*> propertyMap;
|
||||
if (!BuildPropertyMap(header, propertyMap, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GaussianPlyPropertyLayout propertyLayout;
|
||||
if (!BuildGaussianPlyPropertyLayout(propertyMap, propertyLayout, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outData.splatCount = header.vertexCount;
|
||||
outData.colorTextureWidth = GaussianSplatRuntimeData::kColorTextureWidth;
|
||||
outData.colorTextureHeight =
|
||||
std::max<uint32_t>(1u, (header.vertexCount + outData.colorTextureWidth - 1u) / outData.colorTextureWidth);
|
||||
outData.colorTextureHeight = (outData.colorTextureHeight + 15u) / 16u * 16u;
|
||||
|
||||
outData.positionData.resize(static_cast<size_t>(header.vertexCount) * GaussianSplatRuntimeData::kPositionStride);
|
||||
outData.otherData.resize(static_cast<size_t>(header.vertexCount) * GaussianSplatRuntimeData::kOtherStride);
|
||||
outData.colorData.resize(
|
||||
static_cast<size_t>(outData.colorTextureWidth) *
|
||||
static_cast<size_t>(outData.colorTextureHeight) *
|
||||
GaussianSplatRuntimeData::kColorStride);
|
||||
outData.shData.resize(static_cast<size_t>(header.vertexCount) * GaussianSplatRuntimeData::kShStride);
|
||||
|
||||
outData.boundsMin = {
|
||||
std::numeric_limits<float>::infinity(),
|
||||
std::numeric_limits<float>::infinity(),
|
||||
std::numeric_limits<float>::infinity(),
|
||||
};
|
||||
outData.boundsMax = {
|
||||
-std::numeric_limits<float>::infinity(),
|
||||
-std::numeric_limits<float>::infinity(),
|
||||
-std::numeric_limits<float>::infinity(),
|
||||
};
|
||||
|
||||
std::vector<std::byte> vertexBytes(header.vertexStride);
|
||||
for (uint32_t splatIndex = 0; splatIndex < header.vertexCount; ++splatIndex) {
|
||||
input.read(reinterpret_cast<char*>(vertexBytes.data()), static_cast<std::streamsize>(vertexBytes.size()));
|
||||
if (input.gcount() != static_cast<std::streamsize>(vertexBytes.size())) {
|
||||
outErrorMessage =
|
||||
"Unexpected end of file while reading Gaussian splat vertex " + std::to_string(splatIndex) + ".";
|
||||
return false;
|
||||
}
|
||||
|
||||
RawGaussianSplat splat;
|
||||
if (!ReadGaussianSplat(vertexBytes.data(), propertyLayout, splat, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LinearizeGaussianSplat(splat);
|
||||
|
||||
outData.boundsMin = Min(outData.boundsMin, splat.position);
|
||||
outData.boundsMax = Max(outData.boundsMax, splat.position);
|
||||
|
||||
const size_t positionOffset = static_cast<size_t>(splatIndex) * GaussianSplatRuntimeData::kPositionStride;
|
||||
WriteFloat3(outData.positionData, positionOffset, splat.position);
|
||||
|
||||
const size_t otherOffset = static_cast<size_t>(splatIndex) * GaussianSplatRuntimeData::kOtherStride;
|
||||
const uint32_t packedRotation = EncodeQuatToNorm10(splat.rotation);
|
||||
WriteValue(outData.otherData, otherOffset, packedRotation);
|
||||
WriteFloat3(outData.otherData, otherOffset + sizeof(uint32_t), splat.scale);
|
||||
|
||||
const size_t shOffset = static_cast<size_t>(splatIndex) * GaussianSplatRuntimeData::kShStride;
|
||||
for (uint32_t coefficientIndex = 0; coefficientIndex < GaussianSplatRuntimeData::kShCoefficientCount; ++coefficientIndex) {
|
||||
const size_t coefficientOffset = shOffset + static_cast<size_t>(coefficientIndex) * sizeof(float) * 3u;
|
||||
WriteFloat3(outData.shData, coefficientOffset, splat.sh[coefficientIndex]);
|
||||
}
|
||||
|
||||
const uint32_t textureIndex = SplatIndexToTextureIndex(splatIndex);
|
||||
const size_t colorOffset = static_cast<size_t>(textureIndex) * GaussianSplatRuntimeData::kColorStride;
|
||||
WriteFloat4(outData.colorData, colorOffset, splat.dc0.x, splat.dc0.y, splat.dc0.z, splat.opacity);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteGaussianSceneSummary(
|
||||
const std::filesystem::path& filePath,
|
||||
const GaussianSplatRuntimeData& data,
|
||||
std::string& outErrorMessage) {
|
||||
outErrorMessage.clear();
|
||||
|
||||
std::ofstream output(filePath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
outErrorMessage = "Failed to open summary output file: " + filePath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
output << "splat_count=" << data.splatCount << '\n';
|
||||
output << "color_texture_width=" << data.colorTextureWidth << '\n';
|
||||
output << "color_texture_height=" << data.colorTextureHeight << '\n';
|
||||
output << "bounds_min=" << data.boundsMin.x << "," << data.boundsMin.y << "," << data.boundsMin.z << '\n';
|
||||
output << "bounds_max=" << data.boundsMax.x << "," << data.boundsMax.y << "," << data.boundsMax.z << '\n';
|
||||
output << "position_bytes=" << data.positionData.size() << '\n';
|
||||
output << "other_bytes=" << data.otherData.size() << '\n';
|
||||
output << "color_bytes=" << data.colorData.size() << '\n';
|
||||
output << "sh_bytes=" << data.shData.size() << '\n';
|
||||
|
||||
return output.good();
|
||||
}
|
||||
|
||||
} // namespace XC3DGSD3D12
|
||||
42
MVS/3DGS-D3D12/src/main.cpp
Normal file
42
MVS/3DGS-D3D12/src/main.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "XC3DGSD3D12/App.h"
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int showCommand) {
|
||||
XC3DGSD3D12::App app;
|
||||
|
||||
int argumentCount = 0;
|
||||
LPWSTR* arguments = CommandLineToArgvW(GetCommandLineW(), &argumentCount);
|
||||
for (int index = 1; index + 1 < argumentCount; ++index) {
|
||||
if (std::wstring(arguments[index]) == L"--frame-limit") {
|
||||
app.SetFrameLimit(static_cast<unsigned int>(_wtoi(arguments[index + 1])));
|
||||
++index;
|
||||
} else if (std::wstring(arguments[index]) == L"--scene") {
|
||||
app.SetGaussianScenePath(arguments[index + 1]);
|
||||
++index;
|
||||
} else if (std::wstring(arguments[index]) == L"--summary-file") {
|
||||
app.SetSummaryPath(arguments[index + 1]);
|
||||
++index;
|
||||
} else if (std::wstring(arguments[index]) == L"--screenshot-file") {
|
||||
app.SetScreenshotPath(arguments[index + 1]);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
if (arguments != nullptr) {
|
||||
LocalFree(arguments);
|
||||
}
|
||||
|
||||
if (!app.Initialize(instance, showCommand)) {
|
||||
const std::wstring message =
|
||||
app.GetLastErrorMessage().empty()
|
||||
? L"Failed to initialize XC 3DGS D3D12 MVS."
|
||||
: app.GetLastErrorMessage();
|
||||
MessageBoxW(nullptr, message.c_str(), L"Initialization Error", MB_OK | MB_ICONERROR);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return app.Run();
|
||||
}
|
||||
8
MVS/3DGS-Unity/Editor.meta
Normal file
8
MVS/3DGS-Unity/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07d8a5410ba18f64e9efe04f3a023cfa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
35
MVS/3DGS-Unity/Editor/GaussianMoveTool.cs
Normal file
35
MVS/3DGS-Unity/Editor/GaussianMoveTool.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using GaussianSplatting.Runtime;
|
||||
using UnityEditor;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GaussianSplatting.Editor
|
||||
{
|
||||
[EditorTool("Gaussian Move Tool", typeof(GaussianSplatRenderer), typeof(GaussianToolContext))]
|
||||
class GaussianMoveTool : GaussianTool
|
||||
{
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
var gs = GetRenderer();
|
||||
if (!gs || !CanBeEdited() || !HasSelection())
|
||||
return;
|
||||
var tr = gs.transform;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var selCenterLocal = GetSelectionCenterLocal();
|
||||
var selCenterWorld = tr.TransformPoint(selCenterLocal);
|
||||
var newPosWorld = Handles.DoPositionHandle(selCenterWorld, Tools.handleRotation);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
var newPosLocal = tr.InverseTransformPoint(newPosWorld);
|
||||
var wasModified = gs.editModified;
|
||||
gs.EditTranslateSelection(newPosLocal - selCenterLocal);
|
||||
if (!wasModified)
|
||||
GaussianSplatRendererEditor.RepaintAll();
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Editor/GaussianMoveTool.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/GaussianMoveTool.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c9f40b54eb504648b2a0beadabbcc8d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
70
MVS/3DGS-Unity/Editor/GaussianRotateTool.cs
Normal file
70
MVS/3DGS-Unity/Editor/GaussianRotateTool.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using GaussianSplatting.Runtime;
|
||||
using UnityEditor;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GaussianSplatting.Editor
|
||||
{
|
||||
/* not working correctly yet
|
||||
[EditorTool("Gaussian Rotate Tool", typeof(GaussianSplatRenderer), typeof(GaussianToolContext))]
|
||||
class GaussianRotateTool : GaussianTool
|
||||
{
|
||||
Quaternion m_CurrentRotation = Quaternion.identity;
|
||||
Vector3 m_FrozenSelCenterLocal = Vector3.zero;
|
||||
bool m_FreezePivot = false;
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
m_FreezePivot = false;
|
||||
}
|
||||
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
var gs = GetRenderer();
|
||||
if (!gs || !CanBeEdited() || !HasSelection())
|
||||
return;
|
||||
var tr = gs.transform;
|
||||
var evt = Event.current;
|
||||
|
||||
var selCenterLocal = GetSelectionCenterLocal();
|
||||
if (evt.type == EventType.MouseDown)
|
||||
{
|
||||
gs.EditStorePosMouseDown();
|
||||
gs.EditStoreOtherMouseDown();
|
||||
m_FrozenSelCenterLocal = selCenterLocal;
|
||||
m_FreezePivot = true;
|
||||
}
|
||||
if (evt.type == EventType.MouseUp)
|
||||
{
|
||||
m_CurrentRotation = Quaternion.identity;
|
||||
m_FreezePivot = false;
|
||||
}
|
||||
|
||||
if (m_FreezePivot)
|
||||
selCenterLocal = m_FrozenSelCenterLocal;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var selCenterWorld = tr.TransformPoint(selCenterLocal);
|
||||
var newRotation = Handles.DoRotationHandle(m_CurrentRotation, selCenterWorld);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Matrix4x4 localToWorld = gs.transform.localToWorldMatrix;
|
||||
Matrix4x4 worldToLocal = gs.transform.worldToLocalMatrix;
|
||||
var wasModified = gs.editModified;
|
||||
var rotToApply = newRotation;
|
||||
gs.EditRotateSelection(selCenterLocal, localToWorld, worldToLocal, rotToApply);
|
||||
m_CurrentRotation = newRotation;
|
||||
if (!wasModified)
|
||||
GaussianSplatRendererEditor.RepaintAll();
|
||||
|
||||
if(GUIUtility.hotControl == 0)
|
||||
{
|
||||
m_CurrentRotation = Tools.handleRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
3
MVS/3DGS-Unity/Editor/GaussianRotateTool.cs.meta
Normal file
3
MVS/3DGS-Unity/Editor/GaussianRotateTool.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5128238188a44c86914a22a862195242
|
||||
timeCreated: 1697805149
|
||||
68
MVS/3DGS-Unity/Editor/GaussianScaleTool.cs
Normal file
68
MVS/3DGS-Unity/Editor/GaussianScaleTool.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using GaussianSplatting.Runtime;
|
||||
using UnityEditor;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GaussianSplatting.Editor
|
||||
{
|
||||
/* // not working correctly yet when the GS itself has scale
|
||||
[EditorTool("Gaussian Scale Tool", typeof(GaussianSplatRenderer), typeof(GaussianToolContext))]
|
||||
class GaussianScaleTool : GaussianTool
|
||||
{
|
||||
Vector3 m_CurrentScale = Vector3.one;
|
||||
Vector3 m_FrozenSelCenterLocal = Vector3.zero;
|
||||
bool m_FreezePivot = false;
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
m_FreezePivot = false;
|
||||
}
|
||||
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
var gs = GetRenderer();
|
||||
if (!gs || !CanBeEdited() || !HasSelection())
|
||||
return;
|
||||
var tr = gs.transform;
|
||||
var evt = Event.current;
|
||||
|
||||
var selCenterLocal = GetSelectionCenterLocal();
|
||||
if (evt.type == EventType.MouseDown)
|
||||
{
|
||||
gs.EditStorePosMouseDown();
|
||||
m_FrozenSelCenterLocal = selCenterLocal;
|
||||
m_FreezePivot = true;
|
||||
}
|
||||
if (evt.type == EventType.MouseUp)
|
||||
{
|
||||
m_CurrentScale = Vector3.one;
|
||||
m_FreezePivot = false;
|
||||
}
|
||||
|
||||
if (m_FreezePivot)
|
||||
selCenterLocal = m_FrozenSelCenterLocal;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var selCenterWorld = tr.TransformPoint(selCenterLocal);
|
||||
m_CurrentScale = Handles.DoScaleHandle(m_CurrentScale, selCenterWorld, Tools.handleRotation, HandleUtility.GetHandleSize(selCenterWorld));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Matrix4x4 localToWorld = Matrix4x4.identity;
|
||||
Matrix4x4 worldToLocal = Matrix4x4.identity;
|
||||
if (Tools.pivotRotation == PivotRotation.Global)
|
||||
{
|
||||
localToWorld = gs.transform.localToWorldMatrix;
|
||||
worldToLocal = gs.transform.worldToLocalMatrix;
|
||||
}
|
||||
var wasModified = gs.editModified;
|
||||
gs.EditScaleSelection(selCenterLocal, localToWorld, worldToLocal, m_CurrentScale);
|
||||
if (!wasModified)
|
||||
GaussianSplatRendererEditor.RepaintAll();
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
3
MVS/3DGS-Unity/Editor/GaussianScaleTool.cs.meta
Normal file
3
MVS/3DGS-Unity/Editor/GaussianScaleTool.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dbf3d17a31b942b28f5d8c187adb8fdf
|
||||
timeCreated: 1697732813
|
||||
1208
MVS/3DGS-Unity/Editor/GaussianSplatAssetCreator.cs
Normal file
1208
MVS/3DGS-Unity/Editor/GaussianSplatAssetCreator.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
MVS/3DGS-Unity/Editor/GaussianSplatAssetCreator.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/GaussianSplatAssetCreator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 635bd950b8a74c84f870d5c8f02c3974
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
71
MVS/3DGS-Unity/Editor/GaussianSplatAssetEditor.cs
Normal file
71
MVS/3DGS-Unity/Editor/GaussianSplatAssetEditor.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using GaussianSplatting.Runtime;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GaussianSplatting.Editor
|
||||
{
|
||||
[CustomEditor(typeof(GaussianSplatAsset))]
|
||||
[CanEditMultipleObjects]
|
||||
public class GaussianSplatAssetEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var gs = target as GaussianSplatAsset;
|
||||
if (!gs)
|
||||
return;
|
||||
|
||||
using var _ = new EditorGUI.DisabledScope(true);
|
||||
|
||||
if (targets.Length == 1)
|
||||
SingleAssetGUI(gs);
|
||||
else
|
||||
{
|
||||
int totalCount = 0;
|
||||
foreach (var tgt in targets)
|
||||
{
|
||||
var gss = tgt as GaussianSplatAsset;
|
||||
if (gss)
|
||||
{
|
||||
totalCount += gss.splatCount;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.TextField("Total Splats", $"{totalCount:N0}");
|
||||
}
|
||||
}
|
||||
|
||||
static void SingleAssetGUI(GaussianSplatAsset gs)
|
||||
{
|
||||
var splatCount = gs.splatCount;
|
||||
EditorGUILayout.TextField("Splats", $"{splatCount:N0}");
|
||||
var prevBackColor = GUI.backgroundColor;
|
||||
if (gs.formatVersion != GaussianSplatAsset.kCurrentVersion)
|
||||
GUI.backgroundColor *= Color.red;
|
||||
EditorGUILayout.IntField("Version", gs.formatVersion);
|
||||
GUI.backgroundColor = prevBackColor;
|
||||
|
||||
long sizePos = gs.posData != null ? gs.posData.dataSize : 0;
|
||||
long sizeOther = gs.otherData != null ? gs.otherData.dataSize : 0;
|
||||
long sizeCol = gs.colorData != null ? gs.colorData.dataSize : 0;
|
||||
long sizeSH = GaussianSplatAsset.CalcSHDataSize(gs.splatCount, gs.shFormat);
|
||||
long sizeChunk = gs.chunkData != null ? gs.chunkData.dataSize : 0;
|
||||
|
||||
EditorGUILayout.TextField("Memory", EditorUtility.FormatBytes(sizePos + sizeOther + sizeSH + sizeCol + sizeChunk));
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.TextField("Positions", $"{EditorUtility.FormatBytes(sizePos)} ({gs.posFormat})");
|
||||
EditorGUILayout.TextField("Other", $"{EditorUtility.FormatBytes(sizeOther)} ({gs.scaleFormat})");
|
||||
EditorGUILayout.TextField("Base color", $"{EditorUtility.FormatBytes(sizeCol)} ({gs.colorFormat})");
|
||||
EditorGUILayout.TextField("SHs", $"{EditorUtility.FormatBytes(sizeSH)} ({gs.shFormat})");
|
||||
EditorGUILayout.TextField("Chunks",
|
||||
$"{EditorUtility.FormatBytes(sizeChunk)} ({UnsafeUtility.SizeOf<GaussianSplatAsset.ChunkInfo>()} B/chunk)");
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.Vector3Field("Bounds Min", gs.boundsMin);
|
||||
EditorGUILayout.Vector3Field("Bounds Max", gs.boundsMax);
|
||||
|
||||
EditorGUILayout.TextField("Data Hash", gs.dataHash.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Editor/GaussianSplatAssetEditor.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/GaussianSplatAssetEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75971a29a6deda14c9b1ff5f4ab2f2a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
444
MVS/3DGS-Unity/Editor/GaussianSplatRendererEditor.cs
Normal file
444
MVS/3DGS-Unity/Editor/GaussianSplatRendererEditor.cs
Normal file
@@ -0,0 +1,444 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using GaussianSplatting.Runtime;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Mathematics;
|
||||
using UnityEditor;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
using GaussianSplatRenderer = GaussianSplatting.Runtime.GaussianSplatRenderer;
|
||||
|
||||
namespace GaussianSplatting.Editor
|
||||
{
|
||||
[CustomEditor(typeof(GaussianSplatRenderer))]
|
||||
[CanEditMultipleObjects]
|
||||
public class GaussianSplatRendererEditor : UnityEditor.Editor
|
||||
{
|
||||
const string kPrefExportBake = "nesnausk.GaussianSplatting.ExportBakeTransform";
|
||||
|
||||
SerializedProperty m_PropAsset;
|
||||
SerializedProperty m_PropSplatScale;
|
||||
SerializedProperty m_PropOpacityScale;
|
||||
SerializedProperty m_PropSHOrder;
|
||||
SerializedProperty m_PropSHOnly;
|
||||
SerializedProperty m_PropSortNthFrame;
|
||||
SerializedProperty m_PropRenderMode;
|
||||
SerializedProperty m_PropPointDisplaySize;
|
||||
SerializedProperty m_PropCutouts;
|
||||
SerializedProperty m_PropShaderSplats;
|
||||
SerializedProperty m_PropShaderComposite;
|
||||
SerializedProperty m_PropShaderDebugPoints;
|
||||
SerializedProperty m_PropShaderDebugBoxes;
|
||||
SerializedProperty m_PropCSSplatUtilities;
|
||||
|
||||
bool m_ResourcesExpanded = false;
|
||||
int m_CameraIndex = 0;
|
||||
|
||||
bool m_ExportBakeTransform;
|
||||
|
||||
static int s_EditStatsUpdateCounter = 0;
|
||||
|
||||
static HashSet<GaussianSplatRendererEditor> s_AllEditors = new();
|
||||
|
||||
public static void BumpGUICounter()
|
||||
{
|
||||
++s_EditStatsUpdateCounter;
|
||||
}
|
||||
|
||||
public static void RepaintAll()
|
||||
{
|
||||
foreach (var e in s_AllEditors)
|
||||
e.Repaint();
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
m_ExportBakeTransform = EditorPrefs.GetBool(kPrefExportBake, false);
|
||||
|
||||
m_PropAsset = serializedObject.FindProperty("m_Asset");
|
||||
m_PropSplatScale = serializedObject.FindProperty("m_SplatScale");
|
||||
m_PropOpacityScale = serializedObject.FindProperty("m_OpacityScale");
|
||||
m_PropSHOrder = serializedObject.FindProperty("m_SHOrder");
|
||||
m_PropSHOnly = serializedObject.FindProperty("m_SHOnly");
|
||||
m_PropSortNthFrame = serializedObject.FindProperty("m_SortNthFrame");
|
||||
m_PropRenderMode = serializedObject.FindProperty("m_RenderMode");
|
||||
m_PropPointDisplaySize = serializedObject.FindProperty("m_PointDisplaySize");
|
||||
m_PropCutouts = serializedObject.FindProperty("m_Cutouts");
|
||||
m_PropShaderSplats = serializedObject.FindProperty("m_ShaderSplats");
|
||||
m_PropShaderComposite = serializedObject.FindProperty("m_ShaderComposite");
|
||||
m_PropShaderDebugPoints = serializedObject.FindProperty("m_ShaderDebugPoints");
|
||||
m_PropShaderDebugBoxes = serializedObject.FindProperty("m_ShaderDebugBoxes");
|
||||
m_PropCSSplatUtilities = serializedObject.FindProperty("m_CSSplatUtilities");
|
||||
|
||||
s_AllEditors.Add(this);
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
s_AllEditors.Remove(this);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var gs = target as GaussianSplatRenderer;
|
||||
if (!gs)
|
||||
return;
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
GUILayout.Label("Data Asset", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_PropAsset);
|
||||
|
||||
if (!gs.HasValidAsset)
|
||||
{
|
||||
var msg = gs.asset != null && gs.asset.formatVersion != GaussianSplatAsset.kCurrentVersion
|
||||
? "Gaussian Splat asset version is not compatible, please recreate the asset"
|
||||
: "Gaussian Splat asset is not assigned or is empty";
|
||||
EditorGUILayout.HelpBox(msg, MessageType.Error);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
GUILayout.Label("Render Options", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_PropSplatScale);
|
||||
EditorGUILayout.PropertyField(m_PropOpacityScale);
|
||||
EditorGUILayout.PropertyField(m_PropSHOrder);
|
||||
EditorGUILayout.PropertyField(m_PropSHOnly);
|
||||
EditorGUILayout.PropertyField(m_PropSortNthFrame);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
GUILayout.Label("Debugging Tweaks", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_PropRenderMode);
|
||||
if (m_PropRenderMode.intValue is (int)GaussianSplatRenderer.RenderMode.DebugPoints or (int)GaussianSplatRenderer.RenderMode.DebugPointIndices)
|
||||
EditorGUILayout.PropertyField(m_PropPointDisplaySize);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
m_ResourcesExpanded = EditorGUILayout.Foldout(m_ResourcesExpanded, "Resources", true, EditorStyles.foldoutHeader);
|
||||
if (m_ResourcesExpanded)
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_PropShaderSplats);
|
||||
EditorGUILayout.PropertyField(m_PropShaderComposite);
|
||||
EditorGUILayout.PropertyField(m_PropShaderDebugPoints);
|
||||
EditorGUILayout.PropertyField(m_PropShaderDebugBoxes);
|
||||
EditorGUILayout.PropertyField(m_PropCSSplatUtilities);
|
||||
}
|
||||
bool validAndEnabled = gs && gs.enabled && gs.gameObject.activeInHierarchy && gs.HasValidAsset;
|
||||
if (validAndEnabled && !gs.HasValidRenderSetup)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Shader resources are not set up", MessageType.Error);
|
||||
validAndEnabled = false;
|
||||
}
|
||||
|
||||
if (validAndEnabled && targets.Length == 1)
|
||||
{
|
||||
EditCameras(gs);
|
||||
EditGUI(gs);
|
||||
}
|
||||
if (validAndEnabled && targets.Length > 1)
|
||||
{
|
||||
MultiEditGUI();
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void EditCameras(GaussianSplatRenderer gs)
|
||||
{
|
||||
var asset = gs.asset;
|
||||
var cameras = asset.cameras;
|
||||
if (cameras != null && cameras.Length != 0)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
GUILayout.Label("Cameras", EditorStyles.boldLabel);
|
||||
var camIndex = EditorGUILayout.IntSlider("Camera", m_CameraIndex, 0, cameras.Length - 1);
|
||||
camIndex = math.clamp(camIndex, 0, cameras.Length - 1);
|
||||
if (camIndex != m_CameraIndex)
|
||||
{
|
||||
m_CameraIndex = camIndex;
|
||||
gs.ActivateCamera(camIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiEditGUI()
|
||||
{
|
||||
DrawSeparator();
|
||||
CountTargetSplats(out var totalSplats, out var totalObjects);
|
||||
EditorGUILayout.LabelField("Total Objects", $"{totalObjects}");
|
||||
EditorGUILayout.LabelField("Total Splats", $"{totalSplats:N0}");
|
||||
if (totalSplats > GaussianSplatAsset.kMaxSplats)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"Can't merge, too many splats (max. supported {GaussianSplatAsset.kMaxSplats:N0})", MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var targetGs = (GaussianSplatRenderer) target;
|
||||
if (!targetGs || !targetGs.HasValidAsset || !targetGs.isActiveAndEnabled)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"Can't merge into {target.name} (no asset or disable)", MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetGs.asset.chunkData != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"Can't merge into {target.name} (needs to use Very High quality preset)", MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
if (GUILayout.Button($"Merge into {target.name}"))
|
||||
{
|
||||
MergeSplatObjects();
|
||||
}
|
||||
}
|
||||
|
||||
void CountTargetSplats(out int totalSplats, out int totalObjects)
|
||||
{
|
||||
totalObjects = 0;
|
||||
totalSplats = 0;
|
||||
foreach (var obj in targets)
|
||||
{
|
||||
var gs = obj as GaussianSplatRenderer;
|
||||
if (!gs || !gs.HasValidAsset || !gs.isActiveAndEnabled)
|
||||
continue;
|
||||
++totalObjects;
|
||||
totalSplats += gs.splatCount;
|
||||
}
|
||||
}
|
||||
|
||||
void MergeSplatObjects()
|
||||
{
|
||||
CountTargetSplats(out var totalSplats, out _);
|
||||
if (totalSplats > GaussianSplatAsset.kMaxSplats)
|
||||
return;
|
||||
var targetGs = (GaussianSplatRenderer) target;
|
||||
|
||||
int copyDstOffset = targetGs.splatCount;
|
||||
targetGs.EditSetSplatCount(totalSplats);
|
||||
foreach (var obj in targets)
|
||||
{
|
||||
var gs = obj as GaussianSplatRenderer;
|
||||
if (!gs || !gs.HasValidAsset || !gs.isActiveAndEnabled)
|
||||
continue;
|
||||
if (gs == targetGs)
|
||||
continue;
|
||||
gs.EditCopySplatsInto(targetGs, 0, copyDstOffset, gs.splatCount);
|
||||
copyDstOffset += gs.splatCount;
|
||||
gs.gameObject.SetActive(false);
|
||||
}
|
||||
Debug.Assert(copyDstOffset == totalSplats, $"Merge count mismatch, {copyDstOffset} vs {totalSplats}");
|
||||
Selection.activeObject = targetGs;
|
||||
}
|
||||
|
||||
void EditGUI(GaussianSplatRenderer gs)
|
||||
{
|
||||
++s_EditStatsUpdateCounter;
|
||||
|
||||
DrawSeparator();
|
||||
bool wasToolActive = ToolManager.activeContextType == typeof(GaussianToolContext);
|
||||
GUILayout.BeginHorizontal();
|
||||
bool isToolActive = GUILayout.Toggle(wasToolActive, "Edit", EditorStyles.miniButton);
|
||||
using (new EditorGUI.DisabledScope(!gs.editModified))
|
||||
{
|
||||
if (GUILayout.Button("Reset", GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Reset Splat Modifications?",
|
||||
$"This will reset edits of {gs.name} to match the {gs.asset.name} asset. Continue?",
|
||||
"Yes, reset", "Cancel"))
|
||||
{
|
||||
gs.enabled = false;
|
||||
gs.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
if (!wasToolActive && isToolActive)
|
||||
{
|
||||
ToolManager.SetActiveContext<GaussianToolContext>();
|
||||
if (Tools.current == Tool.View)
|
||||
Tools.current = Tool.Move;
|
||||
}
|
||||
|
||||
if (wasToolActive && !isToolActive)
|
||||
{
|
||||
ToolManager.SetActiveContext<GameObjectToolContext>();
|
||||
}
|
||||
|
||||
if (isToolActive && gs.asset.chunkData != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Splat move/rotate/scale tools need Very High splat quality preset", MessageType.Warning);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
GUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Add Cutout"))
|
||||
{
|
||||
GaussianCutout cutout = ObjectFactory.CreateGameObject("GSCutout", typeof(GaussianCutout)).GetComponent<GaussianCutout>();
|
||||
Transform cutoutTr = cutout.transform;
|
||||
cutoutTr.SetParent(gs.transform, false);
|
||||
cutoutTr.localScale = (gs.asset.boundsMax - gs.asset.boundsMin) * 0.25f;
|
||||
gs.m_Cutouts ??= Array.Empty<GaussianCutout>();
|
||||
ArrayUtility.Add(ref gs.m_Cutouts, cutout);
|
||||
gs.UpdateEditCountsAndBounds();
|
||||
EditorUtility.SetDirty(gs);
|
||||
Selection.activeGameObject = cutout.gameObject;
|
||||
}
|
||||
if (GUILayout.Button("Use All Cutouts"))
|
||||
{
|
||||
gs.m_Cutouts = FindObjectsByType<GaussianCutout>(FindObjectsSortMode.InstanceID);
|
||||
gs.UpdateEditCountsAndBounds();
|
||||
EditorUtility.SetDirty(gs);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("No Cutouts"))
|
||||
{
|
||||
gs.m_Cutouts = Array.Empty<GaussianCutout>();
|
||||
gs.UpdateEditCountsAndBounds();
|
||||
EditorUtility.SetDirty(gs);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUILayout.PropertyField(m_PropCutouts);
|
||||
|
||||
bool hasCutouts = gs.m_Cutouts != null && gs.m_Cutouts.Length != 0;
|
||||
bool modifiedOrHasCutouts = gs.editModified || hasCutouts;
|
||||
|
||||
var asset = gs.asset;
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
m_ExportBakeTransform = EditorGUILayout.Toggle("Export in world space", m_ExportBakeTransform);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
EditorPrefs.SetBool(kPrefExportBake, m_ExportBakeTransform);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Export PLY"))
|
||||
ExportPlyFile(gs, m_ExportBakeTransform);
|
||||
if (asset.posFormat > GaussianSplatAsset.VectorFormat.Norm16 ||
|
||||
asset.scaleFormat > GaussianSplatAsset.VectorFormat.Norm16 ||
|
||||
asset.colorFormat > GaussianSplatAsset.ColorFormat.Float16x4 ||
|
||||
asset.shFormat > GaussianSplatAsset.SHFormat.Float16)
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"It is recommended to use High or VeryHigh quality preset for editing splats, lower levels are lossy",
|
||||
MessageType.Warning);
|
||||
}
|
||||
|
||||
bool displayEditStats = isToolActive || modifiedOrHasCutouts;
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Splats", $"{gs.splatCount:N0}");
|
||||
if (displayEditStats)
|
||||
{
|
||||
EditorGUILayout.LabelField("Cut", $"{gs.editCutSplats:N0}");
|
||||
EditorGUILayout.LabelField("Deleted", $"{gs.editDeletedSplats:N0}");
|
||||
EditorGUILayout.LabelField("Selected", $"{gs.editSelectedSplats:N0}");
|
||||
if (hasCutouts)
|
||||
{
|
||||
if (s_EditStatsUpdateCounter > 10)
|
||||
{
|
||||
gs.UpdateEditCountsAndBounds();
|
||||
s_EditStatsUpdateCounter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawSeparator()
|
||||
{
|
||||
EditorGUILayout.Space(12f, true);
|
||||
GUILayout.Box(GUIContent.none, "sv_iconselector_sep", GUILayout.Height(2), GUILayout.ExpandWidth(true));
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
bool HasFrameBounds()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Bounds OnGetFrameBounds()
|
||||
{
|
||||
var gs = target as GaussianSplatRenderer;
|
||||
if (!gs || !gs.HasValidRenderSetup)
|
||||
return new Bounds(Vector3.zero, Vector3.one);
|
||||
Bounds bounds = default;
|
||||
bounds.SetMinMax(gs.asset.boundsMin, gs.asset.boundsMax);
|
||||
if (gs.editSelectedSplats > 0)
|
||||
{
|
||||
bounds = gs.editSelectedBounds;
|
||||
}
|
||||
bounds.extents *= 0.7f;
|
||||
return TransformBounds(gs.transform, bounds);
|
||||
}
|
||||
|
||||
public static Bounds TransformBounds(Transform tr, Bounds bounds )
|
||||
{
|
||||
var center = tr.TransformPoint(bounds.center);
|
||||
|
||||
var ext = bounds.extents;
|
||||
var axisX = tr.TransformVector(ext.x, 0, 0);
|
||||
var axisY = tr.TransformVector(0, ext.y, 0);
|
||||
var axisZ = tr.TransformVector(0, 0, ext.z);
|
||||
|
||||
// sum their absolute value to get the world extents
|
||||
ext.x = Mathf.Abs(axisX.x) + Mathf.Abs(axisY.x) + Mathf.Abs(axisZ.x);
|
||||
ext.y = Mathf.Abs(axisX.y) + Mathf.Abs(axisY.y) + Mathf.Abs(axisZ.y);
|
||||
ext.z = Mathf.Abs(axisX.z) + Mathf.Abs(axisY.z) + Mathf.Abs(axisZ.z);
|
||||
|
||||
return new Bounds { center = center, extents = ext };
|
||||
}
|
||||
|
||||
static unsafe void ExportPlyFile(GaussianSplatRenderer gs, bool bakeTransform)
|
||||
{
|
||||
var path = EditorUtility.SaveFilePanel(
|
||||
"Export Gaussian Splat PLY file", "", $"{gs.asset.name}-edit.ply", "ply");
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return;
|
||||
|
||||
int kSplatSize = UnsafeUtility.SizeOf<GaussianSplatAssetCreator.InputSplatData>();
|
||||
using var gpuData = new GraphicsBuffer(GraphicsBuffer.Target.Structured, gs.splatCount, kSplatSize);
|
||||
|
||||
if (!gs.EditExportData(gpuData, bakeTransform))
|
||||
return;
|
||||
|
||||
GaussianSplatAssetCreator.InputSplatData[] data = new GaussianSplatAssetCreator.InputSplatData[gpuData.count];
|
||||
gpuData.GetData(data);
|
||||
|
||||
var gpuDeleted = gs.GpuEditDeleted;
|
||||
uint[] deleted = new uint[gpuDeleted.count];
|
||||
gpuDeleted.GetData(deleted);
|
||||
|
||||
// count non-deleted splats
|
||||
int aliveCount = 0;
|
||||
for (int i = 0; i < data.Length; ++i)
|
||||
{
|
||||
int wordIdx = i >> 5;
|
||||
int bitIdx = i & 31;
|
||||
bool isDeleted = (deleted[wordIdx] & (1u << bitIdx)) != 0;
|
||||
bool isCutout = data[i].nor.sqrMagnitude > 0;
|
||||
if (!isDeleted && !isCutout)
|
||||
++aliveCount;
|
||||
}
|
||||
|
||||
using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
|
||||
// note: this is a long string! but we don't use multiline literal because we want guaranteed LF line ending
|
||||
var header = $"ply\nformat binary_little_endian 1.0\nelement vertex {aliveCount}\nproperty float x\nproperty float y\nproperty float z\nproperty float nx\nproperty float ny\nproperty float nz\nproperty float f_dc_0\nproperty float f_dc_1\nproperty float f_dc_2\nproperty float f_rest_0\nproperty float f_rest_1\nproperty float f_rest_2\nproperty float f_rest_3\nproperty float f_rest_4\nproperty float f_rest_5\nproperty float f_rest_6\nproperty float f_rest_7\nproperty float f_rest_8\nproperty float f_rest_9\nproperty float f_rest_10\nproperty float f_rest_11\nproperty float f_rest_12\nproperty float f_rest_13\nproperty float f_rest_14\nproperty float f_rest_15\nproperty float f_rest_16\nproperty float f_rest_17\nproperty float f_rest_18\nproperty float f_rest_19\nproperty float f_rest_20\nproperty float f_rest_21\nproperty float f_rest_22\nproperty float f_rest_23\nproperty float f_rest_24\nproperty float f_rest_25\nproperty float f_rest_26\nproperty float f_rest_27\nproperty float f_rest_28\nproperty float f_rest_29\nproperty float f_rest_30\nproperty float f_rest_31\nproperty float f_rest_32\nproperty float f_rest_33\nproperty float f_rest_34\nproperty float f_rest_35\nproperty float f_rest_36\nproperty float f_rest_37\nproperty float f_rest_38\nproperty float f_rest_39\nproperty float f_rest_40\nproperty float f_rest_41\nproperty float f_rest_42\nproperty float f_rest_43\nproperty float f_rest_44\nproperty float opacity\nproperty float scale_0\nproperty float scale_1\nproperty float scale_2\nproperty float rot_0\nproperty float rot_1\nproperty float rot_2\nproperty float rot_3\nend_header\n";
|
||||
fs.Write(Encoding.UTF8.GetBytes(header));
|
||||
for (int i = 0; i < data.Length; ++i)
|
||||
{
|
||||
int wordIdx = i >> 5;
|
||||
int bitIdx = i & 31;
|
||||
bool isDeleted = (deleted[wordIdx] & (1u << bitIdx)) != 0;
|
||||
bool isCutout = data[i].nor.sqrMagnitude > 0;
|
||||
if (!isDeleted && !isCutout)
|
||||
{
|
||||
var splat = data[i];
|
||||
byte* ptr = (byte*)&splat;
|
||||
fs.Write(new ReadOnlySpan<byte>(ptr, kSplatSize));
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"Exported PLY {path} with {aliveCount:N0} splats");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Editor/GaussianSplatRendererEditor.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/GaussianSplatRendererEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0ce434aee9ae4ee6b1f5cd10ae7c8cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
210
MVS/3DGS-Unity/Editor/GaussianSplatValidator.cs
Normal file
210
MVS/3DGS-Unity/Editor/GaussianSplatValidator.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using System.IO;
|
||||
using GaussianSplatting.Runtime;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
|
||||
namespace GaussianSplatting.Editor
|
||||
{
|
||||
[BurstCompile]
|
||||
public static class GaussianSplatValidator
|
||||
{
|
||||
struct RefItem
|
||||
{
|
||||
public string assetPath;
|
||||
public int cameraIndex;
|
||||
public float fov;
|
||||
}
|
||||
|
||||
// currently on RTX 3080Ti: 43.76, 39.36, 43.50 PSNR
|
||||
[MenuItem("Tools/Gaussian Splats/Debug/Validate Render against SBIR")]
|
||||
public static void ValidateSBIR()
|
||||
{
|
||||
ValidateImpl("SBIR");
|
||||
}
|
||||
// currently on RTX 3080Ti: matches
|
||||
[MenuItem("Tools/Gaussian Splats/Debug/Validate Render against D3D12")]
|
||||
public static void ValidateD3D12()
|
||||
{
|
||||
ValidateImpl("D3D12");
|
||||
}
|
||||
|
||||
static unsafe void ValidateImpl(string refPrefix)
|
||||
{
|
||||
var gaussians = Object.FindObjectOfType(typeof(GaussianSplatRenderer)) as GaussianSplatRenderer;
|
||||
{
|
||||
if (gaussians == null)
|
||||
{
|
||||
Debug.LogError("No GaussianSplatRenderer object found");
|
||||
return;
|
||||
}
|
||||
}
|
||||
var items = new RefItem[]
|
||||
{
|
||||
new() {assetPath = "bicycle", cameraIndex = 0, fov = 39.09651f},
|
||||
new() {assetPath = "truck", cameraIndex = 30, fov = 50},
|
||||
new() {assetPath = "garden", cameraIndex = 30, fov = 47},
|
||||
};
|
||||
|
||||
var cam = Camera.main;
|
||||
var oldAsset = gaussians.asset;
|
||||
var oldCamPos = cam.transform.localPosition;
|
||||
var oldCamRot = cam.transform.localRotation;
|
||||
var oldCamFov = cam.fieldOfView;
|
||||
|
||||
for (var index = 0; index < items.Length; index++)
|
||||
{
|
||||
var item = items[index];
|
||||
EditorUtility.DisplayProgressBar("Validating Gaussian splat rendering", item.assetPath, (float)index / items.Length);
|
||||
var path = $"Assets/GaussianAssets/{item.assetPath}-point_cloud-iteration_30000-point_cloud.asset";
|
||||
var gs = AssetDatabase.LoadAssetAtPath<GaussianSplatAsset>(path);
|
||||
if (gs == null)
|
||||
{
|
||||
Debug.LogError($"Did not find asset for validation item {item.assetPath} at {path}");
|
||||
continue;
|
||||
}
|
||||
var refImageFile = $"../../docs/RefImages/{refPrefix}_{item.assetPath}{item.cameraIndex}.png"; // use our snapshot by default
|
||||
if (!File.Exists(refImageFile))
|
||||
{
|
||||
Debug.LogError($"Did not find reference image for validation item {item.assetPath} at {refImageFile}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var compareTexture = new Texture2D(4, 4, GraphicsFormat.R8G8B8A8_SRGB, TextureCreationFlags.None);
|
||||
byte[] refImageBytes = File.ReadAllBytes(refImageFile);
|
||||
ImageConversion.LoadImage(compareTexture, refImageBytes, false);
|
||||
|
||||
int width = compareTexture.width;
|
||||
int height = compareTexture.height;
|
||||
|
||||
var renderTarget = RenderTexture.GetTemporary(width, height, 24, GraphicsFormat.R8G8B8A8_SRGB);
|
||||
cam.targetTexture = renderTarget;
|
||||
cam.fieldOfView = item.fov;
|
||||
|
||||
var captureTexture = new Texture2D(width, height, GraphicsFormat.R8G8B8A8_SRGB, TextureCreationFlags.None);
|
||||
NativeArray<Color32> diffPixels = new(width * height, Allocator.Persistent);
|
||||
|
||||
gaussians.m_Asset = gs;
|
||||
gaussians.Update();
|
||||
gaussians.ActivateCamera(item.cameraIndex);
|
||||
cam.Render();
|
||||
Graphics.SetRenderTarget(renderTarget);
|
||||
captureTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
|
||||
|
||||
NativeArray<Color32> refPixels = compareTexture.GetPixelData<Color32>(0);
|
||||
NativeArray<Color32> gotPixels = captureTexture.GetPixelData<Color32>(0);
|
||||
float psnr = 0, rmse = 0;
|
||||
int errorsCount = 0;
|
||||
DiffImagesJob difJob = new DiffImagesJob();
|
||||
difJob.difPixels = diffPixels;
|
||||
difJob.refPixels = refPixels;
|
||||
difJob.gotPixels = gotPixels;
|
||||
difJob.psnrPtr = &psnr;
|
||||
difJob.rmsePtr = &rmse;
|
||||
difJob.difPixCount = &errorsCount;
|
||||
difJob.Schedule().Complete();
|
||||
|
||||
string pathDif = $"../../Shot-{refPrefix}-{item.assetPath}{item.cameraIndex}-diff.png";
|
||||
string pathRef = $"../../Shot-{refPrefix}-{item.assetPath}{item.cameraIndex}-ref.png";
|
||||
string pathGot = $"../../Shot-{refPrefix}-{item.assetPath}{item.cameraIndex}-got.png";
|
||||
|
||||
if (errorsCount > 50 || psnr < 90.0f)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"{refPrefix} {item.assetPath} cam {item.cameraIndex}: RMSE {rmse:F2} PSNR {psnr:F2} diff pixels {errorsCount:N0}");
|
||||
|
||||
NativeArray<byte> pngBytes = ImageConversion.EncodeNativeArrayToPNG(diffPixels,
|
||||
GraphicsFormat.R8G8B8A8_SRGB, (uint) width, (uint) height);
|
||||
File.WriteAllBytes(pathDif, pngBytes.ToArray());
|
||||
pngBytes.Dispose();
|
||||
pngBytes = ImageConversion.EncodeNativeArrayToPNG(refPixels, GraphicsFormat.R8G8B8A8_SRGB,
|
||||
(uint) width, (uint) height);
|
||||
File.WriteAllBytes(pathRef, pngBytes.ToArray());
|
||||
pngBytes.Dispose();
|
||||
pngBytes = ImageConversion.EncodeNativeArrayToPNG(gotPixels, GraphicsFormat.R8G8B8A8_SRGB,
|
||||
(uint) width, (uint) height);
|
||||
File.WriteAllBytes(pathGot, pngBytes.ToArray());
|
||||
pngBytes.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete(pathDif);
|
||||
File.Delete(pathRef);
|
||||
File.Delete(pathGot);
|
||||
}
|
||||
|
||||
diffPixels.Dispose();
|
||||
RenderTexture.ReleaseTemporary(renderTarget);
|
||||
Object.DestroyImmediate(captureTexture);
|
||||
Object.DestroyImmediate(compareTexture);
|
||||
}
|
||||
|
||||
cam.targetTexture = null;
|
||||
gaussians.m_Asset = oldAsset;
|
||||
gaussians.Update();
|
||||
cam.transform.localPosition = oldCamPos;
|
||||
cam.transform.localRotation = oldCamRot;
|
||||
cam.fieldOfView = oldCamFov;
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
struct DiffImagesJob : IJob
|
||||
{
|
||||
public NativeArray<Color32> refPixels;
|
||||
public NativeArray<Color32> gotPixels;
|
||||
public NativeArray<Color32> difPixels;
|
||||
[NativeDisableUnsafePtrRestriction] public unsafe float* rmsePtr;
|
||||
[NativeDisableUnsafePtrRestriction] public unsafe float* psnrPtr;
|
||||
[NativeDisableUnsafePtrRestriction] public unsafe int* difPixCount;
|
||||
|
||||
public unsafe void Execute()
|
||||
{
|
||||
const int kDiffScale = 5;
|
||||
const int kDiffThreshold = 3 * kDiffScale;
|
||||
*difPixCount = 0;
|
||||
double sumSqDif = 0;
|
||||
for (int i = 0; i < refPixels.Length; ++i)
|
||||
{
|
||||
Color32 cref = refPixels[i];
|
||||
// note: LoadImage always loads PNGs into ARGB order, so swizzle to normal RGBA
|
||||
cref = new Color32(cref.g, cref.b, cref.a, 255);
|
||||
refPixels[i] = cref;
|
||||
|
||||
Color32 cgot = gotPixels[i];
|
||||
cgot.a = 255;
|
||||
gotPixels[i] = cgot;
|
||||
|
||||
Color32 cdif = new Color32(0, 0, 0, 255);
|
||||
cdif.r = (byte)math.abs(cref.r - cgot.r);
|
||||
cdif.g = (byte)math.abs(cref.g - cgot.g);
|
||||
cdif.b = (byte)math.abs(cref.b - cgot.b);
|
||||
sumSqDif += cdif.r * cdif.r + cdif.g * cdif.g + cdif.b * cdif.b;
|
||||
|
||||
cdif.r = (byte)math.min(255, cdif.r * kDiffScale);
|
||||
cdif.g = (byte)math.min(255, cdif.g * kDiffScale);
|
||||
cdif.b = (byte)math.min(255, cdif.b * kDiffScale);
|
||||
difPixels[i] = cdif;
|
||||
if (cdif.r >= kDiffThreshold || cdif.g >= kDiffThreshold || cdif.b >= kDiffThreshold)
|
||||
{
|
||||
(*difPixCount)++;
|
||||
}
|
||||
}
|
||||
|
||||
double meanSqDif = sumSqDif / (refPixels.Length * 3);
|
||||
double rmse = math.sqrt(meanSqDif);
|
||||
double psnr = 20.0 * math.log10(255.0) - 10.0 * math.log10(rmse * rmse);
|
||||
*rmsePtr = (float) rmse;
|
||||
*psnrPtr = (float) psnr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Editor/GaussianSplatValidator.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/GaussianSplatValidator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f8e75b80eb181a4698f733ba59b694b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
21
MVS/3DGS-Unity/Editor/GaussianSplattingEditor.asmdef
Normal file
21
MVS/3DGS-Unity/Editor/GaussianSplattingEditor.asmdef
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "GaussianSplattingEditor",
|
||||
"rootNamespace": "GaussianSplatting.Editor",
|
||||
"references": [
|
||||
"GUID:4b653174f8fcdcd49b4c9a6f1ca8c7c3",
|
||||
"GUID:2665a8d13d1b3f18800f46e256720795",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||
"GUID:e0cd26848372d4e5c891c569017e11f1"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14414175af4b366469db63f2efee475f
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
43
MVS/3DGS-Unity/Editor/GaussianTool.cs
Normal file
43
MVS/3DGS-Unity/Editor/GaussianTool.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using GaussianSplatting.Runtime;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GaussianSplatting.Editor
|
||||
{
|
||||
abstract class GaussianTool : EditorTool
|
||||
{
|
||||
protected GaussianSplatRenderer GetRenderer()
|
||||
{
|
||||
var gs = target as GaussianSplatRenderer;
|
||||
if (!gs || !gs.HasValidAsset || !gs.HasValidRenderSetup)
|
||||
return null;
|
||||
return gs;
|
||||
}
|
||||
|
||||
protected bool CanBeEdited()
|
||||
{
|
||||
var gs = GetRenderer();
|
||||
if (!gs)
|
||||
return false;
|
||||
return gs.asset.chunkData == null; // need to be lossless / non-chunked for editing
|
||||
}
|
||||
|
||||
protected bool HasSelection()
|
||||
{
|
||||
var gs = GetRenderer();
|
||||
if (!gs)
|
||||
return false;
|
||||
return gs.editSelectedSplats > 0;
|
||||
}
|
||||
|
||||
protected Vector3 GetSelectionCenterLocal()
|
||||
{
|
||||
var gs = GetRenderer();
|
||||
if (!gs || gs.editSelectedSplats == 0)
|
||||
return Vector3.zero;
|
||||
return gs.editSelectedBounds.center;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Editor/GaussianTool.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/GaussianTool.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6203c808ab9e64a4a8ff0277c5aa7669
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
192
MVS/3DGS-Unity/Editor/GaussianToolContext.cs
Normal file
192
MVS/3DGS-Unity/Editor/GaussianToolContext.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using System;
|
||||
using GaussianSplatting.Runtime;
|
||||
using UnityEditor;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GaussianSplatting.Editor
|
||||
{
|
||||
[EditorToolContext("GaussianSplats", typeof(GaussianSplatRenderer)), Icon(k_IconPath)]
|
||||
class GaussianToolContext : EditorToolContext
|
||||
{
|
||||
const string k_IconPath = "Packages/org.nesnausk.gaussian-splatting/Editor/Icons/GaussianContext.png";
|
||||
|
||||
Vector2 m_MouseStartDragPos;
|
||||
|
||||
protected override Type GetEditorToolType(Tool tool)
|
||||
{
|
||||
if (tool == Tool.Move)
|
||||
return typeof(GaussianMoveTool);
|
||||
//if (tool == Tool.Rotate)
|
||||
// return typeof(GaussianRotateTool); // not correctly working yet
|
||||
//if (tool == Tool.Scale)
|
||||
// return typeof(GaussianScaleTool); // not working correctly yet when the GS itself has scale
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void OnWillBeDeactivated()
|
||||
{
|
||||
var gs = target as GaussianSplatRenderer;
|
||||
if (!gs)
|
||||
return;
|
||||
gs.EditDeselectAll();
|
||||
}
|
||||
|
||||
static void HandleKeyboardCommands(Event evt, GaussianSplatRenderer gs)
|
||||
{
|
||||
if (evt.type != EventType.ValidateCommand && evt.type != EventType.ExecuteCommand)
|
||||
return;
|
||||
bool execute = evt.type == EventType.ExecuteCommand;
|
||||
switch (evt.commandName)
|
||||
{
|
||||
// ugh, EventCommandNames string constants is internal :(
|
||||
case "SoftDelete":
|
||||
case "Delete":
|
||||
if (execute)
|
||||
{
|
||||
gs.EditDeleteSelected();
|
||||
GaussianSplatRendererEditor.RepaintAll();
|
||||
}
|
||||
evt.Use();
|
||||
break;
|
||||
case "SelectAll":
|
||||
if (execute)
|
||||
{
|
||||
gs.EditSelectAll();
|
||||
GaussianSplatRendererEditor.RepaintAll();
|
||||
}
|
||||
evt.Use();
|
||||
break;
|
||||
case "DeselectAll":
|
||||
if (execute)
|
||||
{
|
||||
gs.EditDeselectAll();
|
||||
GaussianSplatRendererEditor.RepaintAll();
|
||||
}
|
||||
evt.Use();
|
||||
break;
|
||||
case "InvertSelection":
|
||||
if (execute)
|
||||
{
|
||||
gs.EditInvertSelection();
|
||||
GaussianSplatRendererEditor.RepaintAll();
|
||||
}
|
||||
evt.Use();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsViewToolActive()
|
||||
{
|
||||
return Tools.viewToolActive || Tools.current == Tool.View || (Event.current != null && Event.current.alt);
|
||||
}
|
||||
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
if (!(window is SceneView sceneView))
|
||||
return;
|
||||
var gs = target as GaussianSplatRenderer;
|
||||
if (!gs)
|
||||
return;
|
||||
|
||||
GaussianSplatRendererEditor.BumpGUICounter();
|
||||
|
||||
int id = GUIUtility.GetControlID(FocusType.Passive);
|
||||
Event evt = Event.current;
|
||||
HandleKeyboardCommands(evt, gs);
|
||||
var evtType = evt.GetTypeForControl(id);
|
||||
switch (evtType)
|
||||
{
|
||||
case EventType.Layout:
|
||||
// make this be the default tool, so that we get focus when user clicks on nothing else
|
||||
HandleUtility.AddDefaultControl(id);
|
||||
break;
|
||||
case EventType.MouseDown:
|
||||
if (IsViewToolActive())
|
||||
break;
|
||||
if (HandleUtility.nearestControl == id && evt.button == 0)
|
||||
{
|
||||
// shift/command adds to selection, ctrl removes from selection: if none of these
|
||||
// are present, start a new selection
|
||||
if (!evt.shift && !EditorGUI.actionKey && !evt.control)
|
||||
gs.EditDeselectAll();
|
||||
|
||||
// record selection state at start
|
||||
gs.EditStoreSelectionMouseDown();
|
||||
GaussianSplatRendererEditor.RepaintAll();
|
||||
|
||||
GUIUtility.hotControl = id;
|
||||
m_MouseStartDragPos = evt.mousePosition;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == id && evt.button == 0)
|
||||
{
|
||||
Rect rect = FromToRect(m_MouseStartDragPos, evt.mousePosition);
|
||||
Vector2 rectMin = HandleUtility.GUIPointToScreenPixelCoordinate(rect.min);
|
||||
Vector2 rectMax = HandleUtility.GUIPointToScreenPixelCoordinate(rect.max);
|
||||
gs.EditUpdateSelection(rectMin, rectMax, sceneView.camera, evt.control);
|
||||
GaussianSplatRendererEditor.RepaintAll();
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == id && evt.button == 0)
|
||||
{
|
||||
m_MouseStartDragPos = Vector2.zero;
|
||||
GUIUtility.hotControl = 0;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
case EventType.Repaint:
|
||||
// draw cutout gizmos
|
||||
Handles.color = new Color(1,0,1,0.7f);
|
||||
var prevMatrix = Handles.matrix;
|
||||
foreach (var cutout in gs.m_Cutouts)
|
||||
{
|
||||
if (!cutout)
|
||||
continue;
|
||||
Handles.matrix = cutout.transform.localToWorldMatrix;
|
||||
if (cutout.m_Type == GaussianCutout.Type.Ellipsoid)
|
||||
{
|
||||
Handles.DrawWireDisc(Vector3.zero, Vector3.up, 1.0f);
|
||||
Handles.DrawWireDisc(Vector3.zero, Vector3.right, 1.0f);
|
||||
Handles.DrawWireDisc(Vector3.zero, Vector3.forward, 1.0f);
|
||||
}
|
||||
if (cutout.m_Type == GaussianCutout.Type.Box)
|
||||
Handles.DrawWireCube(Vector3.zero, Vector3.one * 2);
|
||||
}
|
||||
|
||||
Handles.matrix = prevMatrix;
|
||||
// draw selection bounding box
|
||||
if (gs.editSelectedSplats > 0)
|
||||
{
|
||||
var selBounds = GaussianSplatRendererEditor.TransformBounds(gs.transform, gs.editSelectedBounds);
|
||||
Handles.DrawWireCube(selBounds.center, selBounds.size);
|
||||
}
|
||||
// draw drag rectangle
|
||||
if (GUIUtility.hotControl == id && evt.mousePosition != m_MouseStartDragPos)
|
||||
{
|
||||
GUIStyle style = "SelectionRect";
|
||||
Handles.BeginGUI();
|
||||
style.Draw(FromToRect(m_MouseStartDragPos, evt.mousePosition), false, false, false, false);
|
||||
Handles.EndGUI();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// build a rect that always has a positive size
|
||||
static Rect FromToRect(Vector2 from, Vector2 to)
|
||||
{
|
||||
if (from.x > to.x)
|
||||
(from.x, to.x) = (to.x, from.x);
|
||||
if (from.y > to.y)
|
||||
(from.y, to.y) = (to.y, from.y);
|
||||
return new Rect(from.x, from.y, to.x - from.x, to.y - from.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
MVS/3DGS-Unity/Editor/GaussianToolContext.cs.meta
Normal file
3
MVS/3DGS-Unity/Editor/GaussianToolContext.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80d7ecbaa1b24e6399ee95f6fc0b9c90
|
||||
timeCreated: 1697718362
|
||||
8
MVS/3DGS-Unity/Editor/Icons.meta
Normal file
8
MVS/3DGS-Unity/Editor/Icons.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 770e497b696b99641aa1bf295d0b3552
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
MVS/3DGS-Unity/Editor/Icons/GaussianContext.png
Normal file
BIN
MVS/3DGS-Unity/Editor/Icons/GaussianContext.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
153
MVS/3DGS-Unity/Editor/Icons/GaussianContext.png.meta
Normal file
153
MVS/3DGS-Unity/Editor/Icons/GaussianContext.png.meta
Normal file
@@ -0,0 +1,153 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c61202bc8cc557546afa505174da220e
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 128
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Nintendo Switch
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: LinuxHeadlessSimulation
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
MVS/3DGS-Unity/Editor/Icons/GaussianContext@2x.png
Normal file
BIN
MVS/3DGS-Unity/Editor/Icons/GaussianContext@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
153
MVS/3DGS-Unity/Editor/Icons/GaussianContext@2x.png.meta
Normal file
153
MVS/3DGS-Unity/Editor/Icons/GaussianContext@2x.png.meta
Normal file
@@ -0,0 +1,153 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15d0de03329e14440b034e884fe10379
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 128
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Nintendo Switch
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: LinuxHeadlessSimulation
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
MVS/3DGS-Unity/Editor/Icons/d_GaussianContext.png
Normal file
BIN
MVS/3DGS-Unity/Editor/Icons/d_GaussianContext.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
231
MVS/3DGS-Unity/Editor/Icons/d_GaussianContext.png.meta
Normal file
231
MVS/3DGS-Unity/Editor/Icons/d_GaussianContext.png.meta
Normal file
@@ -0,0 +1,231 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81df9c0903abfa345a9022d090982f5d
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 128
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: tvOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Lumin
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: CloudRendering
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Nintendo Switch
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: LinuxHeadlessSimulation
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
MVS/3DGS-Unity/Editor/Icons/d_GaussianContext@2x.png
Normal file
BIN
MVS/3DGS-Unity/Editor/Icons/d_GaussianContext@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
153
MVS/3DGS-Unity/Editor/Icons/d_GaussianContext@2x.png.meta
Normal file
153
MVS/3DGS-Unity/Editor/Icons/d_GaussianContext@2x.png.meta
Normal file
@@ -0,0 +1,153 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e37880566baf3964e9b75e45adb36f3f
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 128
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Nintendo Switch
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: LinuxHeadlessSimulation
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
MVS/3DGS-Unity/Editor/Utils.meta
Normal file
8
MVS/3DGS-Unity/Editor/Utils.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f812890ad0ea4c747bdc67b6d2c1c627
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
26
MVS/3DGS-Unity/Editor/Utils/CaptureScreenshot.cs
Normal file
26
MVS/3DGS-Unity/Editor/Utils/CaptureScreenshot.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GaussianSplatting.Editor.Utils
|
||||
{
|
||||
public class CaptureScreenshot : MonoBehaviour
|
||||
{
|
||||
[MenuItem("Tools/Gaussian Splats/Debug/Capture Screenshot %g")]
|
||||
public static void CaptureShot()
|
||||
{
|
||||
int counter = 0;
|
||||
string path;
|
||||
while(true)
|
||||
{
|
||||
path = $"Shot-{counter:0000}.png";
|
||||
if (!System.IO.File.Exists(path))
|
||||
break;
|
||||
++counter;
|
||||
}
|
||||
ScreenCapture.CaptureScreenshot(path);
|
||||
Debug.Log($"Captured {path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Editor/Utils/CaptureScreenshot.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/Utils/CaptureScreenshot.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c80a2b8daebbc1449b79e5ec436f39d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
274
MVS/3DGS-Unity/Editor/Utils/FilePickerControl.cs
Normal file
274
MVS/3DGS-Unity/Editor/Utils/FilePickerControl.cs
Normal file
@@ -0,0 +1,274 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Experimental;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GaussianSplatting.Editor.Utils
|
||||
{
|
||||
public class FilePickerControl
|
||||
{
|
||||
const string kLastPathPref = "nesnausk.utils.FilePickerLastPath";
|
||||
static Texture2D s_FolderIcon => EditorGUIUtility.FindTexture(EditorResources.emptyFolderIconName);
|
||||
static Texture2D s_FileIcon => EditorGUIUtility.FindTexture(EditorResources.folderIconName);
|
||||
static GUIStyle s_StyleTextFieldText;
|
||||
static GUIStyle s_StyleTextFieldDropdown;
|
||||
static readonly int kPathFieldControlID = "FilePickerPathField".GetHashCode();
|
||||
const int kIconSize = 15;
|
||||
const int kRecentPathsCount = 20;
|
||||
|
||||
public static string PathToDisplayString(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return "<none>";
|
||||
path = path.Replace('\\', '/');
|
||||
string[] parts = path.Split('/');
|
||||
|
||||
// check if filename is not some super generic one
|
||||
var baseName = Path.GetFileNameWithoutExtension(parts[^1]).ToLowerInvariant();
|
||||
if (baseName != "point_cloud" && baseName != "splat" && baseName != "input")
|
||||
return parts[^1];
|
||||
|
||||
// otherwise if filename is just some generic "point cloud" type, then take some folder names above it into account
|
||||
if (parts.Length >= 4)
|
||||
path = string.Join('/', parts.TakeLast(4));
|
||||
|
||||
path = path.Replace('/', '-');
|
||||
return path;
|
||||
}
|
||||
|
||||
class PreviousPaths
|
||||
{
|
||||
public PreviousPaths(List<string> paths)
|
||||
{
|
||||
this.paths = paths;
|
||||
UpdateContent();
|
||||
}
|
||||
public void UpdateContent()
|
||||
{
|
||||
this.content = paths.Select(p => new GUIContent(PathToDisplayString(p))).ToArray();
|
||||
}
|
||||
public List<string> paths;
|
||||
public GUIContent[] content;
|
||||
}
|
||||
Dictionary<string, PreviousPaths> m_PreviousPaths = new();
|
||||
|
||||
void PopulatePreviousPaths(string nameKey)
|
||||
{
|
||||
if (m_PreviousPaths.ContainsKey(nameKey))
|
||||
return;
|
||||
|
||||
List<string> prevPaths = new();
|
||||
for (int i = 0; i < kRecentPathsCount; ++i)
|
||||
{
|
||||
string path = EditorPrefs.GetString($"{kLastPathPref}-{nameKey}-{i}");
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
prevPaths.Add(path);
|
||||
}
|
||||
m_PreviousPaths.Add(nameKey, new PreviousPaths(prevPaths));
|
||||
}
|
||||
|
||||
void UpdatePreviousPaths(string nameKey, string path)
|
||||
{
|
||||
if (!m_PreviousPaths.ContainsKey(nameKey))
|
||||
{
|
||||
m_PreviousPaths.Add(nameKey, new PreviousPaths(new List<string>()));
|
||||
}
|
||||
var prevPaths = m_PreviousPaths[nameKey];
|
||||
prevPaths.paths.Remove(path);
|
||||
prevPaths.paths.Insert(0, path);
|
||||
while (prevPaths.paths.Count > kRecentPathsCount)
|
||||
prevPaths.paths.RemoveAt(prevPaths.paths.Count - 1);
|
||||
prevPaths.UpdateContent();
|
||||
|
||||
for (int i = 0; i < prevPaths.paths.Count; ++i)
|
||||
{
|
||||
EditorPrefs.SetString($"{kLastPathPref}-{nameKey}-{i}", prevPaths.paths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static bool CheckPath(string path, bool isFolder)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return false;
|
||||
if (isFolder)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static string PathAbsToStorage(string path)
|
||||
{
|
||||
path = path.Replace('\\', '/');
|
||||
var dataPath = Application.dataPath;
|
||||
if (path.StartsWith(dataPath, StringComparison.Ordinal))
|
||||
{
|
||||
path = Path.GetRelativePath($"{dataPath}/..", path);
|
||||
path = path.Replace('\\', '/');
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
bool CheckAndSetNewPath(ref string path, string nameKey, bool isFolder)
|
||||
{
|
||||
path = PathAbsToStorage(path);
|
||||
if (CheckPath(path, isFolder))
|
||||
{
|
||||
EditorPrefs.SetString($"{kLastPathPref}-{nameKey}", path);
|
||||
UpdatePreviousPaths(nameKey, path);
|
||||
GUI.changed = true;
|
||||
Event.current.Use();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string PreviousPathsDropdown(Rect position, string value, string nameKey, bool isFolder)
|
||||
{
|
||||
PopulatePreviousPaths(nameKey);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = EditorPrefs.GetString($"{kLastPathPref}-{nameKey}");
|
||||
|
||||
m_PreviousPaths.TryGetValue(nameKey, out var prevPaths);
|
||||
|
||||
EditorGUI.BeginDisabledGroup(prevPaths == null || prevPaths.paths.Count == 0);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
int oldIndent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
int parameterIndex = EditorGUI.Popup(position, GUIContent.none, -1, prevPaths.content, s_StyleTextFieldDropdown);
|
||||
if (EditorGUI.EndChangeCheck() && parameterIndex < prevPaths.paths.Count)
|
||||
{
|
||||
string newValue = prevPaths.paths[parameterIndex];
|
||||
if (CheckAndSetNewPath(ref newValue, nameKey, isFolder))
|
||||
value = newValue;
|
||||
}
|
||||
EditorGUI.indentLevel = oldIndent;
|
||||
EditorGUI.EndDisabledGroup();
|
||||
return value;
|
||||
}
|
||||
|
||||
// null extension picks folders
|
||||
public string PathFieldGUI(Rect position, GUIContent label, string value, string extension, string nameKey)
|
||||
{
|
||||
s_StyleTextFieldText ??= new GUIStyle("TextFieldDropDownText");
|
||||
s_StyleTextFieldDropdown ??= new GUIStyle("TextFieldDropdown");
|
||||
bool isFolder = extension == null;
|
||||
|
||||
int controlId = GUIUtility.GetControlID(kPathFieldControlID, FocusType.Keyboard, position);
|
||||
Rect fullRect = EditorGUI.PrefixLabel(position, controlId, label);
|
||||
Rect textRect = new Rect(fullRect.x, fullRect.y, fullRect.width - s_StyleTextFieldDropdown.fixedWidth, fullRect.height);
|
||||
Rect dropdownRect = new Rect(textRect.xMax, fullRect.y, s_StyleTextFieldDropdown.fixedWidth, fullRect.height);
|
||||
Rect iconRect = new Rect(textRect.xMax - kIconSize, textRect.y, kIconSize, textRect.height);
|
||||
|
||||
value = PreviousPathsDropdown(dropdownRect, value, nameKey, isFolder);
|
||||
|
||||
string displayText = PathToDisplayString(value);
|
||||
|
||||
Event evt = Event.current;
|
||||
switch (evt.type)
|
||||
{
|
||||
case EventType.KeyDown:
|
||||
if (GUIUtility.keyboardControl == controlId)
|
||||
{
|
||||
if (evt.keyCode is KeyCode.Backspace or KeyCode.Delete)
|
||||
{
|
||||
value = null;
|
||||
EditorPrefs.SetString($"{kLastPathPref}-{nameKey}", "");
|
||||
GUI.changed = true;
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType.Repaint:
|
||||
s_StyleTextFieldText.Draw(textRect, new GUIContent(displayText), controlId, DragAndDrop.activeControlID == controlId);
|
||||
GUI.DrawTexture(iconRect, isFolder ? s_FolderIcon : s_FileIcon, ScaleMode.ScaleToFit);
|
||||
break;
|
||||
case EventType.MouseDown:
|
||||
if (evt.button != 0 || !GUI.enabled)
|
||||
break;
|
||||
|
||||
if (textRect.Contains(evt.mousePosition))
|
||||
{
|
||||
if (iconRect.Contains(evt.mousePosition))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
value = EditorPrefs.GetString($"{kLastPathPref}-{nameKey}");
|
||||
string newPath;
|
||||
string openToPath = string.Empty;
|
||||
if (isFolder)
|
||||
{
|
||||
if (Directory.Exists(value))
|
||||
openToPath = value;
|
||||
newPath = EditorUtility.OpenFolderPanel("Select folder", openToPath, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (File.Exists(value))
|
||||
openToPath = Path.GetDirectoryName(value);
|
||||
newPath = EditorUtility.OpenFilePanel("Select file", openToPath, extension);
|
||||
}
|
||||
if (CheckAndSetNewPath(ref newPath, nameKey, isFolder))
|
||||
{
|
||||
value = newPath;
|
||||
GUI.changed = true;
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
else if (File.Exists(value) || Directory.Exists(value))
|
||||
{
|
||||
EditorUtility.RevealInFinder(value);
|
||||
}
|
||||
GUIUtility.keyboardControl = controlId;
|
||||
}
|
||||
break;
|
||||
case EventType.DragUpdated:
|
||||
case EventType.DragPerform:
|
||||
if (textRect.Contains(evt.mousePosition) && GUI.enabled)
|
||||
{
|
||||
if (DragAndDrop.paths.Length > 0)
|
||||
{
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
|
||||
string path = DragAndDrop.paths[0];
|
||||
path = PathAbsToStorage(path);
|
||||
if (CheckPath(path, isFolder))
|
||||
{
|
||||
if (evt.type == EventType.DragPerform)
|
||||
{
|
||||
UpdatePreviousPaths(nameKey, path);
|
||||
value = path;
|
||||
GUI.changed = true;
|
||||
DragAndDrop.AcceptDrag();
|
||||
DragAndDrop.activeControlID = 0;
|
||||
}
|
||||
else
|
||||
DragAndDrop.activeControlID = controlId;
|
||||
}
|
||||
else
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType.DragExited:
|
||||
if (GUI.enabled)
|
||||
{
|
||||
HandleUtility.Repaint();
|
||||
}
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Editor/Utils/FilePickerControl.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/Utils/FilePickerControl.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69e6c946494a9b2479ce96542339029c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
595
MVS/3DGS-Unity/Editor/Utils/KMeansClustering.cs
Normal file
595
MVS/3DGS-Unity/Editor/Utils/KMeansClustering.cs
Normal file
@@ -0,0 +1,595 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using System;
|
||||
using Unity.Burst;
|
||||
using Unity.Burst.Intrinsics;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Profiling;
|
||||
using Unity.Profiling.LowLevel;
|
||||
|
||||
namespace GaussianSplatting.Editor.Utils
|
||||
{
|
||||
// Implementation of "Mini Batch" k-means clustering ("Web-Scale K-Means Clustering", Sculley 2010)
|
||||
// using k-means++ for cluster initialization.
|
||||
[BurstCompile]
|
||||
public struct KMeansClustering
|
||||
{
|
||||
static ProfilerMarker s_ProfCalculate = new(ProfilerCategory.Render, "KMeans.Calculate", MarkerFlags.SampleGPU);
|
||||
static ProfilerMarker s_ProfPlusPlus = new(ProfilerCategory.Render, "KMeans.InitialPlusPlus", MarkerFlags.SampleGPU);
|
||||
static ProfilerMarker s_ProfInitialDistanceSum = new(ProfilerCategory.Render, "KMeans.Initialize.DistanceSum", MarkerFlags.SampleGPU);
|
||||
static ProfilerMarker s_ProfInitialPickPoint = new(ProfilerCategory.Render, "KMeans.Initialize.PickPoint", MarkerFlags.SampleGPU);
|
||||
static ProfilerMarker s_ProfInitialDistanceUpdate = new(ProfilerCategory.Render, "KMeans.Initialize.DistanceUpdate", MarkerFlags.SampleGPU);
|
||||
static ProfilerMarker s_ProfAssignClusters = new(ProfilerCategory.Render, "KMeans.AssignClusters", MarkerFlags.SampleGPU);
|
||||
static ProfilerMarker s_ProfUpdateMeans = new(ProfilerCategory.Render, "KMeans.UpdateMeans", MarkerFlags.SampleGPU);
|
||||
|
||||
public static bool Calculate(int dim, NativeArray<float> inputData, int batchSize, float passesOverData, Func<float,bool> progress, NativeArray<float> outClusterMeans, NativeArray<int> outDataLabels)
|
||||
{
|
||||
// Parameter checks
|
||||
if (dim < 1)
|
||||
throw new InvalidOperationException($"KMeans: dimensionality has to be >= 1, was {dim}");
|
||||
if (batchSize < 1)
|
||||
throw new InvalidOperationException($"KMeans: batch size has to be >= 1, was {batchSize}");
|
||||
if (passesOverData < 0.0001f)
|
||||
throw new InvalidOperationException($"KMeans: passes over data must be positive, was {passesOverData}");
|
||||
if (inputData.Length % dim != 0)
|
||||
throw new InvalidOperationException($"KMeans: input length must be multiple of dim={dim}, was {inputData.Length}");
|
||||
if (outClusterMeans.Length % dim != 0)
|
||||
throw new InvalidOperationException($"KMeans: output means length must be multiple of dim={dim}, was {outClusterMeans.Length}");
|
||||
int dataSize = inputData.Length / dim;
|
||||
int k = outClusterMeans.Length / dim;
|
||||
if (k < 1)
|
||||
throw new InvalidOperationException($"KMeans: cluster count length must be at least 1, was {k}");
|
||||
if (dataSize < k)
|
||||
throw new InvalidOperationException($"KMeans: input length ({inputData.Length}) must at least as long as clusters ({outClusterMeans.Length})");
|
||||
if (dataSize != outDataLabels.Length)
|
||||
throw new InvalidOperationException($"KMeans: output labels length must be {dataSize}, was {outDataLabels.Length}");
|
||||
|
||||
using var prof = s_ProfCalculate.Auto();
|
||||
batchSize = math.min(dataSize, batchSize);
|
||||
uint rngState = 1;
|
||||
|
||||
// Do initial cluster placement
|
||||
int initBatchSize = 10 * k;
|
||||
const int kInitAttempts = 3;
|
||||
if (!InitializeCentroids(dim, inputData, initBatchSize, ref rngState, kInitAttempts, outClusterMeans, progress))
|
||||
return false;
|
||||
|
||||
NativeArray<float> counts = new(k, Allocator.TempJob);
|
||||
|
||||
NativeArray<float> batchPoints = new(batchSize * dim, Allocator.TempJob);
|
||||
NativeArray<int> batchClusters = new(batchSize, Allocator.TempJob);
|
||||
|
||||
bool cancelled = false;
|
||||
for (float calcDone = 0.0f, calcLimit = dataSize * passesOverData; calcDone < calcLimit; calcDone += batchSize)
|
||||
{
|
||||
if (progress != null && !progress(0.3f + calcDone / calcLimit * 0.4f))
|
||||
{
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// generate a batch of random input points
|
||||
MakeRandomBatch(dim, inputData, ref rngState, batchPoints);
|
||||
|
||||
// find which of the current centroids each batch point is closest to
|
||||
{
|
||||
using var profPart = s_ProfAssignClusters.Auto();
|
||||
AssignClustersJob job = new AssignClustersJob
|
||||
{
|
||||
dim = dim,
|
||||
data = batchPoints,
|
||||
means = outClusterMeans,
|
||||
indexOffset = 0,
|
||||
clusters = batchClusters,
|
||||
};
|
||||
job.Schedule(batchSize, 1).Complete();
|
||||
}
|
||||
|
||||
// update the centroids
|
||||
{
|
||||
using var profPart = s_ProfUpdateMeans.Auto();
|
||||
UpdateCentroidsJob job = new UpdateCentroidsJob
|
||||
{
|
||||
m_Clusters = outClusterMeans,
|
||||
m_Dim = dim,
|
||||
m_Counts = counts,
|
||||
m_BatchSize = batchSize,
|
||||
m_BatchClusters = batchClusters,
|
||||
m_BatchPoints = batchPoints
|
||||
};
|
||||
job.Schedule().Complete();
|
||||
}
|
||||
}
|
||||
|
||||
// finally find out closest clusters for all input points
|
||||
{
|
||||
using var profPart = s_ProfAssignClusters.Auto();
|
||||
const int kAssignBatchCount = 256 * 1024;
|
||||
AssignClustersJob job = new AssignClustersJob
|
||||
{
|
||||
dim = dim,
|
||||
data = inputData,
|
||||
means = outClusterMeans,
|
||||
indexOffset = 0,
|
||||
clusters = outDataLabels,
|
||||
};
|
||||
for (int i = 0; i < dataSize; i += kAssignBatchCount)
|
||||
{
|
||||
if (progress != null && !progress(0.7f + (float) i / dataSize * 0.3f))
|
||||
{
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
job.indexOffset = i;
|
||||
job.Schedule(math.min(kAssignBatchCount, dataSize - i), 512).Complete();
|
||||
}
|
||||
}
|
||||
|
||||
counts.Dispose();
|
||||
batchPoints.Dispose();
|
||||
batchClusters.Dispose();
|
||||
return !cancelled;
|
||||
}
|
||||
|
||||
static unsafe float DistanceSquared(int dim, NativeArray<float> a, int aIndex, NativeArray<float> b, int bIndex)
|
||||
{
|
||||
aIndex *= dim;
|
||||
bIndex *= dim;
|
||||
float d = 0;
|
||||
if (X86.Avx.IsAvxSupported)
|
||||
{
|
||||
// 8x wide with AVX
|
||||
int i = 0;
|
||||
float* aptr = (float*) a.GetUnsafeReadOnlyPtr() + aIndex;
|
||||
float* bptr = (float*) b.GetUnsafeReadOnlyPtr() + bIndex;
|
||||
for (; i + 7 < dim; i += 8)
|
||||
{
|
||||
v256 va = X86.Avx.mm256_loadu_ps(aptr);
|
||||
v256 vb = X86.Avx.mm256_loadu_ps(bptr);
|
||||
v256 vd = X86.Avx.mm256_sub_ps(va, vb);
|
||||
vd = X86.Avx.mm256_mul_ps(vd, vd);
|
||||
|
||||
vd = X86.Avx.mm256_hadd_ps(vd, vd);
|
||||
d += vd.Float0 + vd.Float1 + vd.Float4 + vd.Float5;
|
||||
|
||||
aptr += 8;
|
||||
bptr += 8;
|
||||
}
|
||||
// remainder
|
||||
for (; i < dim; ++i)
|
||||
{
|
||||
float delta = *aptr - *bptr;
|
||||
d += delta * delta;
|
||||
aptr++;
|
||||
bptr++;
|
||||
}
|
||||
}
|
||||
else if (Arm.Neon.IsNeonSupported)
|
||||
{
|
||||
// 4x wide with NEON
|
||||
int i = 0;
|
||||
float* aptr = (float*) a.GetUnsafeReadOnlyPtr() + aIndex;
|
||||
float* bptr = (float*) b.GetUnsafeReadOnlyPtr() + bIndex;
|
||||
for (; i + 3 < dim; i += 4)
|
||||
{
|
||||
v128 va = Arm.Neon.vld1q_f32(aptr);
|
||||
v128 vb = Arm.Neon.vld1q_f32(bptr);
|
||||
v128 vd = Arm.Neon.vsubq_f32(va, vb);
|
||||
vd = Arm.Neon.vmulq_f32(vd, vd);
|
||||
|
||||
d += Arm.Neon.vaddvq_f32(vd);
|
||||
|
||||
aptr += 4;
|
||||
bptr += 4;
|
||||
}
|
||||
// remainder
|
||||
for (; i < dim; ++i)
|
||||
{
|
||||
float delta = *aptr - *bptr;
|
||||
d += delta * delta;
|
||||
aptr++;
|
||||
bptr++;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < dim; ++i)
|
||||
{
|
||||
float delta = a[aIndex + i] - b[bIndex + i];
|
||||
d += delta * delta;
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
static unsafe void CopyElem(int dim, NativeArray<float> src, int srcIndex, NativeArray<float> dst, int dstIndex)
|
||||
{
|
||||
UnsafeUtility.MemCpy((float*) dst.GetUnsafePtr() + dstIndex * dim,
|
||||
(float*) src.GetUnsafeReadOnlyPtr() + srcIndex * dim, dim * 4);
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
struct ClosestDistanceInitialJob : IJobParallelFor
|
||||
{
|
||||
public int dim;
|
||||
[ReadOnly] public NativeArray<float> data;
|
||||
[ReadOnly] public NativeArray<float> means;
|
||||
public NativeArray<float> minDistSq;
|
||||
public int pointIndex;
|
||||
public void Execute(int index)
|
||||
{
|
||||
if (index == pointIndex)
|
||||
return;
|
||||
minDistSq[index] = DistanceSquared(dim, data, index, means, 0);
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
struct ClosestDistanceUpdateJob : IJobParallelFor
|
||||
{
|
||||
public int dim;
|
||||
[ReadOnly] public NativeArray<float> data;
|
||||
[ReadOnly] public NativeArray<float> means;
|
||||
[ReadOnly] public NativeBitArray taken;
|
||||
public NativeArray<float> minDistSq;
|
||||
public int meanIndex;
|
||||
public void Execute(int index)
|
||||
{
|
||||
if (taken.IsSet(index))
|
||||
return;
|
||||
float distSq = DistanceSquared(dim, data, index, means, meanIndex);
|
||||
minDistSq[index] = math.min(minDistSq[index], distSq);
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
struct CalcDistSqJob : IJobParallelFor
|
||||
{
|
||||
public const int kBatchSize = 1024;
|
||||
public int dataSize;
|
||||
[ReadOnly] public NativeBitArray taken;
|
||||
[ReadOnly] public NativeArray<float> minDistSq;
|
||||
public NativeArray<float> partialSums;
|
||||
|
||||
public void Execute(int batchIndex)
|
||||
{
|
||||
int iStart = math.min(batchIndex * kBatchSize, dataSize);
|
||||
int iEnd = math.min((batchIndex + 1) * kBatchSize, dataSize);
|
||||
float sum = 0;
|
||||
for (int i = iStart; i < iEnd; ++i)
|
||||
{
|
||||
if (taken.IsSet(i))
|
||||
continue;
|
||||
sum += minDistSq[i];
|
||||
}
|
||||
|
||||
partialSums[batchIndex] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
static int PickPointIndex(int dataSize, ref NativeArray<float> partialSums, ref NativeBitArray taken, ref NativeArray<float> minDistSq, float rval)
|
||||
{
|
||||
// Skip batches until we hit the ones that might have value to pick from: binary search for the batch
|
||||
int indexL = 0;
|
||||
int indexR = partialSums.Length;
|
||||
while (indexL < indexR)
|
||||
{
|
||||
int indexM = (indexL + indexR) / 2;
|
||||
if (partialSums[indexM] < rval)
|
||||
indexL = indexM + 1;
|
||||
else
|
||||
indexR = indexM;
|
||||
}
|
||||
float acc = 0.0f;
|
||||
if (indexL > 0)
|
||||
{
|
||||
acc = partialSums[indexL-1];
|
||||
}
|
||||
|
||||
// Now search for the needed point
|
||||
int pointIndex = -1;
|
||||
for (int i = indexL * CalcDistSqJob.kBatchSize; i < dataSize; ++i)
|
||||
{
|
||||
if (taken.IsSet(i))
|
||||
continue;
|
||||
acc += minDistSq[i];
|
||||
if (acc >= rval)
|
||||
{
|
||||
pointIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have not found a point, pick the last available one
|
||||
if (pointIndex < 0)
|
||||
{
|
||||
for (int i = dataSize - 1; i >= 0; --i)
|
||||
{
|
||||
if (taken.IsSet(i))
|
||||
continue;
|
||||
pointIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pointIndex < 0)
|
||||
pointIndex = 0;
|
||||
|
||||
return pointIndex;
|
||||
}
|
||||
|
||||
static void KMeansPlusPlus(int dim, int k, NativeArray<float> data, NativeArray<float> means, NativeArray<float> minDistSq, ref uint rngState)
|
||||
{
|
||||
using var prof = s_ProfPlusPlus.Auto();
|
||||
|
||||
int dataSize = data.Length / dim;
|
||||
|
||||
NativeBitArray taken = new NativeBitArray(dataSize, Allocator.TempJob);
|
||||
|
||||
// Select first mean randomly
|
||||
int pointIndex = (int)(pcg_random(ref rngState) % dataSize);
|
||||
taken.Set(pointIndex, true);
|
||||
CopyElem(dim, data, pointIndex, means, 0);
|
||||
|
||||
// For each point: closest squared distance to the picked point
|
||||
{
|
||||
ClosestDistanceInitialJob job = new ClosestDistanceInitialJob
|
||||
{
|
||||
dim = dim,
|
||||
data = data,
|
||||
means = means,
|
||||
minDistSq = minDistSq,
|
||||
pointIndex = pointIndex
|
||||
};
|
||||
job.Schedule(dataSize, 1024).Complete();
|
||||
}
|
||||
|
||||
int sumBatches = (dataSize + CalcDistSqJob.kBatchSize - 1) / CalcDistSqJob.kBatchSize;
|
||||
NativeArray<float> partialSums = new(sumBatches, Allocator.TempJob);
|
||||
int resultCount = 1;
|
||||
while (resultCount < k)
|
||||
{
|
||||
// Find total sum of distances of not yet taken points
|
||||
float distSqTotal = 0;
|
||||
{
|
||||
using var profPart = s_ProfInitialDistanceSum.Auto();
|
||||
CalcDistSqJob job = new CalcDistSqJob
|
||||
{
|
||||
dataSize = dataSize,
|
||||
taken = taken,
|
||||
minDistSq = minDistSq,
|
||||
partialSums = partialSums
|
||||
};
|
||||
job.Schedule(sumBatches, 1).Complete();
|
||||
for (int i = 0; i < sumBatches; ++i)
|
||||
{
|
||||
distSqTotal += partialSums[i];
|
||||
partialSums[i] = distSqTotal;
|
||||
}
|
||||
}
|
||||
|
||||
// Pick a non-taken point, with a probability proportional
|
||||
// to distance: points furthest from any cluster are picked more.
|
||||
{
|
||||
using var profPart = s_ProfInitialPickPoint.Auto();
|
||||
float rval = pcg_hash_float(rngState + (uint)resultCount, distSqTotal);
|
||||
pointIndex = PickPointIndex(dataSize, ref partialSums, ref taken, ref minDistSq, rval);
|
||||
}
|
||||
|
||||
// Take this point as a new cluster mean
|
||||
taken.Set(pointIndex, true);
|
||||
CopyElem(dim, data, pointIndex, means, resultCount);
|
||||
++resultCount;
|
||||
|
||||
if (resultCount < k)
|
||||
{
|
||||
// Update distances of the points: since it tracks closest one,
|
||||
// calculate distance to the new cluster and update if smaller.
|
||||
using var profPart = s_ProfInitialDistanceUpdate.Auto();
|
||||
ClosestDistanceUpdateJob job = new ClosestDistanceUpdateJob
|
||||
{
|
||||
dim = dim,
|
||||
data = data,
|
||||
means = means,
|
||||
minDistSq = minDistSq,
|
||||
taken = taken,
|
||||
meanIndex = resultCount - 1
|
||||
};
|
||||
job.Schedule(dataSize, 256).Complete();
|
||||
}
|
||||
}
|
||||
|
||||
taken.Dispose();
|
||||
partialSums.Dispose();
|
||||
}
|
||||
|
||||
// For each data point, find cluster index that is closest to it
|
||||
[BurstCompile]
|
||||
struct AssignClustersJob : IJobParallelFor
|
||||
{
|
||||
public int indexOffset;
|
||||
public int dim;
|
||||
[ReadOnly] public NativeArray<float> data;
|
||||
[ReadOnly] public NativeArray<float> means;
|
||||
[NativeDisableParallelForRestriction] public NativeArray<int> clusters;
|
||||
[NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction] public NativeArray<float> distances;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
index += indexOffset;
|
||||
int meansCount = means.Length / dim;
|
||||
float minDist = float.MaxValue;
|
||||
int minIndex = 0;
|
||||
for (int i = 0; i < meansCount; ++i)
|
||||
{
|
||||
float dist = DistanceSquared(dim, data, index, means, i);
|
||||
if (dist < minDist)
|
||||
{
|
||||
minIndex = i;
|
||||
minDist = dist;
|
||||
}
|
||||
}
|
||||
clusters[index] = minIndex;
|
||||
if (distances.IsCreated)
|
||||
distances[index] = minDist;
|
||||
}
|
||||
}
|
||||
|
||||
static void MakeRandomBatch(int dim, NativeArray<float> inputData, ref uint rngState, NativeArray<float> outBatch)
|
||||
{
|
||||
var job = new MakeBatchJob
|
||||
{
|
||||
m_Dim = dim,
|
||||
m_InputData = inputData,
|
||||
m_Seed = pcg_random(ref rngState),
|
||||
m_OutBatch = outBatch
|
||||
};
|
||||
job.Schedule().Complete();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
struct MakeBatchJob : IJob
|
||||
{
|
||||
public int m_Dim;
|
||||
public NativeArray<float> m_InputData;
|
||||
public NativeArray<float> m_OutBatch;
|
||||
public uint m_Seed;
|
||||
public void Execute()
|
||||
{
|
||||
uint dataSize = (uint)(m_InputData.Length / m_Dim);
|
||||
int batchSize = m_OutBatch.Length / m_Dim;
|
||||
NativeHashSet<int> picked = new(batchSize, Allocator.Temp);
|
||||
while (picked.Count < batchSize)
|
||||
{
|
||||
int index = (int)(pcg_hash(m_Seed++) % dataSize);
|
||||
if (!picked.Contains(index))
|
||||
{
|
||||
CopyElem(m_Dim, m_InputData, index, m_OutBatch, picked.Count);
|
||||
picked.Add(index);
|
||||
}
|
||||
}
|
||||
picked.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
struct UpdateCentroidsJob : IJob
|
||||
{
|
||||
public int m_Dim;
|
||||
public int m_BatchSize;
|
||||
[ReadOnly] public NativeArray<int> m_BatchClusters;
|
||||
public NativeArray<float> m_Counts;
|
||||
[ReadOnly] public NativeArray<float> m_BatchPoints;
|
||||
public NativeArray<float> m_Clusters;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
for (int i = 0; i < m_BatchSize; ++i)
|
||||
{
|
||||
int clusterIndex = m_BatchClusters[i];
|
||||
m_Counts[clusterIndex]++;
|
||||
float alpha = 1.0f / m_Counts[clusterIndex];
|
||||
|
||||
for (int j = 0; j < m_Dim; ++j)
|
||||
{
|
||||
m_Clusters[clusterIndex * m_Dim + j] = math.lerp(m_Clusters[clusterIndex * m_Dim + j],
|
||||
m_BatchPoints[i * m_Dim + j], alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool InitializeCentroids(int dim, NativeArray<float> inputData, int initBatchSize, ref uint rngState, int initAttempts, NativeArray<float> outClusters, Func<float,bool> progress)
|
||||
{
|
||||
using var prof = s_ProfPlusPlus.Auto();
|
||||
|
||||
int k = outClusters.Length / dim;
|
||||
int dataSize = inputData.Length / dim;
|
||||
initBatchSize = math.min(initBatchSize, dataSize);
|
||||
|
||||
NativeArray<float> centroidBatch = new(initBatchSize * dim, Allocator.TempJob);
|
||||
NativeArray<float> validationBatch = new(initBatchSize * dim, Allocator.TempJob);
|
||||
MakeRandomBatch(dim, inputData, ref rngState, centroidBatch);
|
||||
MakeRandomBatch(dim, inputData, ref rngState, validationBatch);
|
||||
|
||||
NativeArray<int> tmpIndices = new(initBatchSize, Allocator.TempJob);
|
||||
NativeArray<float> tmpDistances = new(initBatchSize, Allocator.TempJob);
|
||||
NativeArray<float> curCentroids = new(k * dim, Allocator.TempJob);
|
||||
|
||||
float minDistSum = float.MaxValue;
|
||||
|
||||
bool cancelled = false;
|
||||
for (int ia = 0; ia < initAttempts; ++ia)
|
||||
{
|
||||
if (progress != null && !progress((float) ia / initAttempts * 0.3f))
|
||||
{
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
KMeansPlusPlus(dim, k, centroidBatch, curCentroids, tmpDistances, ref rngState);
|
||||
|
||||
{
|
||||
using var profPart = s_ProfAssignClusters.Auto();
|
||||
AssignClustersJob job = new AssignClustersJob
|
||||
{
|
||||
dim = dim,
|
||||
data = validationBatch,
|
||||
means = curCentroids,
|
||||
indexOffset = 0,
|
||||
clusters = tmpIndices,
|
||||
distances = tmpDistances
|
||||
};
|
||||
job.Schedule(initBatchSize, 1).Complete();
|
||||
}
|
||||
|
||||
float distSum = 0;
|
||||
foreach (var d in tmpDistances)
|
||||
distSum += d;
|
||||
|
||||
// is this centroid better?
|
||||
if (distSum < minDistSum)
|
||||
{
|
||||
minDistSum = distSum;
|
||||
outClusters.CopyFrom(curCentroids);
|
||||
}
|
||||
}
|
||||
|
||||
centroidBatch.Dispose();
|
||||
validationBatch.Dispose();
|
||||
tmpDistances.Dispose();
|
||||
tmpIndices.Dispose();
|
||||
curCentroids.Dispose();
|
||||
return !cancelled;
|
||||
}
|
||||
|
||||
// https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/
|
||||
static uint pcg_hash(uint input)
|
||||
{
|
||||
uint state = input * 747796405u + 2891336453u;
|
||||
uint word = ((state >> (int)((state >> 28) + 4u)) ^ state) * 277803737u;
|
||||
return (word >> 22) ^ word;
|
||||
}
|
||||
|
||||
static float pcg_hash_float(uint input, float upTo)
|
||||
{
|
||||
uint val = pcg_hash(input);
|
||||
float f = math.asfloat(0x3f800000 | (val >> 9)) - 1.0f;
|
||||
return f * upTo;
|
||||
}
|
||||
|
||||
static uint pcg_random(ref uint rng_state)
|
||||
{
|
||||
uint state = rng_state;
|
||||
rng_state = rng_state * 747796405u + 2891336453u;
|
||||
uint word = ((state >> (int)((state >> 28) + 4u)) ^ state) * 277803737u;
|
||||
return (word >> 22) ^ word;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Editor/Utils/KMeansClustering.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/Utils/KMeansClustering.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9cecadf9c980a4ad9a30d0e1ae09d16a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
107
MVS/3DGS-Unity/Editor/Utils/PLYFileReader.cs
Normal file
107
MVS/3DGS-Unity/Editor/Utils/PLYFileReader.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace GaussianSplatting.Editor.Utils
|
||||
{
|
||||
public static class PLYFileReader
|
||||
{
|
||||
public static void ReadFileHeader(string filePath, out int vertexCount, out int vertexStride, out List<string> attrNames)
|
||||
{
|
||||
vertexCount = 0;
|
||||
vertexStride = 0;
|
||||
attrNames = new List<string>();
|
||||
if (!File.Exists(filePath))
|
||||
return;
|
||||
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
ReadHeaderImpl(filePath, out vertexCount, out vertexStride, out attrNames, fs);
|
||||
}
|
||||
|
||||
static void ReadHeaderImpl(string filePath, out int vertexCount, out int vertexStride, out List<string> attrNames, FileStream fs)
|
||||
{
|
||||
// C# arrays and NativeArrays make it hard to have a "byte" array larger than 2GB :/
|
||||
if (fs.Length >= 2 * 1024 * 1024 * 1024L)
|
||||
throw new IOException($"PLY {filePath} read error: currently files larger than 2GB are not supported");
|
||||
|
||||
// read header
|
||||
vertexCount = 0;
|
||||
vertexStride = 0;
|
||||
attrNames = new List<string>();
|
||||
const int kMaxHeaderLines = 9000;
|
||||
for (int lineIdx = 0; lineIdx < kMaxHeaderLines; ++lineIdx)
|
||||
{
|
||||
var line = ReadLine(fs);
|
||||
if (line == "end_header" || line.Length == 0)
|
||||
break;
|
||||
var tokens = line.Split(' ');
|
||||
if (tokens.Length == 3 && tokens[0] == "element" && tokens[1] == "vertex")
|
||||
vertexCount = int.Parse(tokens[2]);
|
||||
if (tokens.Length == 3 && tokens[0] == "property")
|
||||
{
|
||||
ElementType type = tokens[1] switch
|
||||
{
|
||||
"float" => ElementType.Float,
|
||||
"double" => ElementType.Double,
|
||||
"uchar" => ElementType.UChar,
|
||||
_ => ElementType.None
|
||||
};
|
||||
vertexStride += TypeToSize(type);
|
||||
attrNames.Add(tokens[2]);
|
||||
}
|
||||
}
|
||||
//Debug.Log($"PLY {filePath} vtx {vertexCount} stride {vertexStride} attrs #{attrNames.Count} {string.Join(',', attrNames)}");
|
||||
}
|
||||
|
||||
public static void ReadFile(string filePath, out int vertexCount, out int vertexStride, out List<string> attrNames, out NativeArray<byte> vertices)
|
||||
{
|
||||
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
ReadHeaderImpl(filePath, out vertexCount, out vertexStride, out attrNames, fs);
|
||||
|
||||
vertices = new NativeArray<byte>(vertexCount * vertexStride, Allocator.Persistent);
|
||||
var readBytes = fs.Read(vertices);
|
||||
if (readBytes != vertices.Length)
|
||||
throw new IOException($"PLY {filePath} read error, expected {vertices.Length} data bytes got {readBytes}");
|
||||
}
|
||||
|
||||
enum ElementType
|
||||
{
|
||||
None,
|
||||
Float,
|
||||
Double,
|
||||
UChar
|
||||
}
|
||||
|
||||
static int TypeToSize(ElementType t)
|
||||
{
|
||||
return t switch
|
||||
{
|
||||
ElementType.None => 0,
|
||||
ElementType.Float => 4,
|
||||
ElementType.Double => 8,
|
||||
ElementType.UChar => 1,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(t), t, null)
|
||||
};
|
||||
}
|
||||
|
||||
static string ReadLine(FileStream fs)
|
||||
{
|
||||
var byteBuffer = new List<byte>();
|
||||
while (true)
|
||||
{
|
||||
int b = fs.ReadByte();
|
||||
if (b == -1 || b == '\n')
|
||||
break;
|
||||
byteBuffer.Add((byte)b);
|
||||
}
|
||||
// if line had CRLF line endings, remove the CR part
|
||||
if (byteBuffer.Count > 0 && byteBuffer.Last() == '\r')
|
||||
byteBuffer.RemoveAt(byteBuffer.Count-1);
|
||||
return Encoding.UTF8.GetString(byteBuffer.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Editor/Utils/PLYFileReader.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/Utils/PLYFileReader.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27964c85711004ddca73909489af2e2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
403
MVS/3DGS-Unity/Editor/Utils/TinyJsonParser.cs
Normal file
403
MVS/3DGS-Unity/Editor/Utils/TinyJsonParser.cs
Normal file
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
Embedded TinyJSON from https://github.com/pbhogan/TinyJSON (version 71fed96, 2019 May 10) directly here, with
|
||||
custom namespace wrapped around it so it does not clash with any other embedded TinyJSON. Original license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Alex Parker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
|
||||
namespace GaussianSplatting.Editor.Utils
|
||||
{
|
||||
// Really simple JSON parser in ~300 lines
|
||||
// - Attempts to parse JSON files with minimal GC allocation
|
||||
// - Nice and simple "[1,2,3]".FromJson<List<int>>() API
|
||||
// - Classes and structs can be parsed too!
|
||||
// class Foo { public int Value; }
|
||||
// "{\"Value\":10}".FromJson<Foo>()
|
||||
// - Can parse JSON without type information into Dictionary<string,object> and List<object> e.g.
|
||||
// "[1,2,3]".FromJson<object>().GetType() == typeof(List<object>)
|
||||
// "{\"Value\":10}".FromJson<object>().GetType() == typeof(Dictionary<string,object>)
|
||||
// - No JIT Emit support to support AOT compilation on iOS
|
||||
// - Attempts are made to NOT throw an exception if the JSON is corrupted or invalid: returns null instead.
|
||||
// - Only public fields and property setters on classes/structs will be written to
|
||||
//
|
||||
// Limitations:
|
||||
// - No JIT Emit support to parse structures quickly
|
||||
// - Limited to parsing <2GB JSON files (due to int.MaxValue)
|
||||
// - Parsing of abstract classes or interfaces is NOT supported and will throw an exception.
|
||||
public static class JSONParser
|
||||
{
|
||||
[ThreadStatic] static Stack<List<string>> splitArrayPool;
|
||||
[ThreadStatic] static StringBuilder stringBuilder;
|
||||
[ThreadStatic] static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfoCache;
|
||||
[ThreadStatic] static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfoCache;
|
||||
|
||||
public static T FromJson<T>(this string json)
|
||||
{
|
||||
// Initialize, if needed, the ThreadStatic variables
|
||||
if (propertyInfoCache == null) propertyInfoCache = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
|
||||
if (fieldInfoCache == null) fieldInfoCache = new Dictionary<Type, Dictionary<string, FieldInfo>>();
|
||||
if (stringBuilder == null) stringBuilder = new StringBuilder();
|
||||
if (splitArrayPool == null) splitArrayPool = new Stack<List<string>>();
|
||||
|
||||
//Remove all whitespace not within strings to make parsing simpler
|
||||
stringBuilder.Length = 0;
|
||||
for (int i = 0; i < json.Length; i++)
|
||||
{
|
||||
char c = json[i];
|
||||
if (c == '"')
|
||||
{
|
||||
i = AppendUntilStringEnd(true, i, json);
|
||||
continue;
|
||||
}
|
||||
if (char.IsWhiteSpace(c))
|
||||
continue;
|
||||
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
|
||||
//Parse the thing!
|
||||
return (T)ParseValue(typeof(T), stringBuilder.ToString());
|
||||
}
|
||||
|
||||
static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json)
|
||||
{
|
||||
stringBuilder.Append(json[startIdx]);
|
||||
for (int i = startIdx + 1; i < json.Length; i++)
|
||||
{
|
||||
if (json[i] == '\\')
|
||||
{
|
||||
if (appendEscapeCharacter)
|
||||
stringBuilder.Append(json[i]);
|
||||
stringBuilder.Append(json[i + 1]);
|
||||
i++;//Skip next character as it is escaped
|
||||
}
|
||||
else if (json[i] == '"')
|
||||
{
|
||||
stringBuilder.Append(json[i]);
|
||||
return i;
|
||||
}
|
||||
else
|
||||
stringBuilder.Append(json[i]);
|
||||
}
|
||||
return json.Length - 1;
|
||||
}
|
||||
|
||||
//Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
|
||||
static List<string> Split(string json)
|
||||
{
|
||||
List<string> splitArray = splitArrayPool.Count > 0 ? splitArrayPool.Pop() : new List<string>();
|
||||
splitArray.Clear();
|
||||
if (json.Length == 2)
|
||||
return splitArray;
|
||||
int parseDepth = 0;
|
||||
stringBuilder.Length = 0;
|
||||
for (int i = 1; i < json.Length - 1; i++)
|
||||
{
|
||||
switch (json[i])
|
||||
{
|
||||
case '[':
|
||||
case '{':
|
||||
parseDepth++;
|
||||
break;
|
||||
case ']':
|
||||
case '}':
|
||||
parseDepth--;
|
||||
break;
|
||||
case '"':
|
||||
i = AppendUntilStringEnd(true, i, json);
|
||||
continue;
|
||||
case ',':
|
||||
case ':':
|
||||
if (parseDepth == 0)
|
||||
{
|
||||
splitArray.Add(stringBuilder.ToString());
|
||||
stringBuilder.Length = 0;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
stringBuilder.Append(json[i]);
|
||||
}
|
||||
|
||||
splitArray.Add(stringBuilder.ToString());
|
||||
|
||||
return splitArray;
|
||||
}
|
||||
|
||||
internal static object ParseValue(Type type, string json)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
if (json.Length <= 2)
|
||||
return string.Empty;
|
||||
StringBuilder parseStringBuilder = new StringBuilder(json.Length);
|
||||
for (int i = 1; i < json.Length - 1; ++i)
|
||||
{
|
||||
if (json[i] == '\\' && i + 1 < json.Length - 1)
|
||||
{
|
||||
int j = "\"\\nrtbf/".IndexOf(json[i + 1]);
|
||||
if (j >= 0)
|
||||
{
|
||||
parseStringBuilder.Append("\"\\\n\r\t\b\f/"[j]);
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
if (json[i + 1] == 'u' && i + 5 < json.Length - 1)
|
||||
{
|
||||
UInt32 c = 0;
|
||||
if (UInt32.TryParse(json.Substring(i + 2, 4), System.Globalization.NumberStyles.AllowHexSpecifier, null, out c))
|
||||
{
|
||||
parseStringBuilder.Append((char)c);
|
||||
i += 5;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
parseStringBuilder.Append(json[i]);
|
||||
}
|
||||
return parseStringBuilder.ToString();
|
||||
}
|
||||
if (type.IsPrimitive)
|
||||
{
|
||||
var result = Convert.ChangeType(json, type, System.Globalization.CultureInfo.InvariantCulture);
|
||||
return result;
|
||||
}
|
||||
if (type == typeof(decimal))
|
||||
{
|
||||
decimal result;
|
||||
decimal.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out result);
|
||||
return result;
|
||||
}
|
||||
if (type == typeof(DateTime))
|
||||
{
|
||||
DateTime result;
|
||||
DateTime.TryParse(json.Replace("\"",""), System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out result);
|
||||
return result;
|
||||
}
|
||||
if (json == "null")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (type.IsEnum)
|
||||
{
|
||||
if (json[0] == '"')
|
||||
json = json.Substring(1, json.Length - 2);
|
||||
try
|
||||
{
|
||||
return Enum.Parse(type, json, false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (type.IsArray)
|
||||
{
|
||||
Type arrayType = type.GetElementType();
|
||||
if (json[0] != '[' || json[json.Length - 1] != ']')
|
||||
return null;
|
||||
|
||||
List<string> elems = Split(json);
|
||||
Array newArray = Array.CreateInstance(arrayType, elems.Count);
|
||||
for (int i = 0; i < elems.Count; i++)
|
||||
newArray.SetValue(ParseValue(arrayType, elems[i]), i);
|
||||
splitArrayPool.Push(elems);
|
||||
return newArray;
|
||||
}
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
|
||||
{
|
||||
Type listType = type.GetGenericArguments()[0];
|
||||
if (json[0] != '[' || json[json.Length - 1] != ']')
|
||||
return null;
|
||||
|
||||
List<string> elems = Split(json);
|
||||
var list = (IList)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count });
|
||||
for (int i = 0; i < elems.Count; i++)
|
||||
list.Add(ParseValue(listType, elems[i]));
|
||||
splitArrayPool.Push(elems);
|
||||
return list;
|
||||
}
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
{
|
||||
Type keyType, valueType;
|
||||
{
|
||||
Type[] args = type.GetGenericArguments();
|
||||
keyType = args[0];
|
||||
valueType = args[1];
|
||||
}
|
||||
|
||||
//Refuse to parse dictionary keys that aren't of type string
|
||||
if (keyType != typeof(string))
|
||||
return null;
|
||||
//Must be a valid dictionary element
|
||||
if (json[0] != '{' || json[json.Length - 1] != '}')
|
||||
return null;
|
||||
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
|
||||
List<string> elems = Split(json);
|
||||
if (elems.Count % 2 != 0)
|
||||
return null;
|
||||
|
||||
var dictionary = (IDictionary)type.GetConstructor(new Type[] { typeof(int) }).Invoke(new object[] { elems.Count / 2 });
|
||||
for (int i = 0; i < elems.Count; i += 2)
|
||||
{
|
||||
if (elems[i].Length <= 2)
|
||||
continue;
|
||||
string keyValue = elems[i].Substring(1, elems[i].Length - 2);
|
||||
object val = ParseValue(valueType, elems[i + 1]);
|
||||
dictionary[keyValue] = val;
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
if (type == typeof(object))
|
||||
{
|
||||
return ParseAnonymousValue(json);
|
||||
}
|
||||
if (json[0] == '{' && json[json.Length - 1] == '}')
|
||||
{
|
||||
return ParseObject(type, json);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static object ParseAnonymousValue(string json)
|
||||
{
|
||||
if (json.Length == 0)
|
||||
return null;
|
||||
if (json[0] == '{' && json[json.Length - 1] == '}')
|
||||
{
|
||||
List<string> elems = Split(json);
|
||||
if (elems.Count % 2 != 0)
|
||||
return null;
|
||||
var dict = new Dictionary<string, object>(elems.Count / 2);
|
||||
for (int i = 0; i < elems.Count; i += 2)
|
||||
dict[elems[i].Substring(1, elems[i].Length - 2)] = ParseAnonymousValue(elems[i + 1]);
|
||||
return dict;
|
||||
}
|
||||
if (json[0] == '[' && json[json.Length - 1] == ']')
|
||||
{
|
||||
List<string> items = Split(json);
|
||||
var finalList = new List<object>(items.Count);
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
finalList.Add(ParseAnonymousValue(items[i]));
|
||||
return finalList;
|
||||
}
|
||||
if (json[0] == '"' && json[json.Length - 1] == '"')
|
||||
{
|
||||
string str = json.Substring(1, json.Length - 2);
|
||||
return str.Replace("\\", string.Empty);
|
||||
}
|
||||
if (char.IsDigit(json[0]) || json[0] == '-')
|
||||
{
|
||||
if (json.Contains("."))
|
||||
{
|
||||
double result;
|
||||
double.TryParse(json, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out result);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
int result;
|
||||
int.TryParse(json, out result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (json == "true")
|
||||
return true;
|
||||
if (json == "false")
|
||||
return false;
|
||||
// handles json == "null" as well as invalid JSON
|
||||
return null;
|
||||
}
|
||||
|
||||
static Dictionary<string, T> CreateMemberNameDictionary<T>(T[] members) where T : MemberInfo
|
||||
{
|
||||
Dictionary<string, T> nameToMember = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
|
||||
for (int i = 0; i < members.Length; i++)
|
||||
{
|
||||
T member = members[i];
|
||||
if (member.IsDefined(typeof(IgnoreDataMemberAttribute), true))
|
||||
continue;
|
||||
|
||||
string name = member.Name;
|
||||
if (member.IsDefined(typeof(DataMemberAttribute), true))
|
||||
{
|
||||
DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), true);
|
||||
if (!string.IsNullOrEmpty(dataMemberAttribute.Name))
|
||||
name = dataMemberAttribute.Name;
|
||||
}
|
||||
|
||||
nameToMember.Add(name, member);
|
||||
}
|
||||
|
||||
return nameToMember;
|
||||
}
|
||||
|
||||
static object ParseObject(Type type, string json)
|
||||
{
|
||||
object instance = FormatterServices.GetUninitializedObject(type);
|
||||
|
||||
//The list is split into key/value pairs only, this means the split must be divisible by 2 to be valid JSON
|
||||
List<string> elems = Split(json);
|
||||
if (elems.Count % 2 != 0)
|
||||
return instance;
|
||||
|
||||
Dictionary<string, FieldInfo> nameToField;
|
||||
Dictionary<string, PropertyInfo> nameToProperty;
|
||||
if (!fieldInfoCache.TryGetValue(type, out nameToField))
|
||||
{
|
||||
nameToField = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
|
||||
fieldInfoCache.Add(type, nameToField);
|
||||
}
|
||||
if (!propertyInfoCache.TryGetValue(type, out nameToProperty))
|
||||
{
|
||||
nameToProperty = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
|
||||
propertyInfoCache.Add(type, nameToProperty);
|
||||
}
|
||||
|
||||
for (int i = 0; i < elems.Count; i += 2)
|
||||
{
|
||||
if (elems[i].Length <= 2)
|
||||
continue;
|
||||
string key = elems[i].Substring(1, elems[i].Length - 2);
|
||||
string value = elems[i + 1];
|
||||
|
||||
FieldInfo fieldInfo;
|
||||
PropertyInfo propertyInfo;
|
||||
if (nameToField.TryGetValue(key, out fieldInfo))
|
||||
fieldInfo.SetValue(instance, ParseValue(fieldInfo.FieldType, value));
|
||||
else if (nameToProperty.TryGetValue(key, out propertyInfo))
|
||||
propertyInfo.SetValue(instance, ParseValue(propertyInfo.PropertyType, value), null);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Editor/Utils/TinyJsonParser.cs.meta
Normal file
11
MVS/3DGS-Unity/Editor/Utils/TinyJsonParser.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9ea5041388393f459c378c31e4d7b1f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
21
MVS/3DGS-Unity/LICENSE.md
Normal file
21
MVS/3DGS-Unity/LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Aras Pranckevičius
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
7
MVS/3DGS-Unity/LICENSE.md.meta
Normal file
7
MVS/3DGS-Unity/LICENSE.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da286c32b8dba1744aecca8cb1ab4ad6
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
MVS/3DGS-Unity/Materials.meta
Normal file
8
MVS/3DGS-Unity/Materials.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78bfe028c2744c741bd4f94574de884a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
30
MVS/3DGS-Unity/Materials/BlackSkybox.mat
Normal file
30
MVS/3DGS-Unity/Materials/BlackSkybox.mat
Normal file
@@ -0,0 +1,30 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: BlackSkybox
|
||||
m_Shader: {fileID: 4800000, guid: a4867e5be68354ccda78062a92c74391, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs: []
|
||||
m_Ints: []
|
||||
m_Floats: []
|
||||
m_Colors:
|
||||
- _Color: {r: 0, g: 0, b: 0, a: 0}
|
||||
m_BuildTextureStacks: []
|
||||
8
MVS/3DGS-Unity/Materials/BlackSkybox.mat.meta
Normal file
8
MVS/3DGS-Unity/Materials/BlackSkybox.mat.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9c9951d4a35e4a54812fa0280fa548c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
MVS/3DGS-Unity/Runtime.meta
Normal file
8
MVS/3DGS-Unity/Runtime.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71627bcf67390da439d82a2a05a57bb4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
77
MVS/3DGS-Unity/Runtime/GaussianCutout.cs
Normal file
77
MVS/3DGS-Unity/Runtime/GaussianCutout.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GaussianSplatting.Runtime
|
||||
{
|
||||
public class GaussianCutout : MonoBehaviour
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
Ellipsoid,
|
||||
Box
|
||||
}
|
||||
|
||||
public Type m_Type = Type.Ellipsoid;
|
||||
public bool m_Invert = false;
|
||||
|
||||
public struct ShaderData // match GaussianCutoutShaderData in CS
|
||||
{
|
||||
public Matrix4x4 matrix;
|
||||
public uint typeAndFlags;
|
||||
}
|
||||
|
||||
public static ShaderData GetShaderData(GaussianCutout self, Matrix4x4 rendererMatrix)
|
||||
{
|
||||
ShaderData sd = default;
|
||||
if (self && self.isActiveAndEnabled)
|
||||
{
|
||||
var tr = self.transform;
|
||||
sd.matrix = tr.worldToLocalMatrix * rendererMatrix;
|
||||
sd.typeAndFlags = ((uint)self.m_Type) | (self.m_Invert ? 0x100u : 0u);
|
||||
}
|
||||
else
|
||||
{
|
||||
sd.typeAndFlags = ~0u;
|
||||
}
|
||||
return sd;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.matrix = transform.localToWorldMatrix;
|
||||
var color = Color.magenta;
|
||||
color.a = 0.2f;
|
||||
if (Selection.Contains(gameObject))
|
||||
color.a = 0.9f;
|
||||
else
|
||||
{
|
||||
// mid amount of alpha if a GS object that contains us as a cutout is selected
|
||||
var activeGo = Selection.activeGameObject;
|
||||
if (activeGo != null)
|
||||
{
|
||||
var activeSplat = activeGo.GetComponent<GaussianSplatRenderer>();
|
||||
if (activeSplat != null)
|
||||
{
|
||||
if (activeSplat.m_Cutouts != null && activeSplat.m_Cutouts.Contains(this))
|
||||
color.a = 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gizmos.color = color;
|
||||
if (m_Type == Type.Ellipsoid)
|
||||
{
|
||||
Gizmos.DrawWireSphere(Vector3.zero, 1.0f);
|
||||
}
|
||||
if (m_Type == Type.Box)
|
||||
{
|
||||
Gizmos.DrawWireCube(Vector3.zero, Vector3.one * 2);
|
||||
}
|
||||
}
|
||||
#endif // #if UNITY_EDITOR
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Runtime/GaussianCutout.cs.meta
Normal file
11
MVS/3DGS-Unity/Runtime/GaussianCutout.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c57a1c501bd05549ae157cc474bd4c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
247
MVS/3DGS-Unity/Runtime/GaussianSplatAsset.cs
Normal file
247
MVS/3DGS-Unity/Runtime/GaussianSplatAsset.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using System;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
|
||||
namespace GaussianSplatting.Runtime
|
||||
{
|
||||
public class GaussianSplatAsset : ScriptableObject
|
||||
{
|
||||
public const int kCurrentVersion = 2023_10_20;
|
||||
public const int kChunkSize = 256;
|
||||
public const int kTextureWidth = 2048; // allows up to 32M splats on desktop GPU (2k width x 16k height)
|
||||
public const int kMaxSplats = 8_600_000; // mostly due to 2GB GPU buffer size limit when exporting a splat (2GB / 248B is just over 8.6M)
|
||||
|
||||
[SerializeField] int m_FormatVersion;
|
||||
[SerializeField] int m_SplatCount;
|
||||
[SerializeField] Vector3 m_BoundsMin;
|
||||
[SerializeField] Vector3 m_BoundsMax;
|
||||
[SerializeField] Hash128 m_DataHash;
|
||||
|
||||
public int formatVersion => m_FormatVersion;
|
||||
public int splatCount => m_SplatCount;
|
||||
public Vector3 boundsMin => m_BoundsMin;
|
||||
public Vector3 boundsMax => m_BoundsMax;
|
||||
public Hash128 dataHash => m_DataHash;
|
||||
|
||||
// Match VECTOR_FMT_* in HLSL
|
||||
public enum VectorFormat
|
||||
{
|
||||
Float32, // 12 bytes: 32F.32F.32F
|
||||
Norm16, // 6 bytes: 16.16.16
|
||||
Norm11, // 4 bytes: 11.10.11
|
||||
Norm6 // 2 bytes: 6.5.5
|
||||
}
|
||||
|
||||
public static int GetVectorSize(VectorFormat fmt)
|
||||
{
|
||||
return fmt switch
|
||||
{
|
||||
VectorFormat.Float32 => 12,
|
||||
VectorFormat.Norm16 => 6,
|
||||
VectorFormat.Norm11 => 4,
|
||||
VectorFormat.Norm6 => 2,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(fmt), fmt, null)
|
||||
};
|
||||
}
|
||||
|
||||
public enum ColorFormat
|
||||
{
|
||||
Float32x4,
|
||||
Float16x4,
|
||||
Norm8x4,
|
||||
BC7,
|
||||
}
|
||||
public static int GetColorSize(ColorFormat fmt)
|
||||
{
|
||||
return fmt switch
|
||||
{
|
||||
ColorFormat.Float32x4 => 16,
|
||||
ColorFormat.Float16x4 => 8,
|
||||
ColorFormat.Norm8x4 => 4,
|
||||
ColorFormat.BC7 => 1,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(fmt), fmt, null)
|
||||
};
|
||||
}
|
||||
|
||||
public enum SHFormat
|
||||
{
|
||||
Float32,
|
||||
Float16,
|
||||
Norm11,
|
||||
Norm6,
|
||||
Cluster64k,
|
||||
Cluster32k,
|
||||
Cluster16k,
|
||||
Cluster8k,
|
||||
Cluster4k,
|
||||
}
|
||||
|
||||
public struct SHTableItemFloat32
|
||||
{
|
||||
public float3 sh1, sh2, sh3, sh4, sh5, sh6, sh7, sh8, sh9, shA, shB, shC, shD, shE, shF;
|
||||
public float3 shPadding; // pad to multiple of 16 bytes
|
||||
}
|
||||
public struct SHTableItemFloat16
|
||||
{
|
||||
public half3 sh1, sh2, sh3, sh4, sh5, sh6, sh7, sh8, sh9, shA, shB, shC, shD, shE, shF;
|
||||
public half3 shPadding; // pad to multiple of 16 bytes
|
||||
}
|
||||
public struct SHTableItemNorm11
|
||||
{
|
||||
public uint sh1, sh2, sh3, sh4, sh5, sh6, sh7, sh8, sh9, shA, shB, shC, shD, shE, shF;
|
||||
}
|
||||
public struct SHTableItemNorm6
|
||||
{
|
||||
public ushort sh1, sh2, sh3, sh4, sh5, sh6, sh7, sh8, sh9, shA, shB, shC, shD, shE, shF;
|
||||
public ushort shPadding; // pad to multiple of 4 bytes
|
||||
}
|
||||
|
||||
public void Initialize(int splats, VectorFormat formatPos, VectorFormat formatScale, ColorFormat formatColor, SHFormat formatSh, Vector3 bMin, Vector3 bMax, CameraInfo[] cameraInfos)
|
||||
{
|
||||
m_SplatCount = splats;
|
||||
m_FormatVersion = kCurrentVersion;
|
||||
m_PosFormat = formatPos;
|
||||
m_ScaleFormat = formatScale;
|
||||
m_ColorFormat = formatColor;
|
||||
m_SHFormat = formatSh;
|
||||
m_Cameras = cameraInfos;
|
||||
m_BoundsMin = bMin;
|
||||
m_BoundsMax = bMax;
|
||||
}
|
||||
|
||||
public void SetDataHash(Hash128 hash)
|
||||
{
|
||||
m_DataHash = hash;
|
||||
}
|
||||
|
||||
public void SetAssetFiles(TextAsset dataChunk, TextAsset dataPos, TextAsset dataOther, TextAsset dataColor, TextAsset dataSh)
|
||||
{
|
||||
m_ChunkData = dataChunk;
|
||||
m_PosData = dataPos;
|
||||
m_OtherData = dataOther;
|
||||
m_ColorData = dataColor;
|
||||
m_SHData = dataSh;
|
||||
}
|
||||
|
||||
public static int GetOtherSizeNoSHIndex(VectorFormat scaleFormat)
|
||||
{
|
||||
return 4 + GetVectorSize(scaleFormat);
|
||||
}
|
||||
|
||||
public static int GetSHCount(SHFormat fmt, int splatCount)
|
||||
{
|
||||
return fmt switch
|
||||
{
|
||||
SHFormat.Float32 => splatCount,
|
||||
SHFormat.Float16 => splatCount,
|
||||
SHFormat.Norm11 => splatCount,
|
||||
SHFormat.Norm6 => splatCount,
|
||||
SHFormat.Cluster64k => 64 * 1024,
|
||||
SHFormat.Cluster32k => 32 * 1024,
|
||||
SHFormat.Cluster16k => 16 * 1024,
|
||||
SHFormat.Cluster8k => 8 * 1024,
|
||||
SHFormat.Cluster4k => 4 * 1024,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(fmt), fmt, null)
|
||||
};
|
||||
}
|
||||
|
||||
public static (int,int) CalcTextureSize(int splatCount)
|
||||
{
|
||||
int width = kTextureWidth;
|
||||
int height = math.max(1, (splatCount + width - 1) / width);
|
||||
// our swizzle tiles are 16x16, so make texture multiple of that height
|
||||
int blockHeight = 16;
|
||||
height = (height + blockHeight - 1) / blockHeight * blockHeight;
|
||||
return (width, height);
|
||||
}
|
||||
|
||||
public static GraphicsFormat ColorFormatToGraphics(ColorFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
ColorFormat.Float32x4 => GraphicsFormat.R32G32B32A32_SFloat,
|
||||
ColorFormat.Float16x4 => GraphicsFormat.R16G16B16A16_SFloat,
|
||||
ColorFormat.Norm8x4 => GraphicsFormat.R8G8B8A8_UNorm,
|
||||
ColorFormat.BC7 => GraphicsFormat.RGBA_BC7_UNorm,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
|
||||
};
|
||||
}
|
||||
|
||||
public static long CalcPosDataSize(int splatCount, VectorFormat formatPos)
|
||||
{
|
||||
return splatCount * GetVectorSize(formatPos);
|
||||
}
|
||||
public static long CalcOtherDataSize(int splatCount, VectorFormat formatScale)
|
||||
{
|
||||
return splatCount * GetOtherSizeNoSHIndex(formatScale);
|
||||
}
|
||||
public static long CalcColorDataSize(int splatCount, ColorFormat formatColor)
|
||||
{
|
||||
var (width, height) = CalcTextureSize(splatCount);
|
||||
return width * height * GetColorSize(formatColor);
|
||||
}
|
||||
public static long CalcSHDataSize(int splatCount, SHFormat formatSh)
|
||||
{
|
||||
int shCount = GetSHCount(formatSh, splatCount);
|
||||
return formatSh switch
|
||||
{
|
||||
SHFormat.Float32 => shCount * UnsafeUtility.SizeOf<SHTableItemFloat32>(),
|
||||
SHFormat.Float16 => shCount * UnsafeUtility.SizeOf<SHTableItemFloat16>(),
|
||||
SHFormat.Norm11 => shCount * UnsafeUtility.SizeOf<SHTableItemNorm11>(),
|
||||
SHFormat.Norm6 => shCount * UnsafeUtility.SizeOf<SHTableItemNorm6>(),
|
||||
_ => shCount * UnsafeUtility.SizeOf<SHTableItemFloat16>() + splatCount * 2
|
||||
};
|
||||
}
|
||||
public static long CalcChunkDataSize(int splatCount)
|
||||
{
|
||||
int chunkCount = (splatCount + kChunkSize - 1) / kChunkSize;
|
||||
return chunkCount * UnsafeUtility.SizeOf<ChunkInfo>();
|
||||
}
|
||||
|
||||
[SerializeField] VectorFormat m_PosFormat = VectorFormat.Norm11;
|
||||
[SerializeField] VectorFormat m_ScaleFormat = VectorFormat.Norm11;
|
||||
[SerializeField] SHFormat m_SHFormat = SHFormat.Norm11;
|
||||
[SerializeField] ColorFormat m_ColorFormat;
|
||||
|
||||
[SerializeField] TextAsset m_PosData;
|
||||
[SerializeField] TextAsset m_ColorData;
|
||||
[SerializeField] TextAsset m_OtherData;
|
||||
[SerializeField] TextAsset m_SHData;
|
||||
// Chunk data is optional (if data formats are fully lossless then there's no chunking)
|
||||
[SerializeField] TextAsset m_ChunkData;
|
||||
|
||||
[SerializeField] CameraInfo[] m_Cameras;
|
||||
|
||||
public VectorFormat posFormat => m_PosFormat;
|
||||
public VectorFormat scaleFormat => m_ScaleFormat;
|
||||
public SHFormat shFormat => m_SHFormat;
|
||||
public ColorFormat colorFormat => m_ColorFormat;
|
||||
|
||||
public TextAsset posData => m_PosData;
|
||||
public TextAsset colorData => m_ColorData;
|
||||
public TextAsset otherData => m_OtherData;
|
||||
public TextAsset shData => m_SHData;
|
||||
public TextAsset chunkData => m_ChunkData;
|
||||
public CameraInfo[] cameras => m_Cameras;
|
||||
|
||||
public struct ChunkInfo
|
||||
{
|
||||
public uint colR, colG, colB, colA;
|
||||
public float2 posX, posY, posZ;
|
||||
public uint sclX, sclY, sclZ;
|
||||
public uint shR, shG, shB;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct CameraInfo
|
||||
{
|
||||
public Vector3 pos;
|
||||
public Vector3 axisX, axisY, axisZ;
|
||||
public float fov;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Runtime/GaussianSplatAsset.cs.meta
Normal file
11
MVS/3DGS-Unity/Runtime/GaussianSplatAsset.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33b71fae31e6c7d438e8566dc713e666
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
59
MVS/3DGS-Unity/Runtime/GaussianSplatHDRPPass.cs
Normal file
59
MVS/3DGS-Unity/Runtime/GaussianSplatHDRPPass.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#if GS_ENABLE_HDRP
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.HighDefinition;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
|
||||
namespace GaussianSplatting.Runtime
|
||||
{
|
||||
// Note: I have no idea what is the proper usage of CustomPass.
|
||||
// Code below "seems to work" but I'm just fumbling along, without understanding any of it.
|
||||
class GaussianSplatHDRPPass : CustomPass
|
||||
{
|
||||
RTHandle m_RenderTarget;
|
||||
|
||||
// It can be used to configure render targets and their clear state. Also to create temporary render target textures.
|
||||
// When empty this render pass will render to the active camera render target.
|
||||
// You should never call CommandBuffer.SetRenderTarget. Instead call <c>ConfigureTarget</c> and <c>ConfigureClear</c>.
|
||||
// The render pipeline will ensure target setup and clearing happens in an performance manner.
|
||||
protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
|
||||
{
|
||||
m_RenderTarget = RTHandles.Alloc(Vector2.one,
|
||||
colorFormat: GraphicsFormat.R16G16B16A16_SFloat, useDynamicScale: true,
|
||||
depthBufferBits: DepthBits.None, msaaSamples: MSAASamples.None,
|
||||
filterMode: FilterMode.Point, wrapMode: TextureWrapMode.Clamp, name: "_GaussianSplatRT");
|
||||
}
|
||||
|
||||
protected override void Execute(CustomPassContext ctx)
|
||||
{
|
||||
var cam = ctx.hdCamera.camera;
|
||||
|
||||
var system = GaussianSplatRenderSystem.instance;
|
||||
if (!system.GatherSplatsForCamera(cam))
|
||||
return;
|
||||
|
||||
ctx.cmd.SetGlobalTexture(m_RenderTarget.name, m_RenderTarget.nameID);
|
||||
CoreUtils.SetRenderTarget(ctx.cmd, m_RenderTarget, ctx.cameraDepthBuffer, ClearFlag.Color,
|
||||
new Color(0, 0, 0, 0));
|
||||
|
||||
// add sorting, view calc and drawing commands for each splat object
|
||||
Material matComposite =
|
||||
GaussianSplatRenderSystem.instance.SortAndRenderSplats(ctx.hdCamera.camera, ctx.cmd);
|
||||
|
||||
// compose
|
||||
ctx.cmd.BeginSample(GaussianSplatRenderSystem.s_ProfCompose);
|
||||
CoreUtils.SetRenderTarget(ctx.cmd, ctx.cameraColorBuffer, ClearFlag.None);
|
||||
CoreUtils.DrawFullScreen(ctx.cmd, matComposite, ctx.propertyBlock, shaderPassId: 0);
|
||||
ctx.cmd.EndSample(GaussianSplatRenderSystem.s_ProfCompose);
|
||||
}
|
||||
|
||||
protected override void Cleanup()
|
||||
{
|
||||
m_RenderTarget.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
MVS/3DGS-Unity/Runtime/GaussianSplatHDRPPass.cs.meta
Normal file
11
MVS/3DGS-Unity/Runtime/GaussianSplatHDRPPass.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f40f16e78da87c646826cc5335ccb1f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1070
MVS/3DGS-Unity/Runtime/GaussianSplatRenderer.cs
Normal file
1070
MVS/3DGS-Unity/Runtime/GaussianSplatRenderer.cs
Normal file
File diff suppressed because it is too large
Load Diff
17
MVS/3DGS-Unity/Runtime/GaussianSplatRenderer.cs.meta
Normal file
17
MVS/3DGS-Unity/Runtime/GaussianSplatRenderer.cs.meta
Normal file
@@ -0,0 +1,17 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c12d929a7f62c48adbe9ff45c9a33ff0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- m_ShaderSplats: {fileID: 4800000, guid: ed800126ae8844a67aad1974ddddd59c, type: 3}
|
||||
- m_ShaderComposite: {fileID: 4800000, guid: 7e184af7d01193a408eb916d8acafff9, type: 3}
|
||||
- m_ShaderDebugPoints: {fileID: 4800000, guid: b44409fc67214394f8f47e4e2648425e, type: 3}
|
||||
- m_ShaderDebugBoxes: {fileID: 4800000, guid: 4006f2680fd7c8b4cbcb881454c782be, type: 3}
|
||||
- m_CSSplatUtilities: {fileID: 7200000, guid: ec84f78b836bd4f96a105d6b804f08bd, type: 3}
|
||||
- m_CSFfxSort: {fileID: 7200000, guid: dec36776b6c843544a1f6f9b436a4474, type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
158
MVS/3DGS-Unity/Runtime/GaussianSplatURPFeature.cs
Normal file
158
MVS/3DGS-Unity/Runtime/GaussianSplatURPFeature.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#if GS_ENABLE_URP
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
using UnityEngine.Rendering.RenderGraphModule;
|
||||
#endif
|
||||
|
||||
namespace GaussianSplatting.Runtime
|
||||
{
|
||||
// Note: I have no idea what is the purpose of ScriptableRendererFeature vs ScriptableRenderPass, which one of those
|
||||
// is supposed to do resource management vs logic, etc. etc. Code below "seems to work" but I'm just fumbling along,
|
||||
// without understanding any of it.
|
||||
//
|
||||
// ReSharper disable once InconsistentNaming
|
||||
class GaussianSplatURPFeature : ScriptableRendererFeature
|
||||
{
|
||||
class GSRenderPass : ScriptableRenderPass
|
||||
{
|
||||
const string GaussianSplatRTName = "_GaussianSplatRT";
|
||||
#if !UNITY_6000_0_OR_NEWER
|
||||
RTHandle m_RenderTarget;
|
||||
internal ScriptableRenderer m_Renderer;
|
||||
internal CommandBuffer m_Cmb;
|
||||
#endif
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
#if !UNITY_6000_0_OR_NEWER
|
||||
m_RenderTarget?.Release();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
const string ProfilerTag = "GaussianSplatRenderGraph";
|
||||
static readonly ProfilingSampler s_profilingSampler = new(ProfilerTag);
|
||||
static readonly int s_gaussianSplatRT = Shader.PropertyToID(GaussianSplatRTName);
|
||||
|
||||
class PassData
|
||||
{
|
||||
internal UniversalCameraData CameraData;
|
||||
internal TextureHandle SourceTexture;
|
||||
internal TextureHandle SourceDepth;
|
||||
internal TextureHandle GaussianSplatRT;
|
||||
}
|
||||
|
||||
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
|
||||
{
|
||||
using var builder = renderGraph.AddUnsafePass(ProfilerTag, out PassData passData);
|
||||
|
||||
var cameraData = frameData.Get<UniversalCameraData>();
|
||||
var resourceData = frameData.Get<UniversalResourceData>();
|
||||
|
||||
RenderTextureDescriptor rtDesc = cameraData.cameraTargetDescriptor;
|
||||
rtDesc.depthBufferBits = 0;
|
||||
rtDesc.msaaSamples = 1;
|
||||
rtDesc.graphicsFormat = GraphicsFormat.R16G16B16A16_SFloat;
|
||||
var textureHandle = UniversalRenderer.CreateRenderGraphTexture(renderGraph, rtDesc, GaussianSplatRTName, true);
|
||||
|
||||
passData.CameraData = cameraData;
|
||||
passData.SourceTexture = resourceData.activeColorTexture;
|
||||
passData.SourceDepth = resourceData.activeDepthTexture;
|
||||
passData.GaussianSplatRT = textureHandle;
|
||||
|
||||
builder.UseTexture(resourceData.activeColorTexture, AccessFlags.ReadWrite);
|
||||
builder.UseTexture(resourceData.activeDepthTexture);
|
||||
builder.UseTexture(textureHandle, AccessFlags.Write);
|
||||
builder.AllowPassCulling(false);
|
||||
builder.SetRenderFunc(static (PassData data, UnsafeGraphContext context) =>
|
||||
{
|
||||
var commandBuffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
|
||||
using var _ = new ProfilingScope(commandBuffer, s_profilingSampler);
|
||||
commandBuffer.SetGlobalTexture(s_gaussianSplatRT, data.GaussianSplatRT);
|
||||
CoreUtils.SetRenderTarget(commandBuffer, data.GaussianSplatRT, data.SourceDepth, ClearFlag.Color, Color.clear);
|
||||
Material matComposite = GaussianSplatRenderSystem.instance.SortAndRenderSplats(data.CameraData.camera, commandBuffer);
|
||||
commandBuffer.BeginSample(GaussianSplatRenderSystem.s_ProfCompose);
|
||||
Blitter.BlitCameraTexture(commandBuffer, data.GaussianSplatRT, data.SourceTexture, matComposite, 0);
|
||||
commandBuffer.EndSample(GaussianSplatRenderSystem.s_ProfCompose);
|
||||
});
|
||||
}
|
||||
#else
|
||||
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
|
||||
{
|
||||
RenderTextureDescriptor rtDesc = renderingData.cameraData.cameraTargetDescriptor;
|
||||
rtDesc.depthBufferBits = 0;
|
||||
rtDesc.msaaSamples = 1;
|
||||
rtDesc.graphicsFormat = GraphicsFormat.R16G16B16A16_SFloat;
|
||||
RenderingUtils.ReAllocateIfNeeded(ref m_RenderTarget, rtDesc, FilterMode.Point, TextureWrapMode.Clamp, name: GaussianSplatRTName);
|
||||
cmd.SetGlobalTexture(m_RenderTarget.name, m_RenderTarget.nameID);
|
||||
|
||||
ConfigureTarget(m_RenderTarget, m_Renderer.cameraDepthTargetHandle);
|
||||
ConfigureClear(ClearFlag.Color, new Color(0,0,0,0));
|
||||
}
|
||||
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||
{
|
||||
if (m_Cmb == null)
|
||||
return;
|
||||
|
||||
// add sorting, view calc and drawing commands for each splat object
|
||||
Material matComposite = GaussianSplatRenderSystem.instance.SortAndRenderSplats(renderingData.cameraData.camera, m_Cmb);
|
||||
|
||||
// compose
|
||||
m_Cmb.BeginSample(GaussianSplatRenderSystem.s_ProfCompose);
|
||||
Blitter.BlitCameraTexture(m_Cmb, m_RenderTarget, m_Renderer.cameraColorTargetHandle, RenderBufferLoadAction.Load, RenderBufferStoreAction.Store, matComposite, 0);
|
||||
m_Cmb.EndSample(GaussianSplatRenderSystem.s_ProfCompose);
|
||||
context.ExecuteCommandBuffer(m_Cmb);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
GSRenderPass m_Pass;
|
||||
bool m_HasCamera;
|
||||
|
||||
public override void Create()
|
||||
{
|
||||
m_Pass = new GSRenderPass
|
||||
{
|
||||
renderPassEvent = RenderPassEvent.BeforeRenderingTransparents
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnCameraPreCull(ScriptableRenderer renderer, in CameraData cameraData)
|
||||
{
|
||||
m_HasCamera = false;
|
||||
var system = GaussianSplatRenderSystem.instance;
|
||||
if (!system.GatherSplatsForCamera(cameraData.camera))
|
||||
return;
|
||||
|
||||
#if !UNITY_6000_0_OR_NEWER
|
||||
CommandBuffer cmb = system.InitialClearCmdBuffer(cameraData.camera);
|
||||
m_Pass.m_Cmb = cmb;
|
||||
#endif
|
||||
m_HasCamera = true;
|
||||
}
|
||||
|
||||
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
||||
{
|
||||
if (!m_HasCamera)
|
||||
return;
|
||||
#if !UNITY_6000_0_OR_NEWER
|
||||
m_Pass.m_Renderer = renderer;
|
||||
#endif
|
||||
renderer.EnqueuePass(m_Pass);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
m_Pass?.Dispose();
|
||||
m_Pass = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // #if GS_ENABLE_URP
|
||||
17
MVS/3DGS-Unity/Runtime/GaussianSplatURPFeature.cs.meta
Normal file
17
MVS/3DGS-Unity/Runtime/GaussianSplatURPFeature.cs.meta
Normal file
@@ -0,0 +1,17 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01052c1a4da12064d8681d7c1800f94a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- m_ShaderSplats: {fileID: 4800000, guid: ed800126ae8844a67aad1974ddddd59c, type: 3}
|
||||
- m_ShaderComposite: {fileID: 4800000, guid: 7e184af7d01193a408eb916d8acafff9, type: 3}
|
||||
- m_ShaderDebugPoints: {fileID: 4800000, guid: b44409fc67214394f8f47e4e2648425e, type: 3}
|
||||
- m_ShaderDebugBoxes: {fileID: 4800000, guid: 4006f2680fd7c8b4cbcb881454c782be, type: 3}
|
||||
- m_CSSplatUtilities: {fileID: 7200000, guid: ec84f78b836bd4f96a105d6b804f08bd, type: 3}
|
||||
- m_CSFfxSort: {fileID: 7200000, guid: dec36776b6c843544a1f6f9b436a4474, type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
31
MVS/3DGS-Unity/Runtime/GaussianSplatting.asmdef
Normal file
31
MVS/3DGS-Unity/Runtime/GaussianSplatting.asmdef
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "GaussianSplatting",
|
||||
"rootNamespace": "GaussianSplatting.Runtime",
|
||||
"references": [
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||
"GUID:8992d429105beaf428dfc91fb5b9f531",
|
||||
"GUID:15fc0a57446b3144c949da3e2b9737a9",
|
||||
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
|
||||
"GUID:457756d89b35d2941b3e7b37b4ece6f1"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.render-pipelines.universal",
|
||||
"expression": "",
|
||||
"define": "GS_ENABLE_URP"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.render-pipelines.high-definition",
|
||||
"expression": "",
|
||||
"define": "GS_ENABLE_HDRP"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
MVS/3DGS-Unity/Runtime/GaussianSplatting.asmdef.meta
Normal file
7
MVS/3DGS-Unity/Runtime/GaussianSplatting.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b653174f8fcdcd49b4c9a6f1ca8c7c3
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
107
MVS/3DGS-Unity/Runtime/GaussianUtils.cs
Normal file
107
MVS/3DGS-Unity/Runtime/GaussianUtils.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace GaussianSplatting.Runtime
|
||||
{
|
||||
public static class GaussianUtils
|
||||
{
|
||||
public static float Sigmoid(float v)
|
||||
{
|
||||
return math.rcp(1.0f + math.exp(-v));
|
||||
}
|
||||
|
||||
public static float3 SH0ToColor(float3 dc0)
|
||||
{
|
||||
const float kSH_C0 = 0.2820948f;
|
||||
return dc0 * kSH_C0 + 0.5f;
|
||||
}
|
||||
|
||||
public static float3 LinearScale(float3 logScale)
|
||||
{
|
||||
return math.abs(math.exp(logScale));
|
||||
}
|
||||
|
||||
public static float SquareCentered01(float x)
|
||||
{
|
||||
x -= 0.5f;
|
||||
x *= x * math.sign(x);
|
||||
return x * 2.0f + 0.5f;
|
||||
}
|
||||
|
||||
public static float InvSquareCentered01(float x)
|
||||
{
|
||||
x -= 0.5f;
|
||||
x *= 0.5f;
|
||||
x = math.sqrt(math.abs(x)) * math.sign(x);
|
||||
return x + 0.5f;
|
||||
}
|
||||
|
||||
public static float4 NormalizeSwizzleRotation(float4 wxyz)
|
||||
{
|
||||
return math.normalize(wxyz).yzwx;
|
||||
}
|
||||
|
||||
// Returns three smallest quaternion components in xyz (normalized to 0..1 range), and index/3 of the largest one in w
|
||||
public static float4 PackSmallest3Rotation(float4 q)
|
||||
{
|
||||
// find biggest component
|
||||
float4 absQ = math.abs(q);
|
||||
int index = 0;
|
||||
float maxV = absQ.x;
|
||||
if (absQ.y > maxV)
|
||||
{
|
||||
index = 1;
|
||||
maxV = absQ.y;
|
||||
}
|
||||
if (absQ.z > maxV)
|
||||
{
|
||||
index = 2;
|
||||
maxV = absQ.z;
|
||||
}
|
||||
if (absQ.w > maxV)
|
||||
{
|
||||
index = 3;
|
||||
maxV = absQ.w;
|
||||
}
|
||||
|
||||
if (index == 0) q = q.yzwx;
|
||||
if (index == 1) q = q.xzwy;
|
||||
if (index == 2) q = q.xywz;
|
||||
|
||||
float3 three = q.xyz * (q.w >= 0 ? 1 : -1); // -1/sqrt2..+1/sqrt2 range
|
||||
three = (three * math.SQRT2) * 0.5f + 0.5f; // 0..1 range
|
||||
|
||||
return new float4(three, index / 3.0f);
|
||||
}
|
||||
|
||||
|
||||
// Based on https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
|
||||
// Insert two 0 bits after each of the 21 low bits of x
|
||||
static ulong MortonPart1By2(ulong x)
|
||||
{
|
||||
x &= 0x1fffff;
|
||||
x = (x ^ (x << 32)) & 0x1f00000000ffffUL;
|
||||
x = (x ^ (x << 16)) & 0x1f0000ff0000ffUL;
|
||||
x = (x ^ (x << 8)) & 0x100f00f00f00f00fUL;
|
||||
x = (x ^ (x << 4)) & 0x10c30c30c30c30c3UL;
|
||||
x = (x ^ (x << 2)) & 0x1249249249249249UL;
|
||||
return x;
|
||||
}
|
||||
// Encode three 21-bit integers into 3D Morton order
|
||||
public static ulong MortonEncode3(uint3 v)
|
||||
{
|
||||
return (MortonPart1By2(v.z) << 2) | (MortonPart1By2(v.y) << 1) | MortonPart1By2(v.x);
|
||||
}
|
||||
|
||||
// See GaussianSplatting.hlsl
|
||||
public static uint2 DecodeMorton2D_16x16(uint t)
|
||||
{
|
||||
t = (t & 0xFF) | ((t & 0xFE) << 7); // -EAFBGCHEAFBGCHD
|
||||
t &= 0x5555; // -E-F-G-H-A-B-C-D
|
||||
t = (t ^ (t >> 1)) & 0x3333; // --EF--GH--AB--CD
|
||||
t = (t ^ (t >> 2)) & 0x0f0f; // ----EFGH----ABCD
|
||||
return new uint2(t & 0xF, t >> 8); // --------EFGHABCD
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Runtime/GaussianUtils.cs.meta
Normal file
11
MVS/3DGS-Unity/Runtime/GaussianUtils.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff862528cafe3e243aa42978a6d284d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
200
MVS/3DGS-Unity/Runtime/GpuSorting.cs
Normal file
200
MVS/3DGS-Unity/Runtime/GpuSorting.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace GaussianSplatting.Runtime
|
||||
{
|
||||
// GPU (uint key, uint payload) 8 bit-LSD radix sort, using reduce-then-scan
|
||||
// Copyright Thomas Smith 2024, MIT license
|
||||
// https://github.com/b0nes164/GPUSorting
|
||||
|
||||
public class GpuSorting
|
||||
{
|
||||
//The size of a threadblock partition in the sort
|
||||
const uint DEVICE_RADIX_SORT_PARTITION_SIZE = 3840;
|
||||
|
||||
//The size of our radix in bits
|
||||
const uint DEVICE_RADIX_SORT_BITS = 8;
|
||||
|
||||
//Number of digits in our radix, 1 << DEVICE_RADIX_SORT_BITS
|
||||
const uint DEVICE_RADIX_SORT_RADIX = 256;
|
||||
|
||||
//Number of sorting passes required to sort a 32bit key, KEY_BITS / DEVICE_RADIX_SORT_BITS
|
||||
const uint DEVICE_RADIX_SORT_PASSES = 4;
|
||||
|
||||
//Keywords to enable for the shader
|
||||
private LocalKeyword m_keyUintKeyword;
|
||||
private LocalKeyword m_payloadUintKeyword;
|
||||
private LocalKeyword m_ascendKeyword;
|
||||
private LocalKeyword m_sortPairKeyword;
|
||||
private LocalKeyword m_vulkanKeyword;
|
||||
|
||||
public struct Args
|
||||
{
|
||||
public uint count;
|
||||
public GraphicsBuffer inputKeys;
|
||||
public GraphicsBuffer inputValues;
|
||||
public SupportResources resources;
|
||||
internal int workGroupCount;
|
||||
}
|
||||
|
||||
public struct SupportResources
|
||||
{
|
||||
public GraphicsBuffer altBuffer;
|
||||
public GraphicsBuffer altPayloadBuffer;
|
||||
public GraphicsBuffer passHistBuffer;
|
||||
public GraphicsBuffer globalHistBuffer;
|
||||
|
||||
public static SupportResources Load(uint count)
|
||||
{
|
||||
//This is threadBlocks * DEVICE_RADIX_SORT_RADIX
|
||||
uint scratchBufferSize = DivRoundUp(count, DEVICE_RADIX_SORT_PARTITION_SIZE) * DEVICE_RADIX_SORT_RADIX;
|
||||
uint reducedScratchBufferSize = DEVICE_RADIX_SORT_RADIX * DEVICE_RADIX_SORT_PASSES;
|
||||
|
||||
var target = GraphicsBuffer.Target.Structured;
|
||||
var resources = new SupportResources
|
||||
{
|
||||
altBuffer = new GraphicsBuffer(target, (int)count, 4) { name = "DeviceRadixAlt" },
|
||||
altPayloadBuffer = new GraphicsBuffer(target, (int)count, 4) { name = "DeviceRadixAltPayload" },
|
||||
passHistBuffer = new GraphicsBuffer(target, (int)scratchBufferSize, 4) { name = "DeviceRadixPassHistogram" },
|
||||
globalHistBuffer = new GraphicsBuffer(target, (int)reducedScratchBufferSize, 4) { name = "DeviceRadixGlobalHistogram" },
|
||||
};
|
||||
return resources;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
altBuffer?.Dispose();
|
||||
altPayloadBuffer?.Dispose();
|
||||
passHistBuffer?.Dispose();
|
||||
globalHistBuffer?.Dispose();
|
||||
|
||||
altBuffer = null;
|
||||
altPayloadBuffer = null;
|
||||
passHistBuffer = null;
|
||||
globalHistBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
readonly ComputeShader m_CS;
|
||||
readonly int m_kernelInitDeviceRadixSort = -1;
|
||||
readonly int m_kernelUpsweep = -1;
|
||||
readonly int m_kernelScan = -1;
|
||||
readonly int m_kernelDownsweep = -1;
|
||||
|
||||
readonly bool m_Valid;
|
||||
|
||||
public bool Valid => m_Valid;
|
||||
|
||||
public GpuSorting(ComputeShader cs)
|
||||
{
|
||||
m_CS = cs;
|
||||
if (cs)
|
||||
{
|
||||
m_kernelInitDeviceRadixSort = cs.FindKernel("InitDeviceRadixSort");
|
||||
m_kernelUpsweep = cs.FindKernel("Upsweep");
|
||||
m_kernelScan = cs.FindKernel("Scan");
|
||||
m_kernelDownsweep = cs.FindKernel("Downsweep");
|
||||
}
|
||||
|
||||
m_Valid = m_kernelInitDeviceRadixSort >= 0 &&
|
||||
m_kernelUpsweep >= 0 &&
|
||||
m_kernelScan >= 0 &&
|
||||
m_kernelDownsweep >= 0;
|
||||
if (m_Valid)
|
||||
{
|
||||
if (!cs.IsSupported(m_kernelInitDeviceRadixSort) ||
|
||||
!cs.IsSupported(m_kernelUpsweep) ||
|
||||
!cs.IsSupported(m_kernelScan) ||
|
||||
!cs.IsSupported(m_kernelDownsweep))
|
||||
{
|
||||
m_Valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
m_keyUintKeyword = new LocalKeyword(cs, "KEY_UINT");
|
||||
m_payloadUintKeyword = new LocalKeyword(cs, "PAYLOAD_UINT");
|
||||
m_ascendKeyword = new LocalKeyword(cs, "SHOULD_ASCEND");
|
||||
m_sortPairKeyword = new LocalKeyword(cs, "SORT_PAIRS");
|
||||
m_vulkanKeyword = new LocalKeyword(cs, "VULKAN");
|
||||
|
||||
cs.EnableKeyword(m_keyUintKeyword);
|
||||
cs.EnableKeyword(m_payloadUintKeyword);
|
||||
cs.EnableKeyword(m_ascendKeyword);
|
||||
cs.EnableKeyword(m_sortPairKeyword);
|
||||
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Vulkan)
|
||||
cs.EnableKeyword(m_vulkanKeyword);
|
||||
else
|
||||
cs.DisableKeyword(m_vulkanKeyword);
|
||||
}
|
||||
|
||||
static uint DivRoundUp(uint x, uint y) => (x + y - 1) / y;
|
||||
|
||||
//Can we remove the last 4 padding without breaking?
|
||||
struct SortConstants
|
||||
{
|
||||
public uint numKeys; // The number of keys to sort
|
||||
public uint radixShift; // The radix shift value for the current pass
|
||||
public uint threadBlocks; // threadBlocks
|
||||
public uint padding0; // Padding - unused
|
||||
}
|
||||
|
||||
public void Dispatch(CommandBuffer cmd, Args args)
|
||||
{
|
||||
Assert.IsTrue(Valid);
|
||||
|
||||
GraphicsBuffer srcKeyBuffer = args.inputKeys;
|
||||
GraphicsBuffer srcPayloadBuffer = args.inputValues;
|
||||
GraphicsBuffer dstKeyBuffer = args.resources.altBuffer;
|
||||
GraphicsBuffer dstPayloadBuffer = args.resources.altPayloadBuffer;
|
||||
|
||||
SortConstants constants = default;
|
||||
constants.numKeys = args.count;
|
||||
constants.threadBlocks = DivRoundUp(args.count, DEVICE_RADIX_SORT_PARTITION_SIZE);
|
||||
|
||||
// Setup overall constants
|
||||
cmd.SetComputeIntParam(m_CS, "e_numKeys", (int)constants.numKeys);
|
||||
cmd.SetComputeIntParam(m_CS, "e_threadBlocks", (int)constants.threadBlocks);
|
||||
|
||||
//Set statically located buffers
|
||||
//Upsweep
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelUpsweep, "b_passHist", args.resources.passHistBuffer);
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelUpsweep, "b_globalHist", args.resources.globalHistBuffer);
|
||||
|
||||
//Scan
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelScan, "b_passHist", args.resources.passHistBuffer);
|
||||
|
||||
//Downsweep
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelDownsweep, "b_passHist", args.resources.passHistBuffer);
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelDownsweep, "b_globalHist", args.resources.globalHistBuffer);
|
||||
|
||||
//Clear the global histogram
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelInitDeviceRadixSort, "b_globalHist", args.resources.globalHistBuffer);
|
||||
cmd.DispatchCompute(m_CS, m_kernelInitDeviceRadixSort, 1, 1, 1);
|
||||
|
||||
// Execute the sort algorithm in 8-bit increments
|
||||
for (constants.radixShift = 0; constants.radixShift < 32; constants.radixShift += DEVICE_RADIX_SORT_BITS)
|
||||
{
|
||||
cmd.SetComputeIntParam(m_CS, "e_radixShift", (int)constants.radixShift);
|
||||
|
||||
//Upsweep
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelUpsweep, "b_sort", srcKeyBuffer);
|
||||
cmd.DispatchCompute(m_CS, m_kernelUpsweep, (int)constants.threadBlocks, 1, 1);
|
||||
|
||||
// Scan
|
||||
cmd.DispatchCompute(m_CS, m_kernelScan, (int)DEVICE_RADIX_SORT_RADIX, 1, 1);
|
||||
|
||||
// Downsweep
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelDownsweep, "b_sort", srcKeyBuffer);
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelDownsweep, "b_sortPayload", srcPayloadBuffer);
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelDownsweep, "b_alt", dstKeyBuffer);
|
||||
cmd.SetComputeBufferParam(m_CS, m_kernelDownsweep, "b_altPayload", dstPayloadBuffer);
|
||||
cmd.DispatchCompute(m_CS, m_kernelDownsweep, (int)constants.threadBlocks, 1, 1);
|
||||
|
||||
// Swap
|
||||
(srcKeyBuffer, dstKeyBuffer) = (dstKeyBuffer, srcKeyBuffer);
|
||||
(srcPayloadBuffer, dstPayloadBuffer) = (dstPayloadBuffer, srcPayloadBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MVS/3DGS-Unity/Runtime/GpuSorting.cs.meta
Normal file
11
MVS/3DGS-Unity/Runtime/GpuSorting.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65a55f12dc9f42e4196260841dd87c15
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
MVS/3DGS-Unity/Shaders.meta
Normal file
8
MVS/3DGS-Unity/Shaders.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cbb533de67b91d45afad2ab53f7f03c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
44
MVS/3DGS-Unity/Shaders/BlackSkybox.shader
Normal file
44
MVS/3DGS-Unity/Shaders/BlackSkybox.shader
Normal file
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
Shader "Unlit/BlackSkybox"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_Color ("Color", Color) = (0,0,0,0)
|
||||
}
|
||||
SubShader
|
||||
{
|
||||
Pass
|
||||
{
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
struct appdata
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
};
|
||||
|
||||
v2f vert (appdata v)
|
||||
{
|
||||
v2f o;
|
||||
o.vertex = UnityObjectToClipPos(v.vertex);
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 _Color;
|
||||
|
||||
half4 frag (v2f i) : SV_Target
|
||||
{
|
||||
return _Color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
9
MVS/3DGS-Unity/Shaders/BlackSkybox.shader.meta
Normal file
9
MVS/3DGS-Unity/Shaders/BlackSkybox.shader.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4867e5be68354ccda78062a92c74391
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
531
MVS/3DGS-Unity/Shaders/DeviceRadixSort.hlsl
Normal file
531
MVS/3DGS-Unity/Shaders/DeviceRadixSort.hlsl
Normal file
@@ -0,0 +1,531 @@
|
||||
/******************************************************************************
|
||||
* DeviceRadixSort
|
||||
* Device Level 8-bit LSD Radix Sort using reduce then scan
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* Copyright Thomas Smith 5/17/2024
|
||||
* https://github.com/b0nes164/GPUSorting
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
******************************************************************************/
|
||||
#include "SortCommon.hlsl"
|
||||
|
||||
#define US_DIM 128U //The number of threads in a Upsweep threadblock
|
||||
#define SCAN_DIM 128U //The number of threads in a Scan threadblock
|
||||
|
||||
RWStructuredBuffer<uint> b_globalHist; //buffer holding device level offsets for each binning pass
|
||||
RWStructuredBuffer<uint> b_passHist; //buffer used to store reduced sums of partition tiles
|
||||
|
||||
groupshared uint g_us[RADIX * 2]; //Shared memory for upsweep
|
||||
groupshared uint g_scan[SCAN_DIM]; //Shared memory for the scan
|
||||
|
||||
//*****************************************************************************
|
||||
//INIT KERNEL
|
||||
//*****************************************************************************
|
||||
//Clear the global histogram, as we will be adding to it atomically
|
||||
[numthreads(1024, 1, 1)]
|
||||
void InitDeviceRadixSort(int3 id : SV_DispatchThreadID)
|
||||
{
|
||||
b_globalHist[id.x] = 0;
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//UPSWEEP KERNEL
|
||||
//*****************************************************************************
|
||||
//histogram, 64 threads to a histogram
|
||||
inline void HistogramDigitCounts(uint gtid, uint gid)
|
||||
{
|
||||
const uint histOffset = gtid / 64 * RADIX;
|
||||
const uint partitionEnd = gid == e_threadBlocks - 1 ?
|
||||
e_numKeys : (gid + 1) * PART_SIZE;
|
||||
for (uint i = gtid + gid * PART_SIZE; i < partitionEnd; i += US_DIM)
|
||||
{
|
||||
#if defined(KEY_UINT)
|
||||
InterlockedAdd(g_us[ExtractDigit(b_sort[i]) + histOffset], 1);
|
||||
#elif defined(KEY_INT)
|
||||
InterlockedAdd(g_us[ExtractDigit(IntToUint(b_sort[i])) + histOffset], 1);
|
||||
#elif defined(KEY_FLOAT)
|
||||
InterlockedAdd(g_us[ExtractDigit(FloatToUint(b_sort[i])) + histOffset], 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//reduce and pass to tile histogram
|
||||
inline void ReduceWriteDigitCounts(uint gtid, uint gid)
|
||||
{
|
||||
for (uint i = gtid; i < RADIX; i += US_DIM)
|
||||
{
|
||||
g_us[i] += g_us[i + RADIX];
|
||||
b_passHist[i * e_threadBlocks + gid] = g_us[i];
|
||||
g_us[i] += WavePrefixSum(g_us[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//Exclusive scan over digit counts, then atomically add to global hist
|
||||
inline void GlobalHistExclusiveScanWGE16(uint gtid, uint waveSize)
|
||||
{
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gtid < (RADIX / waveSize))
|
||||
{
|
||||
g_us[(gtid + 1) * waveSize - 1] +=
|
||||
WavePrefixSum(g_us[(gtid + 1) * waveSize - 1]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
//atomically add to global histogram
|
||||
const uint globalHistOffset = GlobalHistOffset();
|
||||
const uint laneMask = waveSize - 1;
|
||||
const uint circularLaneShift = WaveGetLaneIndex() + 1 & laneMask;
|
||||
for (uint i = gtid; i < RADIX; i += US_DIM)
|
||||
{
|
||||
const uint index = circularLaneShift + (i & ~laneMask);
|
||||
uint t = WaveGetLaneIndex() != laneMask ? g_us[i] : 0;
|
||||
if (i >= waveSize)
|
||||
t += WaveReadLaneAt(g_us[i - 1], 0);
|
||||
InterlockedAdd(b_globalHist[index + globalHistOffset], t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GlobalHistExclusiveScanWLT16(uint gtid, uint waveSize)
|
||||
{
|
||||
const uint globalHistOffset = GlobalHistOffset();
|
||||
if (gtid < waveSize)
|
||||
{
|
||||
const uint circularLaneShift = WaveGetLaneIndex() + 1 &
|
||||
waveSize - 1;
|
||||
InterlockedAdd(b_globalHist[circularLaneShift + globalHistOffset],
|
||||
circularLaneShift ? g_us[gtid] : 0);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
const uint laneLog = countbits(waveSize - 1);
|
||||
uint offset = laneLog;
|
||||
uint j = waveSize;
|
||||
for (; j < (RADIX >> 1); j <<= laneLog)
|
||||
{
|
||||
if (gtid < (RADIX >> offset))
|
||||
{
|
||||
g_us[((gtid + 1) << offset) - 1] +=
|
||||
WavePrefixSum(g_us[((gtid + 1) << offset) - 1]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
for (uint i = gtid + j; i < RADIX; i += US_DIM)
|
||||
{
|
||||
if ((i & ((j << laneLog) - 1)) >= j)
|
||||
{
|
||||
if (i < (j << laneLog))
|
||||
{
|
||||
InterlockedAdd(b_globalHist[i + globalHistOffset],
|
||||
WaveReadLaneAt(g_us[((i >> offset) << offset) - 1], 0) +
|
||||
((i & (j - 1)) ? g_us[i - 1] : 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((i + 1) & (j - 1))
|
||||
{
|
||||
g_us[i] +=
|
||||
WaveReadLaneAt(g_us[((i >> offset) << offset) - 1], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += laneLog;
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
//If RADIX is not a power of lanecount
|
||||
for (uint i = gtid + j; i < RADIX; i += US_DIM)
|
||||
{
|
||||
InterlockedAdd(b_globalHist[i + globalHistOffset],
|
||||
WaveReadLaneAt(g_us[((i >> offset) << offset) - 1], 0) +
|
||||
((i & (j - 1)) ? g_us[i - 1] : 0));
|
||||
}
|
||||
}
|
||||
|
||||
[numthreads(US_DIM, 1, 1)]
|
||||
void Upsweep(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
|
||||
{
|
||||
//get the wave size
|
||||
const uint waveSize = getWaveSize();
|
||||
|
||||
//clear shared memory
|
||||
const uint histsEnd = RADIX * 2;
|
||||
for (uint i = gtid.x; i < histsEnd; i += US_DIM)
|
||||
g_us[i] = 0;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
HistogramDigitCounts(gtid.x, gid.x);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
ReduceWriteDigitCounts(gtid.x, gid.x);
|
||||
|
||||
if (waveSize >= 16)
|
||||
GlobalHistExclusiveScanWGE16(gtid.x, waveSize);
|
||||
|
||||
if (waveSize < 16)
|
||||
GlobalHistExclusiveScanWLT16(gtid.x, waveSize);
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//SCAN KERNEL
|
||||
//*****************************************************************************
|
||||
inline void ExclusiveThreadBlockScanFullWGE16(
|
||||
uint gtid,
|
||||
uint laneMask,
|
||||
uint circularLaneShift,
|
||||
uint partEnd,
|
||||
uint deviceOffset,
|
||||
uint waveSize,
|
||||
inout uint reduction)
|
||||
{
|
||||
for (uint i = gtid; i < partEnd; i += SCAN_DIM)
|
||||
{
|
||||
g_scan[gtid] = b_passHist[i + deviceOffset];
|
||||
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gtid < SCAN_DIM / waveSize)
|
||||
{
|
||||
g_scan[(gtid + 1) * waveSize - 1] +=
|
||||
WavePrefixSum(g_scan[(gtid + 1) * waveSize - 1]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
uint t = (WaveGetLaneIndex() != laneMask ? g_scan[gtid] : 0) + reduction;
|
||||
if (gtid >= waveSize)
|
||||
t += WaveReadLaneAt(g_scan[gtid - 1], 0);
|
||||
b_passHist[circularLaneShift + (i & ~laneMask) + deviceOffset] = t;
|
||||
|
||||
reduction += g_scan[SCAN_DIM - 1];
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
}
|
||||
|
||||
inline void ExclusiveThreadBlockScanPartialWGE16(
|
||||
uint gtid,
|
||||
uint laneMask,
|
||||
uint circularLaneShift,
|
||||
uint partEnd,
|
||||
uint deviceOffset,
|
||||
uint waveSize,
|
||||
uint reduction)
|
||||
{
|
||||
uint i = gtid + partEnd;
|
||||
if (i < e_threadBlocks)
|
||||
g_scan[gtid] = b_passHist[deviceOffset + i];
|
||||
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gtid < SCAN_DIM / waveSize)
|
||||
{
|
||||
g_scan[(gtid + 1) * waveSize - 1] +=
|
||||
WavePrefixSum(g_scan[(gtid + 1) * waveSize - 1]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
const uint index = circularLaneShift + (i & ~laneMask);
|
||||
if (index < e_threadBlocks)
|
||||
{
|
||||
uint t = (WaveGetLaneIndex() != laneMask ? g_scan[gtid] : 0) + reduction;
|
||||
if (gtid >= waveSize)
|
||||
t += g_scan[(gtid & ~laneMask) - 1];
|
||||
b_passHist[index + deviceOffset] = t;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ExclusiveThreadBlockScanWGE16(uint gtid, uint gid, uint waveSize)
|
||||
{
|
||||
uint reduction = 0;
|
||||
const uint laneMask = waveSize - 1;
|
||||
const uint circularLaneShift = WaveGetLaneIndex() + 1 & laneMask;
|
||||
const uint partionsEnd = e_threadBlocks / SCAN_DIM * SCAN_DIM;
|
||||
const uint deviceOffset = gid * e_threadBlocks;
|
||||
|
||||
ExclusiveThreadBlockScanFullWGE16(
|
||||
gtid,
|
||||
laneMask,
|
||||
circularLaneShift,
|
||||
partionsEnd,
|
||||
deviceOffset,
|
||||
waveSize,
|
||||
reduction);
|
||||
|
||||
ExclusiveThreadBlockScanPartialWGE16(
|
||||
gtid,
|
||||
laneMask,
|
||||
circularLaneShift,
|
||||
partionsEnd,
|
||||
deviceOffset,
|
||||
waveSize,
|
||||
reduction);
|
||||
}
|
||||
|
||||
inline void ExclusiveThreadBlockScanFullWLT16(
|
||||
uint gtid,
|
||||
uint partitions,
|
||||
uint deviceOffset,
|
||||
uint laneLog,
|
||||
uint circularLaneShift,
|
||||
uint waveSize,
|
||||
inout uint reduction)
|
||||
{
|
||||
for (uint k = 0; k < partitions; ++k)
|
||||
{
|
||||
g_scan[gtid] = b_passHist[gtid + k * SCAN_DIM + deviceOffset];
|
||||
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (gtid < waveSize)
|
||||
{
|
||||
b_passHist[circularLaneShift + k * SCAN_DIM + deviceOffset] =
|
||||
(circularLaneShift ? g_scan[gtid] : 0) + reduction;
|
||||
}
|
||||
|
||||
uint offset = laneLog;
|
||||
uint j = waveSize;
|
||||
for (; j < (SCAN_DIM >> 1); j <<= laneLog)
|
||||
{
|
||||
if (gtid < (SCAN_DIM >> offset))
|
||||
{
|
||||
g_scan[((gtid + 1) << offset) - 1] +=
|
||||
WavePrefixSum(g_scan[((gtid + 1) << offset) - 1]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if ((gtid & ((j << laneLog) - 1)) >= j)
|
||||
{
|
||||
if (gtid < (j << laneLog))
|
||||
{
|
||||
b_passHist[gtid + k * SCAN_DIM + deviceOffset] =
|
||||
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0) +
|
||||
((gtid & (j - 1)) ? g_scan[gtid - 1] : 0) + reduction;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((gtid + 1) & (j - 1))
|
||||
{
|
||||
g_scan[gtid] +=
|
||||
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += laneLog;
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
//If SCAN_DIM is not a power of lanecount
|
||||
for (uint i = gtid + j; i < SCAN_DIM; i += SCAN_DIM)
|
||||
{
|
||||
b_passHist[i + k * SCAN_DIM + deviceOffset] =
|
||||
WaveReadLaneAt(g_scan[((i >> offset) << offset) - 1], 0) +
|
||||
((i & (j - 1)) ? g_scan[i - 1] : 0) + reduction;
|
||||
}
|
||||
|
||||
reduction += WaveReadLaneAt(g_scan[SCAN_DIM - 1], 0) +
|
||||
WaveReadLaneAt(g_scan[(((SCAN_DIM - 1) >> offset) << offset) - 1], 0);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
}
|
||||
|
||||
inline void ExclusiveThreadBlockScanParitalWLT16(
|
||||
uint gtid,
|
||||
uint partitions,
|
||||
uint deviceOffset,
|
||||
uint laneLog,
|
||||
uint circularLaneShift,
|
||||
uint waveSize,
|
||||
uint reduction)
|
||||
{
|
||||
const uint finalPartSize = e_threadBlocks - partitions * SCAN_DIM;
|
||||
if (gtid < finalPartSize)
|
||||
{
|
||||
g_scan[gtid] = b_passHist[gtid + partitions * SCAN_DIM + deviceOffset];
|
||||
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (gtid < waveSize && circularLaneShift < finalPartSize)
|
||||
{
|
||||
b_passHist[circularLaneShift + partitions * SCAN_DIM + deviceOffset] =
|
||||
(circularLaneShift ? g_scan[gtid] : 0) + reduction;
|
||||
}
|
||||
|
||||
uint offset = laneLog;
|
||||
for (uint j = waveSize; j < finalPartSize; j <<= laneLog)
|
||||
{
|
||||
if (gtid < (finalPartSize >> offset))
|
||||
{
|
||||
g_scan[((gtid + 1) << offset) - 1] +=
|
||||
WavePrefixSum(g_scan[((gtid + 1) << offset) - 1]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if ((gtid & ((j << laneLog) - 1)) >= j && gtid < finalPartSize)
|
||||
{
|
||||
if (gtid < (j << laneLog))
|
||||
{
|
||||
b_passHist[gtid + partitions * SCAN_DIM + deviceOffset] =
|
||||
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0) +
|
||||
((gtid & (j - 1)) ? g_scan[gtid - 1] : 0) + reduction;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((gtid + 1) & (j - 1))
|
||||
{
|
||||
g_scan[gtid] +=
|
||||
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += laneLog;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ExclusiveThreadBlockScanWLT16(uint gtid, uint gid, uint waveSize)
|
||||
{
|
||||
uint reduction = 0;
|
||||
const uint partitions = e_threadBlocks / SCAN_DIM;
|
||||
const uint deviceOffset = gid * e_threadBlocks;
|
||||
const uint laneLog = countbits(waveSize - 1);
|
||||
const uint circularLaneShift = WaveGetLaneIndex() + 1 & waveSize - 1;
|
||||
|
||||
ExclusiveThreadBlockScanFullWLT16(
|
||||
gtid,
|
||||
partitions,
|
||||
deviceOffset,
|
||||
laneLog,
|
||||
circularLaneShift,
|
||||
waveSize,
|
||||
reduction);
|
||||
|
||||
ExclusiveThreadBlockScanParitalWLT16(
|
||||
gtid,
|
||||
partitions,
|
||||
deviceOffset,
|
||||
laneLog,
|
||||
circularLaneShift,
|
||||
waveSize,
|
||||
reduction);
|
||||
}
|
||||
|
||||
//Scan does not need flattening of gids
|
||||
[numthreads(SCAN_DIM, 1, 1)]
|
||||
void Scan(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
|
||||
{
|
||||
const uint waveSize = getWaveSize();
|
||||
if (waveSize >= 16)
|
||||
ExclusiveThreadBlockScanWGE16(gtid.x, gid.x, waveSize);
|
||||
|
||||
if (waveSize < 16)
|
||||
ExclusiveThreadBlockScanWLT16(gtid.x, gid.x, waveSize);
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//DOWNSWEEP KERNEL
|
||||
//*****************************************************************************
|
||||
inline void LoadThreadBlockReductions(uint gtid, uint gid, uint exclusiveHistReduction)
|
||||
{
|
||||
if (gtid < RADIX)
|
||||
{
|
||||
g_d[gtid + PART_SIZE] = b_globalHist[gtid + GlobalHistOffset()] +
|
||||
b_passHist[gtid * e_threadBlocks + gid] - exclusiveHistReduction;
|
||||
}
|
||||
}
|
||||
|
||||
[numthreads(D_DIM, 1, 1)]
|
||||
void Downsweep(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
|
||||
{
|
||||
KeyStruct keys;
|
||||
OffsetStruct offsets;
|
||||
const uint waveSize = getWaveSize();
|
||||
|
||||
ClearWaveHists(gtid.x, waveSize);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gid.x < e_threadBlocks - 1)
|
||||
{
|
||||
if (waveSize >= 16)
|
||||
keys = LoadKeysWGE16(gtid.x, waveSize, gid.x);
|
||||
|
||||
if (waveSize < 16)
|
||||
keys = LoadKeysWLT16(gtid.x, waveSize, gid.x, SerialIterations(waveSize));
|
||||
}
|
||||
|
||||
if (gid.x == e_threadBlocks - 1)
|
||||
{
|
||||
if (waveSize >= 16)
|
||||
keys = LoadKeysPartialWGE16(gtid.x, waveSize, gid.x);
|
||||
|
||||
if (waveSize < 16)
|
||||
keys = LoadKeysPartialWLT16(gtid.x, waveSize, gid.x, SerialIterations(waveSize));
|
||||
}
|
||||
|
||||
uint exclusiveHistReduction;
|
||||
|
||||
if (waveSize >= 16)
|
||||
{
|
||||
offsets = RankKeysWGE16(waveSize, getWaveIndex(gtid.x, waveSize) * RADIX, keys);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
uint histReduction;
|
||||
if (gtid.x < RADIX)
|
||||
{
|
||||
histReduction = WaveHistInclusiveScanCircularShiftWGE16(gtid.x, waveSize);
|
||||
histReduction += WavePrefixSum(histReduction); //take advantage of barrier to begin scan
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
WaveHistReductionExclusiveScanWGE16(gtid.x, waveSize, histReduction);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
UpdateOffsetsWGE16(gtid.x, waveSize, offsets, keys);
|
||||
if (gtid.x < RADIX)
|
||||
exclusiveHistReduction = g_d[gtid.x]; //take advantage of barrier to grab value
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
|
||||
if (waveSize < 16)
|
||||
{
|
||||
offsets = RankKeysWLT16(waveSize, getWaveIndex(gtid.x, waveSize), keys, SerialIterations(waveSize));
|
||||
|
||||
if (gtid.x < HALF_RADIX)
|
||||
{
|
||||
uint histReduction = WaveHistInclusiveScanCircularShiftWLT16(gtid.x);
|
||||
g_d[gtid.x] = histReduction + (histReduction << 16); //take advantage of barrier to begin scan
|
||||
}
|
||||
|
||||
WaveHistReductionExclusiveScanWLT16(gtid.x);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
UpdateOffsetsWLT16(gtid.x, waveSize, SerialIterations(waveSize), offsets, keys);
|
||||
if (gtid.x < RADIX) //take advantage of barrier to grab value
|
||||
exclusiveHistReduction = g_d[gtid.x >> 1] >> ((gtid.x & 1) ? 16 : 0) & 0xffff;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
|
||||
ScatterKeysShared(offsets, keys);
|
||||
LoadThreadBlockReductions(gtid.x, gid.x, exclusiveHistReduction);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gid.x < e_threadBlocks - 1)
|
||||
ScatterDevice(gtid.x, waveSize, gid.x, offsets);
|
||||
|
||||
if (gid.x == e_threadBlocks - 1)
|
||||
ScatterDevicePartial(gtid.x, waveSize, gid.x, offsets);
|
||||
}
|
||||
7
MVS/3DGS-Unity/Shaders/DeviceRadixSort.hlsl.meta
Normal file
7
MVS/3DGS-Unity/Shaders/DeviceRadixSort.hlsl.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02209b8d952e7fc418492b88139826fd
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
MVS/3DGS-Unity/Shaders/GaussianComposite.shader
Normal file
45
MVS/3DGS-Unity/Shaders/GaussianComposite.shader
Normal file
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
Shader "Hidden/Gaussian Splatting/Composite"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
Pass
|
||||
{
|
||||
ZWrite Off
|
||||
ZTest Always
|
||||
Cull Off
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma require compute
|
||||
#pragma use_dxc
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
};
|
||||
|
||||
v2f vert (uint vtxID : SV_VertexID)
|
||||
{
|
||||
v2f o;
|
||||
float2 quadPos = float2(vtxID&1, (vtxID>>1)&1) * 4.0 - 1.0;
|
||||
o.vertex = float4(quadPos, 1, 1);
|
||||
return o;
|
||||
}
|
||||
|
||||
Texture2D _GaussianSplatRT;
|
||||
|
||||
half4 frag (v2f i) : SV_Target
|
||||
{
|
||||
half4 col = _GaussianSplatRT.Load(int3(i.vertex.xy, 0));
|
||||
col.rgb = GammaToLinearSpace(col.rgb);
|
||||
col.a = saturate(col.a * 1.5);
|
||||
return col;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
9
MVS/3DGS-Unity/Shaders/GaussianComposite.shader.meta
Normal file
9
MVS/3DGS-Unity/Shaders/GaussianComposite.shader.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e184af7d01193a408eb916d8acafff9
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
102
MVS/3DGS-Unity/Shaders/GaussianDebugRenderBoxes.shader
Normal file
102
MVS/3DGS-Unity/Shaders/GaussianDebugRenderBoxes.shader
Normal file
@@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
Shader "Gaussian Splatting/Debug/Render Boxes"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
|
||||
|
||||
Pass
|
||||
{
|
||||
ZWrite Off
|
||||
Blend OneMinusDstAlpha One
|
||||
Cull Front
|
||||
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma require compute
|
||||
#pragma use_dxc
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
#include "GaussianSplatting.hlsl"
|
||||
|
||||
StructuredBuffer<uint> _OrderBuffer;
|
||||
|
||||
bool _DisplayChunks;
|
||||
|
||||
struct v2f
|
||||
{
|
||||
half4 col : COLOR0;
|
||||
float4 vertex : SV_POSITION;
|
||||
};
|
||||
|
||||
float _SplatScale;
|
||||
float _SplatOpacityScale;
|
||||
|
||||
// based on https://iquilezles.org/articles/palettes/
|
||||
// cosine based palette, 4 vec3 params
|
||||
half3 palette(float t, half3 a, half3 b, half3 c, half3 d)
|
||||
{
|
||||
return a + b*cos(6.28318*(c*t+d));
|
||||
}
|
||||
|
||||
v2f vert (uint vtxID : SV_VertexID, uint instID : SV_InstanceID)
|
||||
{
|
||||
v2f o;
|
||||
bool chunks = _DisplayChunks;
|
||||
uint idx = vtxID;
|
||||
float3 localPos = float3(idx&1, (idx>>1)&1, (idx>>2)&1) * 2.0 - 1.0;
|
||||
|
||||
float3 centerWorldPos = 0;
|
||||
|
||||
if (!chunks)
|
||||
{
|
||||
// display splat boxes
|
||||
instID = _OrderBuffer[instID];
|
||||
SplatData splat = LoadSplatData(instID);
|
||||
|
||||
float4 boxRot = splat.rot;
|
||||
float3 boxSize = splat.scale;
|
||||
boxSize *= _SplatScale;
|
||||
|
||||
float3x3 splatRotScaleMat = CalcMatrixFromRotationScale(boxRot, boxSize);
|
||||
splatRotScaleMat = mul((float3x3)unity_ObjectToWorld, splatRotScaleMat);
|
||||
|
||||
centerWorldPos = splat.pos;
|
||||
centerWorldPos = mul(unity_ObjectToWorld, float4(centerWorldPos,1)).xyz;
|
||||
|
||||
o.col.rgb = saturate(splat.sh.col);
|
||||
o.col.a = saturate(splat.opacity * _SplatOpacityScale);
|
||||
|
||||
localPos = mul(splatRotScaleMat, localPos) * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// display chunk boxes
|
||||
localPos = localPos * 0.5 + 0.5;
|
||||
SplatChunkInfo chunk = _SplatChunks[instID];
|
||||
float3 posMin = float3(chunk.posX.x, chunk.posY.x, chunk.posZ.x);
|
||||
float3 posMax = float3(chunk.posX.y, chunk.posY.y, chunk.posZ.y);
|
||||
|
||||
localPos = lerp(posMin, posMax, localPos);
|
||||
localPos = mul(unity_ObjectToWorld, float4(localPos,1)).xyz;
|
||||
|
||||
o.col.rgb = palette((float)instID / (float)_SplatChunkCount, half3(0.5,0.5,0.5), half3(0.5,0.5,0.5), half3(1,1,1), half3(0.0, 0.33, 0.67));
|
||||
o.col.a = 0.1;
|
||||
}
|
||||
|
||||
float3 worldPos = centerWorldPos + localPos;
|
||||
o.vertex = UnityWorldToClipPos(worldPos);
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 frag (v2f i) : SV_Target
|
||||
{
|
||||
half4 res = half4(i.col.rgb * i.col.a, i.col.a);
|
||||
return res;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4006f2680fd7c8b4cbcb881454c782be
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
65
MVS/3DGS-Unity/Shaders/GaussianDebugRenderPoints.shader
Normal file
65
MVS/3DGS-Unity/Shaders/GaussianDebugRenderPoints.shader
Normal file
@@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
Shader "Gaussian Splatting/Debug/Render Points"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
|
||||
|
||||
Pass
|
||||
{
|
||||
ZWrite On
|
||||
Cull Off
|
||||
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma require compute
|
||||
#pragma use_dxc
|
||||
|
||||
#include "GaussianSplatting.hlsl"
|
||||
|
||||
struct v2f
|
||||
{
|
||||
half3 color : TEXCOORD0;
|
||||
float4 vertex : SV_POSITION;
|
||||
};
|
||||
|
||||
float _SplatSize;
|
||||
bool _DisplayIndex;
|
||||
int _SplatCount;
|
||||
|
||||
v2f vert (uint vtxID : SV_VertexID, uint instID : SV_InstanceID)
|
||||
{
|
||||
v2f o;
|
||||
uint splatIndex = instID;
|
||||
|
||||
SplatData splat = LoadSplatData(splatIndex);
|
||||
|
||||
float3 centerWorldPos = splat.pos;
|
||||
centerWorldPos = mul(unity_ObjectToWorld, float4(centerWorldPos,1)).xyz;
|
||||
|
||||
float4 centerClipPos = mul(UNITY_MATRIX_VP, float4(centerWorldPos, 1));
|
||||
|
||||
o.vertex = centerClipPos;
|
||||
uint idx = vtxID;
|
||||
float2 quadPos = float2(idx&1, (idx>>1)&1) * 2.0 - 1.0;
|
||||
o.vertex.xy += (quadPos * _SplatSize / _ScreenParams.xy) * o.vertex.w;
|
||||
|
||||
o.color.rgb = saturate(splat.sh.col);
|
||||
if (_DisplayIndex)
|
||||
{
|
||||
o.color.r = frac((float)splatIndex / (float)_SplatCount * 100);
|
||||
o.color.g = frac((float)splatIndex / (float)_SplatCount * 10);
|
||||
o.color.b = (float)splatIndex / (float)_SplatCount;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 frag (v2f i) : SV_Target
|
||||
{
|
||||
return half4(i.color.rgb, 1);
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b44409fc67214394f8f47e4e2648425e
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
617
MVS/3DGS-Unity/Shaders/GaussianSplatting.hlsl
Normal file
617
MVS/3DGS-Unity/Shaders/GaussianSplatting.hlsl
Normal file
@@ -0,0 +1,617 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#ifndef GAUSSIAN_SPLATTING_HLSL
|
||||
#define GAUSSIAN_SPLATTING_HLSL
|
||||
|
||||
float InvSquareCentered01(float x)
|
||||
{
|
||||
x -= 0.5;
|
||||
x *= 0.5;
|
||||
x = sqrt(abs(x)) * sign(x);
|
||||
return x + 0.5;
|
||||
}
|
||||
|
||||
float3 QuatRotateVector(float3 v, float4 r)
|
||||
{
|
||||
float3 t = 2 * cross(r.xyz, v);
|
||||
return v + r.w * t + cross(r.xyz, t);
|
||||
}
|
||||
|
||||
float4 QuatMul(float4 a, float4 b)
|
||||
{
|
||||
return float4(a.wwww * b + (a.xyzx * b.wwwx + a.yzxy * b.zxyy) * float4(1,1,1,-1) - a.zxyz * b.yzxz);
|
||||
}
|
||||
|
||||
float4 QuatInverse(float4 q)
|
||||
{
|
||||
return rcp(dot(q, q)) * q * float4(-1,-1,-1,1);
|
||||
}
|
||||
|
||||
float3x3 CalcMatrixFromRotationScale(float4 rot, float3 scale)
|
||||
{
|
||||
float3x3 ms = float3x3(
|
||||
scale.x, 0, 0,
|
||||
0, scale.y, 0,
|
||||
0, 0, scale.z
|
||||
);
|
||||
float x = rot.x;
|
||||
float y = rot.y;
|
||||
float z = rot.z;
|
||||
float w = rot.w;
|
||||
float3x3 mr = float3x3(
|
||||
1-2*(y*y + z*z), 2*(x*y - w*z), 2*(x*z + w*y),
|
||||
2*(x*y + w*z), 1-2*(x*x + z*z), 2*(y*z - w*x),
|
||||
2*(x*z - w*y), 2*(y*z + w*x), 1-2*(x*x + y*y)
|
||||
);
|
||||
return mul(mr, ms);
|
||||
}
|
||||
|
||||
void CalcCovariance3D(float3x3 rotMat, out float3 sigma0, out float3 sigma1)
|
||||
{
|
||||
float3x3 sig = mul(rotMat, transpose(rotMat));
|
||||
sigma0 = float3(sig._m00, sig._m01, sig._m02);
|
||||
sigma1 = float3(sig._m11, sig._m12, sig._m22);
|
||||
}
|
||||
|
||||
// from "EWA Splatting" (Zwicker et al 2002) eq. 31
|
||||
float3 CalcCovariance2D(float3 worldPos, float3 cov3d0, float3 cov3d1, float4x4 matrixV, float4x4 matrixP, float4 screenParams)
|
||||
{
|
||||
float4x4 viewMatrix = matrixV;
|
||||
float3 viewPos = mul(viewMatrix, float4(worldPos, 1)).xyz;
|
||||
|
||||
// this is needed in order for splats that are visible in view but clipped "quite a lot" to work
|
||||
float aspect = matrixP._m00 / matrixP._m11;
|
||||
float tanFovX = rcp(matrixP._m00);
|
||||
float tanFovY = rcp(matrixP._m11 * aspect);
|
||||
float limX = 1.3 * tanFovX;
|
||||
float limY = 1.3 * tanFovY;
|
||||
viewPos.x = clamp(viewPos.x / viewPos.z, -limX, limX) * viewPos.z;
|
||||
viewPos.y = clamp(viewPos.y / viewPos.z, -limY, limY) * viewPos.z;
|
||||
|
||||
float focal = screenParams.x * matrixP._m00 / 2;
|
||||
|
||||
float3x3 J = float3x3(
|
||||
focal / viewPos.z, 0, -(focal * viewPos.x) / (viewPos.z * viewPos.z),
|
||||
0, focal / viewPos.z, -(focal * viewPos.y) / (viewPos.z * viewPos.z),
|
||||
0, 0, 0
|
||||
);
|
||||
float3x3 W = (float3x3)viewMatrix;
|
||||
float3x3 T = mul(J, W);
|
||||
float3x3 V = float3x3(
|
||||
cov3d0.x, cov3d0.y, cov3d0.z,
|
||||
cov3d0.y, cov3d1.x, cov3d1.y,
|
||||
cov3d0.z, cov3d1.y, cov3d1.z
|
||||
);
|
||||
float3x3 cov = mul(T, mul(V, transpose(T)));
|
||||
|
||||
// Low pass filter to make each splat at least 1px size.
|
||||
cov._m00 += 0.3;
|
||||
cov._m11 += 0.3;
|
||||
return float3(cov._m00, cov._m01, cov._m11);
|
||||
}
|
||||
|
||||
float3 CalcConic(float3 cov2d)
|
||||
{
|
||||
float det = cov2d.x * cov2d.z - cov2d.y * cov2d.y;
|
||||
return float3(cov2d.z, -cov2d.y, cov2d.x) * rcp(det);
|
||||
}
|
||||
|
||||
float2 CalcScreenSpaceDelta(float2 svPositionXY, float2 centerXY, float4 projectionParams)
|
||||
{
|
||||
float2 d = svPositionXY - centerXY;
|
||||
d.y *= projectionParams.x;
|
||||
return d;
|
||||
}
|
||||
|
||||
float CalcPowerFromConic(float3 conic, float2 d)
|
||||
{
|
||||
return -0.5 * (conic.x * d.x*d.x + conic.z * d.y*d.y) + conic.y * d.x*d.y;
|
||||
}
|
||||
|
||||
// Morton interleaving 16x16 group i.e. by 4 bits of coordinates, based on this thread:
|
||||
// https://twitter.com/rygorous/status/986715358852608000
|
||||
// which is simplified version of https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
|
||||
uint EncodeMorton2D_16x16(uint2 c)
|
||||
{
|
||||
uint t = ((c.y & 0xF) << 8) | (c.x & 0xF); // ----EFGH----ABCD
|
||||
t = (t ^ (t << 2)) & 0x3333; // --EF--GH--AB--CD
|
||||
t = (t ^ (t << 1)) & 0x5555; // -E-F-G-H-A-B-C-D
|
||||
return (t | (t >> 7)) & 0xFF; // --------EAFBGCHD
|
||||
}
|
||||
uint2 DecodeMorton2D_16x16(uint t) // --------EAFBGCHD
|
||||
{
|
||||
t = (t & 0xFF) | ((t & 0xFE) << 7); // -EAFBGCHEAFBGCHD
|
||||
t &= 0x5555; // -E-F-G-H-A-B-C-D
|
||||
t = (t ^ (t >> 1)) & 0x3333; // --EF--GH--AB--CD
|
||||
t = (t ^ (t >> 2)) & 0x0f0f; // ----EFGH----ABCD
|
||||
return uint2(t & 0xF, t >> 8); // --------EFGHABCD
|
||||
}
|
||||
|
||||
|
||||
static const float SH_C1 = 0.4886025;
|
||||
static const float SH_C2[] = { 1.0925484, -1.0925484, 0.3153916, -1.0925484, 0.5462742 };
|
||||
static const float SH_C3[] = { -0.5900436, 2.8906114, -0.4570458, 0.3731763, -0.4570458, 1.4453057, -0.5900436 };
|
||||
|
||||
struct SplatSHData
|
||||
{
|
||||
half3 col, sh1, sh2, sh3, sh4, sh5, sh6, sh7, sh8, sh9, sh10, sh11, sh12, sh13, sh14, sh15;
|
||||
};
|
||||
|
||||
half3 ShadeSH(SplatSHData splat, half3 dir, int shOrder, bool onlySH)
|
||||
{
|
||||
dir *= -1;
|
||||
|
||||
half x = dir.x, y = dir.y, z = dir.z;
|
||||
|
||||
// ambient band
|
||||
half3 res = splat.col; // col = sh0 * SH_C0 + 0.5 is already precomputed
|
||||
if (onlySH)
|
||||
res = 0.5;
|
||||
// 1st degree
|
||||
if (shOrder >= 1)
|
||||
{
|
||||
res += SH_C1 * (-splat.sh1 * y + splat.sh2 * z - splat.sh3 * x);
|
||||
// 2nd degree
|
||||
if (shOrder >= 2)
|
||||
{
|
||||
half xx = x * x, yy = y * y, zz = z * z;
|
||||
half xy = x * y, yz = y * z, xz = x * z;
|
||||
res +=
|
||||
(SH_C2[0] * xy) * splat.sh4 +
|
||||
(SH_C2[1] * yz) * splat.sh5 +
|
||||
(SH_C2[2] * (2 * zz - xx - yy)) * splat.sh6 +
|
||||
(SH_C2[3] * xz) * splat.sh7 +
|
||||
(SH_C2[4] * (xx - yy)) * splat.sh8;
|
||||
// 3rd degree
|
||||
if (shOrder >= 3)
|
||||
{
|
||||
res +=
|
||||
(SH_C3[0] * y * (3 * xx - yy)) * splat.sh9 +
|
||||
(SH_C3[1] * xy * z) * splat.sh10 +
|
||||
(SH_C3[2] * y * (4 * zz - xx - yy)) * splat.sh11 +
|
||||
(SH_C3[3] * z * (2 * zz - 3 * xx - 3 * yy)) * splat.sh12 +
|
||||
(SH_C3[4] * x * (4 * zz - xx - yy)) * splat.sh13 +
|
||||
(SH_C3[5] * z * (xx - yy)) * splat.sh14 +
|
||||
(SH_C3[6] * x * (xx - 3 * yy)) * splat.sh15;
|
||||
}
|
||||
}
|
||||
}
|
||||
return max(res, 0);
|
||||
}
|
||||
|
||||
static const uint kTexWidth = 2048;
|
||||
|
||||
uint3 SplatIndexToPixelIndex(uint idx)
|
||||
{
|
||||
uint3 res;
|
||||
|
||||
uint2 xy = DecodeMorton2D_16x16(idx);
|
||||
uint width = kTexWidth / 16;
|
||||
idx >>= 8;
|
||||
res.x = (idx % width) * 16 + xy.x;
|
||||
res.y = (idx / width) * 16 + xy.y;
|
||||
res.z = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
struct SplatChunkInfo
|
||||
{
|
||||
uint colR, colG, colB, colA;
|
||||
float2 posX, posY, posZ;
|
||||
uint sclX, sclY, sclZ;
|
||||
uint shR, shG, shB;
|
||||
};
|
||||
|
||||
StructuredBuffer<SplatChunkInfo> _SplatChunks;
|
||||
uint _SplatChunkCount;
|
||||
|
||||
static const uint kChunkSize = 256;
|
||||
|
||||
struct SplatData
|
||||
{
|
||||
float3 pos;
|
||||
float4 rot;
|
||||
float3 scale;
|
||||
half opacity;
|
||||
SplatSHData sh;
|
||||
};
|
||||
|
||||
// Decode quaternion from a "smallest 3" e.g. 10.10.10.2 format
|
||||
float4 DecodeRotation(float4 pq)
|
||||
{
|
||||
uint idx = (uint)round(pq.w * 3.0); // note: need to round or index might come out wrong in some formats (e.g. fp16.fp16.fp16.fp16)
|
||||
float4 q;
|
||||
q.xyz = pq.xyz * sqrt(2.0) - (1.0 / sqrt(2.0));
|
||||
q.w = sqrt(1.0 - saturate(dot(q.xyz, q.xyz)));
|
||||
if (idx == 0) q = q.wxyz;
|
||||
if (idx == 1) q = q.xwyz;
|
||||
if (idx == 2) q = q.xywz;
|
||||
return q;
|
||||
}
|
||||
float4 PackSmallest3Rotation(float4 q)
|
||||
{
|
||||
// find biggest component
|
||||
float4 absQ = abs(q);
|
||||
int index = 0;
|
||||
float maxV = absQ.x;
|
||||
if (absQ.y > maxV)
|
||||
{
|
||||
index = 1;
|
||||
maxV = absQ.y;
|
||||
}
|
||||
if (absQ.z > maxV)
|
||||
{
|
||||
index = 2;
|
||||
maxV = absQ.z;
|
||||
}
|
||||
if (absQ.w > maxV)
|
||||
{
|
||||
index = 3;
|
||||
maxV = absQ.w;
|
||||
}
|
||||
|
||||
if (index == 0) q = q.yzwx;
|
||||
if (index == 1) q = q.xzwy;
|
||||
if (index == 2) q = q.xywz;
|
||||
|
||||
float3 three = q.xyz * (q.w >= 0 ? 1 : -1); // -1/sqrt2..+1/sqrt2 range
|
||||
three = (three * sqrt(2.0)) * 0.5 + 0.5; // 0..1 range
|
||||
return float4(three, index / 3.0);
|
||||
}
|
||||
|
||||
half3 DecodePacked_6_5_5(uint enc)
|
||||
{
|
||||
return half3(
|
||||
(enc & 63) / 63.0,
|
||||
((enc >> 6) & 31) / 31.0,
|
||||
((enc >> 11) & 31) / 31.0);
|
||||
}
|
||||
|
||||
half3 DecodePacked_5_6_5(uint enc)
|
||||
{
|
||||
return half3(
|
||||
(enc & 31) / 31.0,
|
||||
((enc >> 5) & 63) / 63.0,
|
||||
((enc >> 11) & 31) / 31.0);
|
||||
}
|
||||
|
||||
half3 DecodePacked_11_10_11(uint enc)
|
||||
{
|
||||
return half3(
|
||||
(enc & 2047) / 2047.0,
|
||||
((enc >> 11) & 1023) / 1023.0,
|
||||
((enc >> 21) & 2047) / 2047.0);
|
||||
}
|
||||
|
||||
float3 DecodePacked_16_16_16(uint2 enc)
|
||||
{
|
||||
return float3(
|
||||
(enc.x & 65535) / 65535.0,
|
||||
((enc.x >> 16) & 65535) / 65535.0,
|
||||
(enc.y & 65535) / 65535.0);
|
||||
}
|
||||
|
||||
float4 DecodePacked_10_10_10_2(uint enc)
|
||||
{
|
||||
return float4(
|
||||
(enc & 1023) / 1023.0,
|
||||
((enc >> 10) & 1023) / 1023.0,
|
||||
((enc >> 20) & 1023) / 1023.0,
|
||||
((enc >> 30) & 3) / 3.0);
|
||||
}
|
||||
uint EncodeQuatToNorm10(float4 v) // 32 bits: 10.10.10.2
|
||||
{
|
||||
return (uint) (v.x * 1023.5f) | ((uint) (v.y * 1023.5f) << 10) | ((uint) (v.z * 1023.5f) << 20) | ((uint) (v.w * 3.5f) << 30);
|
||||
}
|
||||
|
||||
|
||||
#ifdef SHADER_STAGE_COMPUTE
|
||||
#define SplatBufferDataType RWByteAddressBuffer
|
||||
#else
|
||||
#define SplatBufferDataType ByteAddressBuffer
|
||||
#endif
|
||||
|
||||
SplatBufferDataType _SplatPos;
|
||||
SplatBufferDataType _SplatOther;
|
||||
SplatBufferDataType _SplatSH;
|
||||
Texture2D _SplatColor;
|
||||
uint _SplatFormat;
|
||||
|
||||
// Match GaussianSplatAsset.VectorFormat
|
||||
#define VECTOR_FMT_32F 0
|
||||
#define VECTOR_FMT_16 1
|
||||
#define VECTOR_FMT_11 2
|
||||
#define VECTOR_FMT_6 3
|
||||
|
||||
uint LoadUShort(SplatBufferDataType dataBuffer, uint addrU)
|
||||
{
|
||||
uint addrA = addrU & ~0x3;
|
||||
uint val = dataBuffer.Load(addrA);
|
||||
if (addrU != addrA)
|
||||
val >>= 16;
|
||||
return val & 0xFFFF;
|
||||
}
|
||||
|
||||
uint LoadUInt(SplatBufferDataType dataBuffer, uint addrU)
|
||||
{
|
||||
uint addrA = addrU & ~0x3;
|
||||
uint val = dataBuffer.Load(addrA);
|
||||
if (addrU != addrA)
|
||||
{
|
||||
uint val1 = dataBuffer.Load(addrA + 4);
|
||||
val = (val >> 16) | ((val1 & 0xFFFF) << 16);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
float3 LoadAndDecodeVector(SplatBufferDataType dataBuffer, uint addrU, uint fmt)
|
||||
{
|
||||
uint addrA = addrU & ~0x3;
|
||||
|
||||
uint val0 = dataBuffer.Load(addrA);
|
||||
|
||||
float3 res = 0;
|
||||
if (fmt == VECTOR_FMT_32F)
|
||||
{
|
||||
uint val1 = dataBuffer.Load(addrA + 4);
|
||||
uint val2 = dataBuffer.Load(addrA + 8);
|
||||
if (addrU != addrA)
|
||||
{
|
||||
uint val3 = dataBuffer.Load(addrA + 12);
|
||||
val0 = (val0 >> 16) | ((val1 & 0xFFFF) << 16);
|
||||
val1 = (val1 >> 16) | ((val2 & 0xFFFF) << 16);
|
||||
val2 = (val2 >> 16) | ((val3 & 0xFFFF) << 16);
|
||||
}
|
||||
res = float3(asfloat(val0), asfloat(val1), asfloat(val2));
|
||||
}
|
||||
else if (fmt == VECTOR_FMT_16)
|
||||
{
|
||||
uint val1 = dataBuffer.Load(addrA + 4);
|
||||
if (addrU != addrA)
|
||||
{
|
||||
val0 = (val0 >> 16) | ((val1 & 0xFFFF) << 16);
|
||||
val1 >>= 16;
|
||||
}
|
||||
res = DecodePacked_16_16_16(uint2(val0, val1));
|
||||
}
|
||||
else if (fmt == VECTOR_FMT_11)
|
||||
{
|
||||
uint val1 = dataBuffer.Load(addrA + 4);
|
||||
if (addrU != addrA)
|
||||
{
|
||||
val0 = (val0 >> 16) | ((val1 & 0xFFFF) << 16);
|
||||
}
|
||||
res = DecodePacked_11_10_11(val0);
|
||||
}
|
||||
else if (fmt == VECTOR_FMT_6)
|
||||
{
|
||||
if (addrU != addrA)
|
||||
val0 >>= 16;
|
||||
res = DecodePacked_6_5_5(val0);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
float3 LoadSplatPosValue(uint index)
|
||||
{
|
||||
uint fmt = _SplatFormat & 0xFF;
|
||||
uint stride = 0;
|
||||
if (fmt == VECTOR_FMT_32F)
|
||||
stride = 12;
|
||||
else if (fmt == VECTOR_FMT_16)
|
||||
stride = 6;
|
||||
else if (fmt == VECTOR_FMT_11)
|
||||
stride = 4;
|
||||
else if (fmt == VECTOR_FMT_6)
|
||||
stride = 2;
|
||||
return LoadAndDecodeVector(_SplatPos, index * stride, fmt);
|
||||
}
|
||||
|
||||
float3 LoadSplatPos(uint idx)
|
||||
{
|
||||
float3 pos = LoadSplatPosValue(idx);
|
||||
uint chunkIdx = idx / kChunkSize;
|
||||
if (chunkIdx < _SplatChunkCount)
|
||||
{
|
||||
SplatChunkInfo chunk = _SplatChunks[chunkIdx];
|
||||
float3 posMin = float3(chunk.posX.x, chunk.posY.x, chunk.posZ.x);
|
||||
float3 posMax = float3(chunk.posX.y, chunk.posY.y, chunk.posZ.y);
|
||||
pos = lerp(posMin, posMax, pos);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
half4 LoadSplatColTex(uint3 coord)
|
||||
{
|
||||
return _SplatColor.Load(coord);
|
||||
}
|
||||
|
||||
SplatData LoadSplatData(uint idx)
|
||||
{
|
||||
SplatData s = (SplatData)0;
|
||||
|
||||
// figure out raw data offsets / locations
|
||||
uint3 coord = SplatIndexToPixelIndex(idx);
|
||||
|
||||
uint scaleFmt = (_SplatFormat >> 8) & 0xFF;
|
||||
uint shFormat = (_SplatFormat >> 16) & 0xFF;
|
||||
|
||||
uint otherStride = 4; // rotation is 10.10.10.2
|
||||
if (scaleFmt == VECTOR_FMT_32F)
|
||||
otherStride += 12;
|
||||
else if (scaleFmt == VECTOR_FMT_16)
|
||||
otherStride += 6;
|
||||
else if (scaleFmt == VECTOR_FMT_11)
|
||||
otherStride += 4;
|
||||
else if (scaleFmt == VECTOR_FMT_6)
|
||||
otherStride += 2;
|
||||
if (shFormat > VECTOR_FMT_6)
|
||||
otherStride += 2;
|
||||
uint otherAddr = idx * otherStride;
|
||||
|
||||
uint shStride = 0;
|
||||
if (shFormat == VECTOR_FMT_32F)
|
||||
shStride = 192; // 15*3 fp32, rounded up to multiple of 16
|
||||
else if (shFormat == VECTOR_FMT_16 || shFormat > VECTOR_FMT_6)
|
||||
shStride = 96; // 15*3 fp16, rounded up to multiple of 16
|
||||
else if (shFormat == VECTOR_FMT_11)
|
||||
shStride = 60; // 15x uint
|
||||
else if (shFormat == VECTOR_FMT_6)
|
||||
shStride = 32; // 15x ushort, rounded up to multiple of 4
|
||||
|
||||
|
||||
// load raw splat data, which might be chunk-relative
|
||||
s.pos = LoadSplatPosValue(idx);
|
||||
s.rot = DecodeRotation(DecodePacked_10_10_10_2(LoadUInt(_SplatOther, otherAddr)));
|
||||
s.scale = LoadAndDecodeVector(_SplatOther, otherAddr + 4, scaleFmt);
|
||||
half4 col = LoadSplatColTex(coord);
|
||||
|
||||
uint shIndex = idx;
|
||||
if (shFormat > VECTOR_FMT_6)
|
||||
shIndex = LoadUShort(_SplatOther, otherAddr + otherStride - 2);
|
||||
|
||||
uint shOffset = shIndex * shStride;
|
||||
uint4 shRaw0 = _SplatSH.Load4(shOffset);
|
||||
uint4 shRaw1 = _SplatSH.Load4(shOffset + 16);
|
||||
if (shFormat == VECTOR_FMT_32F)
|
||||
{
|
||||
uint4 shRaw2 = _SplatSH.Load4(shOffset + 32);
|
||||
uint4 shRaw3 = _SplatSH.Load4(shOffset + 48);
|
||||
uint4 shRaw4 = _SplatSH.Load4(shOffset + 64);
|
||||
uint4 shRaw5 = _SplatSH.Load4(shOffset + 80);
|
||||
uint4 shRaw6 = _SplatSH.Load4(shOffset + 96);
|
||||
uint4 shRaw7 = _SplatSH.Load4(shOffset + 112);
|
||||
uint4 shRaw8 = _SplatSH.Load4(shOffset + 128);
|
||||
uint4 shRaw9 = _SplatSH.Load4(shOffset + 144);
|
||||
uint4 shRawA = _SplatSH.Load4(shOffset + 160);
|
||||
uint shRawB = _SplatSH.Load(shOffset + 176);
|
||||
s.sh.sh1.r = asfloat(shRaw0.x); s.sh.sh1.g = asfloat(shRaw0.y); s.sh.sh1.b = asfloat(shRaw0.z);
|
||||
s.sh.sh2.r = asfloat(shRaw0.w); s.sh.sh2.g = asfloat(shRaw1.x); s.sh.sh2.b = asfloat(shRaw1.y);
|
||||
s.sh.sh3.r = asfloat(shRaw1.z); s.sh.sh3.g = asfloat(shRaw1.w); s.sh.sh3.b = asfloat(shRaw2.x);
|
||||
s.sh.sh4.r = asfloat(shRaw2.y); s.sh.sh4.g = asfloat(shRaw2.z); s.sh.sh4.b = asfloat(shRaw2.w);
|
||||
s.sh.sh5.r = asfloat(shRaw3.x); s.sh.sh5.g = asfloat(shRaw3.y); s.sh.sh5.b = asfloat(shRaw3.z);
|
||||
s.sh.sh6.r = asfloat(shRaw3.w); s.sh.sh6.g = asfloat(shRaw4.x); s.sh.sh6.b = asfloat(shRaw4.y);
|
||||
s.sh.sh7.r = asfloat(shRaw4.z); s.sh.sh7.g = asfloat(shRaw4.w); s.sh.sh7.b = asfloat(shRaw5.x);
|
||||
s.sh.sh8.r = asfloat(shRaw5.y); s.sh.sh8.g = asfloat(shRaw5.z); s.sh.sh8.b = asfloat(shRaw5.w);
|
||||
s.sh.sh9.r = asfloat(shRaw6.x); s.sh.sh9.g = asfloat(shRaw6.y); s.sh.sh9.b = asfloat(shRaw6.z);
|
||||
s.sh.sh10.r = asfloat(shRaw6.w); s.sh.sh10.g = asfloat(shRaw7.x); s.sh.sh10.b = asfloat(shRaw7.y);
|
||||
s.sh.sh11.r = asfloat(shRaw7.z); s.sh.sh11.g = asfloat(shRaw7.w); s.sh.sh11.b = asfloat(shRaw8.x);
|
||||
s.sh.sh12.r = asfloat(shRaw8.y); s.sh.sh12.g = asfloat(shRaw8.z); s.sh.sh12.b = asfloat(shRaw8.w);
|
||||
s.sh.sh13.r = asfloat(shRaw9.x); s.sh.sh13.g = asfloat(shRaw9.y); s.sh.sh13.b = asfloat(shRaw9.z);
|
||||
s.sh.sh14.r = asfloat(shRaw9.w); s.sh.sh14.g = asfloat(shRawA.x); s.sh.sh14.b = asfloat(shRawA.y);
|
||||
s.sh.sh15.r = asfloat(shRawA.z); s.sh.sh15.g = asfloat(shRawA.w); s.sh.sh15.b = asfloat(shRawB);
|
||||
}
|
||||
else if (shFormat == VECTOR_FMT_16 || shFormat > VECTOR_FMT_6)
|
||||
{
|
||||
uint4 shRaw2 = _SplatSH.Load4(shOffset + 32);
|
||||
uint4 shRaw3 = _SplatSH.Load4(shOffset + 48);
|
||||
uint4 shRaw4 = _SplatSH.Load4(shOffset + 64);
|
||||
uint3 shRaw5 = _SplatSH.Load3(shOffset + 80);
|
||||
s.sh.sh1.r = f16tof32(shRaw0.x ); s.sh.sh1.g = f16tof32(shRaw0.x >> 16); s.sh.sh1.b = f16tof32(shRaw0.y );
|
||||
s.sh.sh2.r = f16tof32(shRaw0.y >> 16); s.sh.sh2.g = f16tof32(shRaw0.z ); s.sh.sh2.b = f16tof32(shRaw0.z >> 16);
|
||||
s.sh.sh3.r = f16tof32(shRaw0.w ); s.sh.sh3.g = f16tof32(shRaw0.w >> 16); s.sh.sh3.b = f16tof32(shRaw1.x );
|
||||
s.sh.sh4.r = f16tof32(shRaw1.x >> 16); s.sh.sh4.g = f16tof32(shRaw1.y ); s.sh.sh4.b = f16tof32(shRaw1.y >> 16);
|
||||
s.sh.sh5.r = f16tof32(shRaw1.z ); s.sh.sh5.g = f16tof32(shRaw1.z >> 16); s.sh.sh5.b = f16tof32(shRaw1.w );
|
||||
s.sh.sh6.r = f16tof32(shRaw1.w >> 16); s.sh.sh6.g = f16tof32(shRaw2.x ); s.sh.sh6.b = f16tof32(shRaw2.x >> 16);
|
||||
s.sh.sh7.r = f16tof32(shRaw2.y ); s.sh.sh7.g = f16tof32(shRaw2.y >> 16); s.sh.sh7.b = f16tof32(shRaw2.z );
|
||||
s.sh.sh8.r = f16tof32(shRaw2.z >> 16); s.sh.sh8.g = f16tof32(shRaw2.w ); s.sh.sh8.b = f16tof32(shRaw2.w >> 16);
|
||||
s.sh.sh9.r = f16tof32(shRaw3.x ); s.sh.sh9.g = f16tof32(shRaw3.x >> 16); s.sh.sh9.b = f16tof32(shRaw3.y );
|
||||
s.sh.sh10.r = f16tof32(shRaw3.y >> 16); s.sh.sh10.g = f16tof32(shRaw3.z ); s.sh.sh10.b = f16tof32(shRaw3.z >> 16);
|
||||
s.sh.sh11.r = f16tof32(shRaw3.w ); s.sh.sh11.g = f16tof32(shRaw3.w >> 16); s.sh.sh11.b = f16tof32(shRaw4.x );
|
||||
s.sh.sh12.r = f16tof32(shRaw4.x >> 16); s.sh.sh12.g = f16tof32(shRaw4.y ); s.sh.sh12.b = f16tof32(shRaw4.y >> 16);
|
||||
s.sh.sh13.r = f16tof32(shRaw4.z ); s.sh.sh13.g = f16tof32(shRaw4.z >> 16); s.sh.sh13.b = f16tof32(shRaw4.w );
|
||||
s.sh.sh14.r = f16tof32(shRaw4.w >> 16); s.sh.sh14.g = f16tof32(shRaw5.x ); s.sh.sh14.b = f16tof32(shRaw5.x >> 16);
|
||||
s.sh.sh15.r = f16tof32(shRaw5.y ); s.sh.sh15.g = f16tof32(shRaw5.y >> 16); s.sh.sh15.b = f16tof32(shRaw5.z );
|
||||
}
|
||||
else if (shFormat == VECTOR_FMT_11)
|
||||
{
|
||||
uint4 shRaw2 = _SplatSH.Load4(shOffset + 32);
|
||||
uint3 shRaw3 = _SplatSH.Load3(shOffset + 48);
|
||||
s.sh.sh1 = DecodePacked_11_10_11(shRaw0.x);
|
||||
s.sh.sh2 = DecodePacked_11_10_11(shRaw0.y);
|
||||
s.sh.sh3 = DecodePacked_11_10_11(shRaw0.z);
|
||||
s.sh.sh4 = DecodePacked_11_10_11(shRaw0.w);
|
||||
s.sh.sh5 = DecodePacked_11_10_11(shRaw1.x);
|
||||
s.sh.sh6 = DecodePacked_11_10_11(shRaw1.y);
|
||||
s.sh.sh7 = DecodePacked_11_10_11(shRaw1.z);
|
||||
s.sh.sh8 = DecodePacked_11_10_11(shRaw1.w);
|
||||
s.sh.sh9 = DecodePacked_11_10_11(shRaw2.x);
|
||||
s.sh.sh10 = DecodePacked_11_10_11(shRaw2.y);
|
||||
s.sh.sh11 = DecodePacked_11_10_11(shRaw2.z);
|
||||
s.sh.sh12 = DecodePacked_11_10_11(shRaw2.w);
|
||||
s.sh.sh13 = DecodePacked_11_10_11(shRaw3.x);
|
||||
s.sh.sh14 = DecodePacked_11_10_11(shRaw3.y);
|
||||
s.sh.sh15 = DecodePacked_11_10_11(shRaw3.z);
|
||||
}
|
||||
else if (shFormat == VECTOR_FMT_6)
|
||||
{
|
||||
s.sh.sh1 = DecodePacked_5_6_5(shRaw0.x);
|
||||
s.sh.sh2 = DecodePacked_5_6_5(shRaw0.x >> 16);
|
||||
s.sh.sh3 = DecodePacked_5_6_5(shRaw0.y);
|
||||
s.sh.sh4 = DecodePacked_5_6_5(shRaw0.y >> 16);
|
||||
s.sh.sh5 = DecodePacked_5_6_5(shRaw0.z);
|
||||
s.sh.sh6 = DecodePacked_5_6_5(shRaw0.z >> 16);
|
||||
s.sh.sh7 = DecodePacked_5_6_5(shRaw0.w);
|
||||
s.sh.sh8 = DecodePacked_5_6_5(shRaw0.w >> 16);
|
||||
s.sh.sh9 = DecodePacked_5_6_5(shRaw1.x);
|
||||
s.sh.sh10 = DecodePacked_5_6_5(shRaw1.x >> 16);
|
||||
s.sh.sh11 = DecodePacked_5_6_5(shRaw1.y);
|
||||
s.sh.sh12 = DecodePacked_5_6_5(shRaw1.y >> 16);
|
||||
s.sh.sh13 = DecodePacked_5_6_5(shRaw1.z);
|
||||
s.sh.sh14 = DecodePacked_5_6_5(shRaw1.z >> 16);
|
||||
s.sh.sh15 = DecodePacked_5_6_5(shRaw1.w);
|
||||
}
|
||||
|
||||
// if raw data is chunk-relative, convert to final values by interpolating between chunk min/max
|
||||
uint chunkIdx = idx / kChunkSize;
|
||||
if (chunkIdx < _SplatChunkCount)
|
||||
{
|
||||
SplatChunkInfo chunk = _SplatChunks[chunkIdx];
|
||||
float3 posMin = float3(chunk.posX.x, chunk.posY.x, chunk.posZ.x);
|
||||
float3 posMax = float3(chunk.posX.y, chunk.posY.y, chunk.posZ.y);
|
||||
half3 sclMin = half3(f16tof32(chunk.sclX ), f16tof32(chunk.sclY ), f16tof32(chunk.sclZ ));
|
||||
half3 sclMax = half3(f16tof32(chunk.sclX>>16), f16tof32(chunk.sclY>>16), f16tof32(chunk.sclZ>>16));
|
||||
half4 colMin = half4(f16tof32(chunk.colR ), f16tof32(chunk.colG ), f16tof32(chunk.colB ), f16tof32(chunk.colA ));
|
||||
half4 colMax = half4(f16tof32(chunk.colR>>16), f16tof32(chunk.colG>>16), f16tof32(chunk.colB>>16), f16tof32(chunk.colA>>16));
|
||||
half3 shMin = half3(f16tof32(chunk.shR ), f16tof32(chunk.shG ), f16tof32(chunk.shB ));
|
||||
half3 shMax = half3(f16tof32(chunk.shR>>16), f16tof32(chunk.shG>>16), f16tof32(chunk.shB>>16));
|
||||
s.pos = lerp(posMin, posMax, s.pos);
|
||||
s.scale = lerp(sclMin, sclMax, s.scale);
|
||||
s.scale *= s.scale;
|
||||
s.scale *= s.scale;
|
||||
s.scale *= s.scale;
|
||||
col = lerp(colMin, colMax, col);
|
||||
col.a = InvSquareCentered01(col.a);
|
||||
|
||||
if (shFormat > VECTOR_FMT_32F && shFormat <= VECTOR_FMT_6)
|
||||
{
|
||||
s.sh.sh1 = lerp(shMin, shMax, s.sh.sh1 );
|
||||
s.sh.sh2 = lerp(shMin, shMax, s.sh.sh2 );
|
||||
s.sh.sh3 = lerp(shMin, shMax, s.sh.sh3 );
|
||||
s.sh.sh4 = lerp(shMin, shMax, s.sh.sh4 );
|
||||
s.sh.sh5 = lerp(shMin, shMax, s.sh.sh5 );
|
||||
s.sh.sh6 = lerp(shMin, shMax, s.sh.sh6 );
|
||||
s.sh.sh7 = lerp(shMin, shMax, s.sh.sh7 );
|
||||
s.sh.sh8 = lerp(shMin, shMax, s.sh.sh8 );
|
||||
s.sh.sh9 = lerp(shMin, shMax, s.sh.sh9 );
|
||||
s.sh.sh10 = lerp(shMin, shMax, s.sh.sh10);
|
||||
s.sh.sh11 = lerp(shMin, shMax, s.sh.sh11);
|
||||
s.sh.sh12 = lerp(shMin, shMax, s.sh.sh12);
|
||||
s.sh.sh13 = lerp(shMin, shMax, s.sh.sh13);
|
||||
s.sh.sh14 = lerp(shMin, shMax, s.sh.sh14);
|
||||
s.sh.sh15 = lerp(shMin, shMax, s.sh.sh15);
|
||||
}
|
||||
}
|
||||
s.opacity = col.a;
|
||||
s.sh.col = col.rgb;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct SplatViewData
|
||||
{
|
||||
float4 pos;
|
||||
float2 axis1, axis2;
|
||||
uint2 color; // 4xFP16
|
||||
};
|
||||
|
||||
#endif // GAUSSIAN_SPLATTING_HLSL
|
||||
7
MVS/3DGS-Unity/Shaders/GaussianSplatting.hlsl.meta
Normal file
7
MVS/3DGS-Unity/Shaders/GaussianSplatting.hlsl.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4087e54957693c48a7be32de91c99e2
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
111
MVS/3DGS-Unity/Shaders/RenderGaussianSplats.shader
Normal file
111
MVS/3DGS-Unity/Shaders/RenderGaussianSplats.shader
Normal file
@@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
Shader "Gaussian Splatting/Render Splats"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
|
||||
|
||||
Pass
|
||||
{
|
||||
ZWrite Off
|
||||
Blend OneMinusDstAlpha One
|
||||
Cull Off
|
||||
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma require compute
|
||||
#pragma use_dxc
|
||||
|
||||
#include "GaussianSplatting.hlsl"
|
||||
|
||||
StructuredBuffer<uint> _OrderBuffer;
|
||||
|
||||
struct v2f
|
||||
{
|
||||
half4 col : COLOR0;
|
||||
float2 pos : TEXCOORD0;
|
||||
float4 vertex : SV_POSITION;
|
||||
};
|
||||
|
||||
StructuredBuffer<SplatViewData> _SplatViewData;
|
||||
ByteAddressBuffer _SplatSelectedBits;
|
||||
uint _SplatBitsValid;
|
||||
|
||||
v2f vert (uint vtxID : SV_VertexID, uint instID : SV_InstanceID)
|
||||
{
|
||||
v2f o = (v2f)0;
|
||||
instID = _OrderBuffer[instID];
|
||||
SplatViewData view = _SplatViewData[instID];
|
||||
float4 centerClipPos = view.pos;
|
||||
bool behindCam = centerClipPos.w <= 0;
|
||||
if (behindCam)
|
||||
{
|
||||
o.vertex = asfloat(0x7fc00000); // NaN discards the primitive
|
||||
}
|
||||
else
|
||||
{
|
||||
o.col.r = f16tof32(view.color.x >> 16);
|
||||
o.col.g = f16tof32(view.color.x);
|
||||
o.col.b = f16tof32(view.color.y >> 16);
|
||||
o.col.a = f16tof32(view.color.y);
|
||||
|
||||
uint idx = vtxID;
|
||||
float2 quadPos = float2(idx&1, (idx>>1)&1) * 2.0 - 1.0;
|
||||
quadPos *= 2;
|
||||
|
||||
o.pos = quadPos;
|
||||
|
||||
float2 deltaScreenPos = (quadPos.x * view.axis1 + quadPos.y * view.axis2) * 2 / _ScreenParams.xy;
|
||||
o.vertex = centerClipPos;
|
||||
o.vertex.xy += deltaScreenPos * centerClipPos.w;
|
||||
|
||||
// is this splat selected?
|
||||
if (_SplatBitsValid)
|
||||
{
|
||||
uint wordIdx = instID / 32;
|
||||
uint bitIdx = instID & 31;
|
||||
uint selVal = _SplatSelectedBits.Load(wordIdx * 4);
|
||||
if (selVal & (1 << bitIdx))
|
||||
{
|
||||
o.col.a = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 frag (v2f i) : SV_Target
|
||||
{
|
||||
float power = -dot(i.pos, i.pos);
|
||||
half alpha = exp(power);
|
||||
if (i.col.a >= 0)
|
||||
{
|
||||
alpha = saturate(alpha * i.col.a);
|
||||
}
|
||||
else
|
||||
{
|
||||
// "selected" splat: magenta outline, increase opacity, magenta tint
|
||||
half3 selectedColor = half3(1,0,1);
|
||||
if (alpha > 7.0/255.0)
|
||||
{
|
||||
if (alpha < 10.0/255.0)
|
||||
{
|
||||
alpha = 1;
|
||||
i.col.rgb = selectedColor;
|
||||
}
|
||||
alpha = saturate(alpha + 0.3);
|
||||
}
|
||||
i.col.rgb = lerp(i.col.rgb, selectedColor, 0.5);
|
||||
}
|
||||
|
||||
if (alpha < 1.0/255.0)
|
||||
discard;
|
||||
|
||||
half4 res = half4(i.col.rgb * alpha, alpha);
|
||||
return res;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
9
MVS/3DGS-Unity/Shaders/RenderGaussianSplats.shader.meta
Normal file
9
MVS/3DGS-Unity/Shaders/RenderGaussianSplats.shader.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed800126ae8844a67aad1974ddddd59c
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
959
MVS/3DGS-Unity/Shaders/SortCommon.hlsl
Normal file
959
MVS/3DGS-Unity/Shaders/SortCommon.hlsl
Normal file
@@ -0,0 +1,959 @@
|
||||
/******************************************************************************
|
||||
* SortCommon
|
||||
* Common functions for GPUSorting
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* Copyright Thomas Smith 5/17/2024
|
||||
* https://github.com/b0nes164/GPUSorting
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
******************************************************************************/
|
||||
#define KEYS_PER_THREAD 15U
|
||||
#define D_DIM 256U
|
||||
#define PART_SIZE 3840U
|
||||
#define D_TOTAL_SMEM 4096U
|
||||
|
||||
#define RADIX 256U //Number of digit bins
|
||||
#define RADIX_MASK 255U //Mask of digit bins
|
||||
#define HALF_RADIX 128U //For smaller waves where bit packing is necessary
|
||||
#define HALF_MASK 127U // ''
|
||||
#define RADIX_LOG 8U //log2(RADIX)
|
||||
#define RADIX_PASSES 4U //(Key width) / RADIX_LOG
|
||||
|
||||
cbuffer cbGpuSorting : register(b0)
|
||||
{
|
||||
uint e_numKeys;
|
||||
uint e_radixShift;
|
||||
uint e_threadBlocks;
|
||||
uint padding;
|
||||
};
|
||||
|
||||
#if defined(KEY_UINT)
|
||||
RWStructuredBuffer<uint> b_sort;
|
||||
RWStructuredBuffer<uint> b_alt;
|
||||
#elif defined(KEY_INT)
|
||||
RWStructuredBuffer<int> b_sort;
|
||||
RWStructuredBuffer<int> b_alt;
|
||||
#elif defined(KEY_FLOAT)
|
||||
RWStructuredBuffer<float> b_sort;
|
||||
RWStructuredBuffer<float> b_alt;
|
||||
#endif
|
||||
|
||||
#if defined(PAYLOAD_UINT)
|
||||
RWStructuredBuffer<uint> b_sortPayload;
|
||||
RWStructuredBuffer<uint> b_altPayload;
|
||||
#elif defined(PAYLOAD_INT)
|
||||
RWStructuredBuffer<int> b_sortPayload;
|
||||
RWStructuredBuffer<int> b_altPayload;
|
||||
#elif defined(PAYLOAD_FLOAT)
|
||||
RWStructuredBuffer<float> b_sortPayload;
|
||||
RWStructuredBuffer<float> b_altPayload;
|
||||
#endif
|
||||
|
||||
groupshared uint g_d[D_TOTAL_SMEM]; //Shared memory for DigitBinningPass and DownSweep kernels
|
||||
|
||||
struct KeyStruct
|
||||
{
|
||||
uint k[KEYS_PER_THREAD];
|
||||
};
|
||||
|
||||
struct OffsetStruct
|
||||
{
|
||||
#if defined(ENABLE_16_BIT)
|
||||
uint16_t o[KEYS_PER_THREAD];
|
||||
#else
|
||||
uint o[KEYS_PER_THREAD];
|
||||
#endif
|
||||
};
|
||||
|
||||
struct DigitStruct
|
||||
{
|
||||
#if defined(ENABLE_16_BIT)
|
||||
uint16_t d[KEYS_PER_THREAD];
|
||||
#else
|
||||
uint d[KEYS_PER_THREAD];
|
||||
#endif
|
||||
};
|
||||
|
||||
//*****************************************************************************
|
||||
//HELPER FUNCTIONS
|
||||
//*****************************************************************************
|
||||
//Due to a bug with SPIRV pre 1.6, we cannot use WaveGetLaneCount() to get the currently active wavesize
|
||||
inline uint getWaveSize()
|
||||
{
|
||||
#if defined(VULKAN)
|
||||
GroupMemoryBarrierWithGroupSync(); //Make absolutely sure the wave is not diverged here
|
||||
return dot(countbits(WaveActiveBallot(true)), uint4(1, 1, 1, 1));
|
||||
#else
|
||||
return WaveGetLaneCount();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline uint getWaveIndex(uint gtid, uint waveSize)
|
||||
{
|
||||
return gtid / waveSize;
|
||||
}
|
||||
|
||||
//Radix Tricks by Michael Herf
|
||||
//http://stereopsis.com/radix.html
|
||||
inline uint FloatToUint(float f)
|
||||
{
|
||||
uint mask = -((int) (asuint(f) >> 31)) | 0x80000000;
|
||||
return asuint(f) ^ mask;
|
||||
}
|
||||
|
||||
inline float UintToFloat(uint u)
|
||||
{
|
||||
uint mask = ((u >> 31) - 1) | 0x80000000;
|
||||
return asfloat(u ^ mask);
|
||||
}
|
||||
|
||||
inline uint IntToUint(int i)
|
||||
{
|
||||
return asuint(i ^ 0x80000000);
|
||||
}
|
||||
|
||||
inline int UintToInt(uint u)
|
||||
{
|
||||
return asint(u ^ 0x80000000);
|
||||
}
|
||||
|
||||
inline uint getWaveCountPass(uint waveSize)
|
||||
{
|
||||
return D_DIM / waveSize;
|
||||
}
|
||||
|
||||
inline uint ExtractDigit(uint key)
|
||||
{
|
||||
return key >> e_radixShift & RADIX_MASK;
|
||||
}
|
||||
|
||||
inline uint ExtractDigit(uint key, uint shift)
|
||||
{
|
||||
return key >> shift & RADIX_MASK;
|
||||
}
|
||||
|
||||
inline uint ExtractPackedIndex(uint key)
|
||||
{
|
||||
return key >> (e_radixShift + 1) & HALF_MASK;
|
||||
}
|
||||
|
||||
inline uint ExtractPackedShift(uint key)
|
||||
{
|
||||
return (key >> e_radixShift & 1) ? 16 : 0;
|
||||
}
|
||||
|
||||
inline uint ExtractPackedValue(uint packed, uint key)
|
||||
{
|
||||
return packed >> ExtractPackedShift(key) & 0xffff;
|
||||
}
|
||||
|
||||
inline uint SubPartSizeWGE16(uint waveSize)
|
||||
{
|
||||
return KEYS_PER_THREAD * waveSize;
|
||||
}
|
||||
|
||||
inline uint SharedOffsetWGE16(uint gtid, uint waveSize)
|
||||
{
|
||||
return WaveGetLaneIndex() + getWaveIndex(gtid, waveSize) * SubPartSizeWGE16(waveSize);
|
||||
}
|
||||
|
||||
inline uint SubPartSizeWLT16(uint waveSize, uint _serialIterations)
|
||||
{
|
||||
return KEYS_PER_THREAD * waveSize * _serialIterations;
|
||||
}
|
||||
|
||||
inline uint SharedOffsetWLT16(uint gtid, uint waveSize, uint _serialIterations)
|
||||
{
|
||||
return WaveGetLaneIndex() +
|
||||
(getWaveIndex(gtid, waveSize) / _serialIterations * SubPartSizeWLT16(waveSize, _serialIterations)) +
|
||||
(getWaveIndex(gtid, waveSize) % _serialIterations * waveSize);
|
||||
}
|
||||
|
||||
inline uint DeviceOffsetWGE16(uint gtid, uint waveSize, uint partIndex)
|
||||
{
|
||||
return SharedOffsetWGE16(gtid, waveSize) + partIndex * PART_SIZE;
|
||||
}
|
||||
|
||||
inline uint DeviceOffsetWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
|
||||
{
|
||||
return SharedOffsetWLT16(gtid, waveSize, serialIterations) + partIndex * PART_SIZE;
|
||||
}
|
||||
|
||||
inline uint GlobalHistOffset()
|
||||
{
|
||||
return e_radixShift << 5;
|
||||
}
|
||||
|
||||
inline uint WaveHistsSizeWGE16(uint waveSize)
|
||||
{
|
||||
return D_DIM / waveSize * RADIX;
|
||||
}
|
||||
|
||||
inline uint WaveHistsSizeWLT16()
|
||||
{
|
||||
return D_TOTAL_SMEM;
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//FUNCTIONS COMMON TO THE DOWNSWEEP / DIGIT BINNING PASS
|
||||
//*****************************************************************************
|
||||
//If the size of a wave is too small, we do not have enough space in
|
||||
//shared memory to assign a histogram to each wave, so instead,
|
||||
//some operations are peformed serially.
|
||||
inline uint SerialIterations(uint waveSize)
|
||||
{
|
||||
return (D_DIM / waveSize + 31) >> 5;
|
||||
}
|
||||
|
||||
inline void ClearWaveHists(uint gtid, uint waveSize)
|
||||
{
|
||||
const uint histsEnd = waveSize >= 16 ?
|
||||
WaveHistsSizeWGE16(waveSize) : WaveHistsSizeWLT16();
|
||||
for (uint i = gtid; i < histsEnd; i += D_DIM)
|
||||
g_d[i] = 0;
|
||||
}
|
||||
|
||||
inline void LoadKey(inout uint key, uint index)
|
||||
{
|
||||
#if defined(KEY_UINT)
|
||||
key = b_sort[index];
|
||||
#elif defined(KEY_INT)
|
||||
key = UintToInt(b_sort[index]);
|
||||
#elif defined(KEY_FLOAT)
|
||||
key = FloatToUint(b_sort[index]);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void LoadDummyKey(inout uint key)
|
||||
{
|
||||
key = 0xffffffff;
|
||||
}
|
||||
|
||||
inline KeyStruct LoadKeysWGE16(uint gtid, uint waveSize, uint partIndex)
|
||||
{
|
||||
KeyStruct keys;
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize)
|
||||
{
|
||||
LoadKey(keys.k[i], t);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
inline KeyStruct LoadKeysWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
|
||||
{
|
||||
KeyStruct keys;
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize * serialIterations)
|
||||
{
|
||||
LoadKey(keys.k[i], t);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
inline KeyStruct LoadKeysPartialWGE16(uint gtid, uint waveSize, uint partIndex)
|
||||
{
|
||||
KeyStruct keys;
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize)
|
||||
{
|
||||
if (t < e_numKeys)
|
||||
LoadKey(keys.k[i], t);
|
||||
else
|
||||
LoadDummyKey(keys.k[i]);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
inline KeyStruct LoadKeysPartialWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
|
||||
{
|
||||
KeyStruct keys;
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize * serialIterations)
|
||||
{
|
||||
if (t < e_numKeys)
|
||||
LoadKey(keys.k[i], t);
|
||||
else
|
||||
LoadDummyKey(keys.k[i]);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
inline uint WaveFlagsWGE16(uint waveSize)
|
||||
{
|
||||
return (waveSize & 31) ? (1U << waveSize) - 1 : 0xffffffff;
|
||||
}
|
||||
|
||||
inline uint WaveFlagsWLT16(uint waveSize)
|
||||
{
|
||||
return (1U << waveSize) - 1;;
|
||||
}
|
||||
|
||||
inline void WarpLevelMultiSplitWGE16(uint key, inout uint4 waveFlags)
|
||||
{
|
||||
[unroll]
|
||||
for (uint k = 0; k < RADIX_LOG; ++k)
|
||||
{
|
||||
const uint currentBit = 1 << k + e_radixShift;
|
||||
const bool t = (key & currentBit) != 0;
|
||||
GroupMemoryBarrierWithGroupSync(); //Play on the safe side, throw in a barrier for convergence
|
||||
const uint4 ballot = WaveActiveBallot(t);
|
||||
if(t)
|
||||
waveFlags &= ballot;
|
||||
else
|
||||
waveFlags &= (~ballot);
|
||||
}
|
||||
}
|
||||
|
||||
inline uint2 CountBitsWGE16(uint waveSize, uint ltMask, uint4 waveFlags)
|
||||
{
|
||||
uint2 count = uint2(0, 0);
|
||||
|
||||
for(uint wavePart = 0; wavePart < waveSize; wavePart += 32)
|
||||
{
|
||||
uint t = countbits(waveFlags[wavePart >> 5]);
|
||||
if (WaveGetLaneIndex() >= wavePart)
|
||||
{
|
||||
if (WaveGetLaneIndex() >= wavePart + 32)
|
||||
count.x += t;
|
||||
else
|
||||
count.x += countbits(waveFlags[wavePart >> 5] & ltMask);
|
||||
}
|
||||
count.y += t;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
inline void WarpLevelMultiSplitWLT16(uint key, inout uint waveFlags)
|
||||
{
|
||||
[unroll]
|
||||
for (uint k = 0; k < RADIX_LOG; ++k)
|
||||
{
|
||||
const bool t = key >> (k + e_radixShift) & 1;
|
||||
waveFlags &= (t ? 0 : 0xffffffff) ^ (uint) WaveActiveBallot(t);
|
||||
}
|
||||
}
|
||||
|
||||
inline OffsetStruct RankKeysWGE16(
|
||||
uint waveSize,
|
||||
uint waveOffset,
|
||||
KeyStruct keys)
|
||||
{
|
||||
OffsetStruct offsets;
|
||||
const uint initialFlags = WaveFlagsWGE16(waveSize);
|
||||
const uint ltMask = (1U << (WaveGetLaneIndex() & 31)) - 1;
|
||||
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
{
|
||||
uint4 waveFlags = initialFlags;
|
||||
WarpLevelMultiSplitWGE16(keys.k[i], waveFlags);
|
||||
|
||||
const uint index = ExtractDigit(keys.k[i]) + waveOffset;
|
||||
const uint2 bitCount = CountBitsWGE16(waveSize, ltMask, waveFlags);
|
||||
|
||||
offsets.o[i] = g_d[index] + bitCount.x;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (bitCount.x == 0)
|
||||
g_d[index] += bitCount.y;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
inline OffsetStruct RankKeysWLT16(uint waveSize, uint waveIndex, KeyStruct keys, uint serialIterations)
|
||||
{
|
||||
OffsetStruct offsets;
|
||||
const uint ltMask = (1U << WaveGetLaneIndex()) - 1;
|
||||
const uint initialFlags = WaveFlagsWLT16(waveSize);
|
||||
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
{
|
||||
uint waveFlags = initialFlags;
|
||||
WarpLevelMultiSplitWLT16(keys.k[i], waveFlags);
|
||||
|
||||
const uint index = ExtractPackedIndex(keys.k[i]) +
|
||||
(waveIndex / serialIterations * HALF_RADIX);
|
||||
|
||||
const uint peerBits = countbits(waveFlags & ltMask);
|
||||
for (uint k = 0; k < serialIterations; ++k)
|
||||
{
|
||||
if (waveIndex % serialIterations == k)
|
||||
offsets.o[i] = ExtractPackedValue(g_d[index], keys.k[i]) + peerBits;
|
||||
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (waveIndex % serialIterations == k && peerBits == 0)
|
||||
{
|
||||
InterlockedAdd(g_d[index],
|
||||
countbits(waveFlags) << ExtractPackedShift(keys.k[i]));
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
}
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
inline uint WaveHistInclusiveScanCircularShiftWGE16(uint gtid, uint waveSize)
|
||||
{
|
||||
uint histReduction = g_d[gtid];
|
||||
for (uint i = gtid + RADIX; i < WaveHistsSizeWGE16(waveSize); i += RADIX)
|
||||
{
|
||||
histReduction += g_d[i];
|
||||
g_d[i] = histReduction - g_d[i];
|
||||
}
|
||||
return histReduction;
|
||||
}
|
||||
|
||||
inline uint WaveHistInclusiveScanCircularShiftWLT16(uint gtid)
|
||||
{
|
||||
uint histReduction = g_d[gtid];
|
||||
for (uint i = gtid + HALF_RADIX; i < WaveHistsSizeWLT16(); i += HALF_RADIX)
|
||||
{
|
||||
histReduction += g_d[i];
|
||||
g_d[i] = histReduction - g_d[i];
|
||||
}
|
||||
return histReduction;
|
||||
}
|
||||
|
||||
inline void WaveHistReductionExclusiveScanWGE16(uint gtid, uint waveSize, uint histReduction)
|
||||
{
|
||||
if (gtid < RADIX)
|
||||
{
|
||||
const uint laneMask = waveSize - 1;
|
||||
g_d[((WaveGetLaneIndex() + 1) & laneMask) + (gtid & ~laneMask)] = histReduction;
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gtid < RADIX / waveSize)
|
||||
{
|
||||
g_d[gtid * waveSize] =
|
||||
WavePrefixSum(g_d[gtid * waveSize]);
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
uint t = WaveReadLaneAt(g_d[gtid], 0);
|
||||
if (gtid < RADIX && WaveGetLaneIndex())
|
||||
g_d[gtid] += t;
|
||||
}
|
||||
|
||||
//inclusive/exclusive prefix sum up the histograms,
|
||||
//use a blelloch scan for in place packed exclusive
|
||||
inline void WaveHistReductionExclusiveScanWLT16(uint gtid)
|
||||
{
|
||||
uint shift = 1;
|
||||
for (uint j = RADIX >> 2; j > 0; j >>= 1)
|
||||
{
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (gtid < j)
|
||||
{
|
||||
g_d[((((gtid << 1) + 2) << shift) - 1) >> 1] +=
|
||||
g_d[((((gtid << 1) + 1) << shift) - 1) >> 1] & 0xffff0000;
|
||||
}
|
||||
shift++;
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
if (gtid == 0)
|
||||
g_d[HALF_RADIX - 1] &= 0xffff;
|
||||
|
||||
for (uint j = 1; j < RADIX >> 1; j <<= 1)
|
||||
{
|
||||
--shift;
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (gtid < j)
|
||||
{
|
||||
const uint t = ((((gtid << 1) + 1) << shift) - 1) >> 1;
|
||||
const uint t2 = ((((gtid << 1) + 2) << shift) - 1) >> 1;
|
||||
const uint t3 = g_d[t];
|
||||
g_d[t] = (g_d[t] & 0xffff) | (g_d[t2] & 0xffff0000);
|
||||
g_d[t2] += t3 & 0xffff0000;
|
||||
}
|
||||
}
|
||||
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
if (gtid < HALF_RADIX)
|
||||
{
|
||||
const uint t = g_d[gtid];
|
||||
g_d[gtid] = (t >> 16) + (t << 16) + (t & 0xffff0000);
|
||||
}
|
||||
}
|
||||
|
||||
inline void UpdateOffsetsWGE16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
inout OffsetStruct offsets,
|
||||
KeyStruct keys)
|
||||
{
|
||||
if (gtid >= waveSize)
|
||||
{
|
||||
const uint t = getWaveIndex(gtid, waveSize) * RADIX;
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
{
|
||||
const uint t2 = ExtractDigit(keys.k[i]);
|
||||
offsets.o[i] += g_d[t2 + t] + g_d[t2];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
offsets.o[i] += g_d[ExtractDigit(keys.k[i])];
|
||||
}
|
||||
}
|
||||
|
||||
inline void UpdateOffsetsWLT16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint serialIterations,
|
||||
inout OffsetStruct offsets,
|
||||
KeyStruct keys)
|
||||
{
|
||||
if (gtid >= waveSize * serialIterations)
|
||||
{
|
||||
const uint t = getWaveIndex(gtid, waveSize) / serialIterations * HALF_RADIX;
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
{
|
||||
const uint t2 = ExtractPackedIndex(keys.k[i]);
|
||||
offsets.o[i] += ExtractPackedValue(g_d[t2 + t] + g_d[t2], keys.k[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
offsets.o[i] += ExtractPackedValue(g_d[ExtractPackedIndex(keys.k[i])], keys.k[i]);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterKeysShared(OffsetStruct offsets, KeyStruct keys)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
|
||||
g_d[offsets.o[i]] = keys.k[i];
|
||||
}
|
||||
|
||||
inline uint DescendingIndex(uint deviceIndex)
|
||||
{
|
||||
return e_numKeys - deviceIndex - 1;
|
||||
}
|
||||
|
||||
inline void WriteKey(uint deviceIndex, uint groupSharedIndex)
|
||||
{
|
||||
#if defined(KEY_UINT)
|
||||
b_alt[deviceIndex] = g_d[groupSharedIndex];
|
||||
#elif defined(KEY_INT)
|
||||
b_alt[deviceIndex] = UintToInt(g_d[groupSharedIndex]);
|
||||
#elif defined(KEY_FLOAT)
|
||||
b_alt[deviceIndex] = UintToFloat(g_d[groupSharedIndex]);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void LoadPayload(inout uint payload, uint deviceIndex)
|
||||
{
|
||||
#if defined(PAYLOAD_UINT)
|
||||
payload = b_sortPayload[deviceIndex];
|
||||
#elif defined(PAYLOAD_INT) || defined(PAYLOAD_FLOAT)
|
||||
payload = asuint(b_sortPayload[deviceIndex]);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ScatterPayloadsShared(OffsetStruct offsets, KeyStruct payloads)
|
||||
{
|
||||
ScatterKeysShared(offsets, payloads);
|
||||
}
|
||||
|
||||
inline void WritePayload(uint deviceIndex, uint groupSharedIndex)
|
||||
{
|
||||
#if defined(PAYLOAD_UINT)
|
||||
b_altPayload[deviceIndex] = g_d[groupSharedIndex];
|
||||
#elif defined(PAYLOAD_INT)
|
||||
b_altPayload[deviceIndex] = asint(g_d[groupSharedIndex]);
|
||||
#elif defined(PAYLOAD_FLOAT)
|
||||
b_altPayload[deviceIndex] = asfloat(g_d[groupSharedIndex]);
|
||||
#endif
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//SCATTERING: FULL PARTITIONS
|
||||
//*****************************************************************************
|
||||
//KEYS ONLY
|
||||
inline void ScatterKeysOnlyDeviceAscending(uint gtid)
|
||||
{
|
||||
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
|
||||
WriteKey(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i, i);
|
||||
}
|
||||
|
||||
inline void ScatterKeysOnlyDeviceDescending(uint gtid)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
|
||||
WriteKey(DescendingIndex(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i), i);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterKeysOnlyDeviceAscending(gtid);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterKeysOnlyDevice(uint gtid)
|
||||
{
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterKeysOnlyDeviceAscending(gtid);
|
||||
#else
|
||||
ScatterKeysOnlyDeviceDescending(gtid);
|
||||
#endif
|
||||
}
|
||||
|
||||
//KEY VALUE PAIRS
|
||||
inline void ScatterPairsKeyPhaseAscending(
|
||||
uint gtid,
|
||||
inout DigitStruct digits)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
digits.d[i] = ExtractDigit(g_d[t]);
|
||||
WriteKey(g_d[digits.d[i] + PART_SIZE] + t, t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPairsKeyPhaseDescending(
|
||||
uint gtid,
|
||||
inout DigitStruct digits)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
digits.d[i] = ExtractDigit(g_d[t]);
|
||||
WriteKey(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterPairsKeyPhaseAscending(gtid, digits);
|
||||
}
|
||||
}
|
||||
|
||||
inline void LoadPayloadsWGE16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
inout KeyStruct payloads)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize)
|
||||
{
|
||||
LoadPayload(payloads.k[i], t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void LoadPayloadsWLT16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
uint serialIterations,
|
||||
inout KeyStruct payloads)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize * serialIterations)
|
||||
{
|
||||
LoadPayload(payloads.k[i], t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPayloadsAscending(uint gtid, DigitStruct digits)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
WritePayload(g_d[digits.d[i] + PART_SIZE] + t, t);
|
||||
}
|
||||
|
||||
inline void ScatterPayloadsDescending(uint gtid, DigitStruct digits)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
WritePayload(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterPayloadsAscending(gtid, digits);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPairsDevice(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
OffsetStruct offsets)
|
||||
{
|
||||
DigitStruct digits;
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterPairsKeyPhaseAscending(gtid, digits);
|
||||
#else
|
||||
ScatterPairsKeyPhaseDescending(gtid, digits);
|
||||
#endif
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
KeyStruct payloads;
|
||||
if (waveSize >= 16)
|
||||
LoadPayloadsWGE16(gtid, waveSize, partIndex, payloads);
|
||||
else
|
||||
LoadPayloadsWLT16(gtid, waveSize, partIndex, SerialIterations(waveSize), payloads);
|
||||
ScatterPayloadsShared(offsets, payloads);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterPayloadsAscending(gtid, digits);
|
||||
#else
|
||||
ScatterPayloadsDescending(gtid, digits);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ScatterDevice(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
OffsetStruct offsets)
|
||||
{
|
||||
#if defined(SORT_PAIRS)
|
||||
ScatterPairsDevice(
|
||||
gtid,
|
||||
waveSize,
|
||||
partIndex,
|
||||
offsets);
|
||||
#else
|
||||
ScatterKeysOnlyDevice(gtid);
|
||||
#endif
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
//SCATTERING: PARTIAL PARTITIONS
|
||||
//*****************************************************************************
|
||||
//KEYS ONLY
|
||||
inline void ScatterKeysOnlyDevicePartialAscending(uint gtid, uint finalPartSize)
|
||||
{
|
||||
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
|
||||
{
|
||||
if (i < finalPartSize)
|
||||
WriteKey(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i, i);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterKeysOnlyDevicePartialDescending(uint gtid, uint finalPartSize)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
|
||||
{
|
||||
if (i < finalPartSize)
|
||||
WriteKey(DescendingIndex(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i), i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterKeysOnlyDevicePartialAscending(gtid, finalPartSize);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterKeysOnlyDevicePartial(uint gtid, uint partIndex)
|
||||
{
|
||||
const uint finalPartSize = e_numKeys - partIndex * PART_SIZE;
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterKeysOnlyDevicePartialAscending(gtid, finalPartSize);
|
||||
#else
|
||||
ScatterKeysOnlyDevicePartialDescending(gtid, finalPartSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
//KEY VALUE PAIRS
|
||||
inline void ScatterPairsKeyPhaseAscendingPartial(
|
||||
uint gtid,
|
||||
uint finalPartSize,
|
||||
inout DigitStruct digits)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
if (t < finalPartSize)
|
||||
{
|
||||
digits.d[i] = ExtractDigit(g_d[t]);
|
||||
WriteKey(g_d[digits.d[i] + PART_SIZE] + t, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPairsKeyPhaseDescendingPartial(
|
||||
uint gtid,
|
||||
uint finalPartSize,
|
||||
inout DigitStruct digits)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
if (t < finalPartSize)
|
||||
{
|
||||
digits.d[i] = ExtractDigit(g_d[t]);
|
||||
WriteKey(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterPairsKeyPhaseAscendingPartial(gtid, finalPartSize, digits);
|
||||
}
|
||||
}
|
||||
|
||||
inline void LoadPayloadsPartialWGE16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
inout KeyStruct payloads)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize)
|
||||
{
|
||||
if (t < e_numKeys)
|
||||
LoadPayload(payloads.k[i], t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void LoadPayloadsPartialWLT16(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
uint serialIterations,
|
||||
inout KeyStruct payloads)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
|
||||
i < KEYS_PER_THREAD;
|
||||
++i, t += waveSize * serialIterations)
|
||||
{
|
||||
if (t < e_numKeys)
|
||||
LoadPayload(payloads.k[i], t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPayloadsAscendingPartial(
|
||||
uint gtid,
|
||||
uint finalPartSize,
|
||||
DigitStruct digits)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
if (t < finalPartSize)
|
||||
WritePayload(g_d[digits.d[i] + PART_SIZE] + t, t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPayloadsDescendingPartial(
|
||||
uint gtid,
|
||||
uint finalPartSize,
|
||||
DigitStruct digits)
|
||||
{
|
||||
if (e_radixShift == 24)
|
||||
{
|
||||
[unroll]
|
||||
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
|
||||
{
|
||||
if (t < finalPartSize)
|
||||
WritePayload(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScatterPayloadsAscendingPartial(gtid, finalPartSize, digits);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ScatterPairsDevicePartial(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
OffsetStruct offsets)
|
||||
{
|
||||
DigitStruct digits;
|
||||
const uint finalPartSize = e_numKeys - partIndex * PART_SIZE;
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterPairsKeyPhaseAscendingPartial(gtid, finalPartSize, digits);
|
||||
#else
|
||||
ScatterPairsKeyPhaseDescendingPartial(gtid, finalPartSize, digits);
|
||||
#endif
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
KeyStruct payloads;
|
||||
if (waveSize >= 16)
|
||||
LoadPayloadsPartialWGE16(gtid, waveSize, partIndex, payloads);
|
||||
else
|
||||
LoadPayloadsPartialWLT16(gtid, waveSize, partIndex, SerialIterations(waveSize), payloads);
|
||||
ScatterPayloadsShared(offsets, payloads);
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
#if defined(SHOULD_ASCEND)
|
||||
ScatterPayloadsAscendingPartial(gtid, finalPartSize, digits);
|
||||
#else
|
||||
ScatterPayloadsDescendingPartial(gtid, finalPartSize, digits);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ScatterDevicePartial(
|
||||
uint gtid,
|
||||
uint waveSize,
|
||||
uint partIndex,
|
||||
OffsetStruct offsets)
|
||||
{
|
||||
#if defined(SORT_PAIRS)
|
||||
ScatterPairsDevicePartial(
|
||||
gtid,
|
||||
waveSize,
|
||||
partIndex,
|
||||
offsets);
|
||||
#else
|
||||
ScatterKeysOnlyDevicePartial(gtid, partIndex);
|
||||
#endif
|
||||
}
|
||||
7
MVS/3DGS-Unity/Shaders/SortCommon.hlsl.meta
Normal file
7
MVS/3DGS-Unity/Shaders/SortCommon.hlsl.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 268e5936ab6d79f4b8aeef8f5d14e7ee
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user