├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── mandelbrot │ ├── Cargo.toml │ └── mandelbrot.rs ├── raymarching_eyes │ ├── Cargo.toml │ ├── raymarching_eyes.rs │ └── sdf_utils.rs ├── simple │ ├── Cargo.toml │ └── simple.rs └── two_dimensional_sdf │ ├── Cargo.toml │ └── two_dimensional_sdf.rs ├── metal_sl_prelude ├── Cargo.toml ├── prelude_macros │ ├── Cargo.toml │ ├── prelude_proc_macros │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── access.rs │ │ │ ├── construct.rs │ │ │ └── lib.rs │ └── src │ │ └── lib.rs └── src │ ├── access.rs │ ├── common.rs │ ├── construct.rs │ ├── generic.rs │ ├── geometric.rs │ ├── graphics.rs │ ├── integer.rs │ ├── math.rs │ ├── matrix.rs │ ├── metal_sl_prelude.rs │ ├── relational.rs │ └── types.rs ├── regex_try ├── Cargo.toml └── src │ └── lib.rs ├── rust_to_metal_sl ├── Cargo.toml └── src │ ├── adapter.rs │ ├── enhancer.rs │ ├── parser.rs │ ├── printer.rs │ └── rust_to_metal_sl.rs ├── shader_roy ├── Cargo.toml └── src │ ├── shader_compiler.rs │ ├── shader_file_path_arg.rs │ ├── shader_prelude.metal │ └── shader_roy.rs └── shader_roy_metal_sl_interface ├── Cargo.toml └── src └── shader_roy_metal_sl_interface.rs /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | **/*.rs.bk 4 | 5 | ./hot_reload_buffer -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ab_glyph_rasterizer" 5 | version = "0.1.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "d9fe5e32de01730eb1f6b7f5b51c17e03e2325bf40a74f754f04f130043affff" 8 | 9 | [[package]] 10 | name = "aho-corasick" 11 | version = "0.7.15" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 14 | dependencies = [ 15 | "memchr", 16 | ] 17 | 18 | [[package]] 19 | name = "andrew" 20 | version = "0.3.1" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "8c4afb09dd642feec8408e33f92f3ffc4052946f6b20f32fb99c1f58cd4fa7cf" 23 | dependencies = [ 24 | "bitflags", 25 | "rusttype", 26 | "walkdir", 27 | "xdg", 28 | "xml-rs", 29 | ] 30 | 31 | [[package]] 32 | name = "anyhow" 33 | version = "1.0.38" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" 36 | 37 | [[package]] 38 | name = "approx" 39 | version = "0.4.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" 42 | dependencies = [ 43 | "num-traits", 44 | ] 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.0.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 51 | 52 | [[package]] 53 | name = "bitflags" 54 | version = "1.2.1" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 57 | 58 | [[package]] 59 | name = "block" 60 | version = "0.1.6" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 63 | 64 | [[package]] 65 | name = "calloop" 66 | version = "0.6.5" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "0b036167e76041694579972c28cf4877b4f92da222560ddb49008937b6a6727c" 69 | dependencies = [ 70 | "log", 71 | "nix", 72 | ] 73 | 74 | [[package]] 75 | name = "cc" 76 | version = "1.0.66" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" 79 | 80 | [[package]] 81 | name = "cfg-if" 82 | version = "0.1.10" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 85 | 86 | [[package]] 87 | name = "cfg-if" 88 | version = "1.0.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 91 | 92 | [[package]] 93 | name = "chrono" 94 | version = "0.4.19" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 97 | dependencies = [ 98 | "libc", 99 | "num-integer", 100 | "num-traits", 101 | "time", 102 | "winapi 0.3.9", 103 | ] 104 | 105 | [[package]] 106 | name = "cocoa" 107 | version = "0.24.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" 110 | dependencies = [ 111 | "bitflags", 112 | "block", 113 | "cocoa-foundation", 114 | "core-foundation 0.9.1", 115 | "core-graphics 0.22.2", 116 | "foreign-types", 117 | "libc", 118 | "objc", 119 | ] 120 | 121 | [[package]] 122 | name = "cocoa-foundation" 123 | version = "0.1.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" 126 | dependencies = [ 127 | "bitflags", 128 | "block", 129 | "core-foundation 0.9.1", 130 | "core-graphics-types", 131 | "foreign-types", 132 | "libc", 133 | "objc", 134 | ] 135 | 136 | [[package]] 137 | name = "core-foundation" 138 | version = "0.7.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" 141 | dependencies = [ 142 | "core-foundation-sys 0.7.0", 143 | "libc", 144 | ] 145 | 146 | [[package]] 147 | name = "core-foundation" 148 | version = "0.9.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" 151 | dependencies = [ 152 | "core-foundation-sys 0.8.2", 153 | "libc", 154 | ] 155 | 156 | [[package]] 157 | name = "core-foundation-sys" 158 | version = "0.7.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" 161 | 162 | [[package]] 163 | name = "core-foundation-sys" 164 | version = "0.8.2" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" 167 | 168 | [[package]] 169 | name = "core-graphics" 170 | version = "0.19.2" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" 173 | dependencies = [ 174 | "bitflags", 175 | "core-foundation 0.7.0", 176 | "foreign-types", 177 | "libc", 178 | ] 179 | 180 | [[package]] 181 | name = "core-graphics" 182 | version = "0.22.2" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86" 185 | dependencies = [ 186 | "bitflags", 187 | "core-foundation 0.9.1", 188 | "core-graphics-types", 189 | "foreign-types", 190 | "libc", 191 | ] 192 | 193 | [[package]] 194 | name = "core-graphics-types" 195 | version = "0.1.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" 198 | dependencies = [ 199 | "bitflags", 200 | "core-foundation 0.9.1", 201 | "foreign-types", 202 | "libc", 203 | ] 204 | 205 | [[package]] 206 | name = "core-video-sys" 207 | version = "0.1.4" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" 210 | dependencies = [ 211 | "cfg-if 0.1.10", 212 | "core-foundation-sys 0.7.0", 213 | "core-graphics 0.19.2", 214 | "libc", 215 | "objc", 216 | ] 217 | 218 | [[package]] 219 | name = "darling" 220 | version = "0.10.2" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" 223 | dependencies = [ 224 | "darling_core", 225 | "darling_macro", 226 | ] 227 | 228 | [[package]] 229 | name = "darling_core" 230 | version = "0.10.2" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" 233 | dependencies = [ 234 | "fnv", 235 | "ident_case", 236 | "proc-macro2", 237 | "quote", 238 | "strsim", 239 | "syn", 240 | ] 241 | 242 | [[package]] 243 | name = "darling_macro" 244 | version = "0.10.2" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" 247 | dependencies = [ 248 | "darling_core", 249 | "quote", 250 | "syn", 251 | ] 252 | 253 | [[package]] 254 | name = "derivative" 255 | version = "2.1.3" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "eaed5874effa6cde088c644ddcdcb4ffd1511391c5be4fdd7a5ccd02c7e4a183" 258 | dependencies = [ 259 | "proc-macro2", 260 | "quote", 261 | "syn", 262 | ] 263 | 264 | [[package]] 265 | name = "dispatch" 266 | version = "0.2.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" 269 | 270 | [[package]] 271 | name = "dlib" 272 | version = "0.4.2" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76" 275 | dependencies = [ 276 | "libloading", 277 | ] 278 | 279 | [[package]] 280 | name = "downcast-rs" 281 | version = "1.2.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 284 | 285 | [[package]] 286 | name = "filetime" 287 | version = "0.2.14" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" 290 | dependencies = [ 291 | "cfg-if 1.0.0", 292 | "libc", 293 | "redox_syscall 0.2.4", 294 | "winapi 0.3.9", 295 | ] 296 | 297 | [[package]] 298 | name = "fnv" 299 | version = "1.0.7" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 302 | 303 | [[package]] 304 | name = "foreign-types" 305 | version = "0.3.2" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 308 | dependencies = [ 309 | "foreign-types-shared", 310 | ] 311 | 312 | [[package]] 313 | name = "foreign-types-shared" 314 | version = "0.1.1" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 317 | 318 | [[package]] 319 | name = "fsevent" 320 | version = "0.4.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" 323 | dependencies = [ 324 | "bitflags", 325 | "fsevent-sys", 326 | ] 327 | 328 | [[package]] 329 | name = "fsevent-sys" 330 | version = "2.0.1" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" 333 | dependencies = [ 334 | "libc", 335 | ] 336 | 337 | [[package]] 338 | name = "fuchsia-zircon" 339 | version = "0.3.3" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 342 | dependencies = [ 343 | "bitflags", 344 | "fuchsia-zircon-sys", 345 | ] 346 | 347 | [[package]] 348 | name = "fuchsia-zircon-sys" 349 | version = "0.3.3" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 352 | 353 | [[package]] 354 | name = "half" 355 | version = "1.7.1" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" 358 | 359 | [[package]] 360 | name = "ident_case" 361 | version = "1.0.1" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 364 | 365 | [[package]] 366 | name = "inotify" 367 | version = "0.7.1" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" 370 | dependencies = [ 371 | "bitflags", 372 | "inotify-sys", 373 | "libc", 374 | ] 375 | 376 | [[package]] 377 | name = "inotify-sys" 378 | version = "0.1.5" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 381 | dependencies = [ 382 | "libc", 383 | ] 384 | 385 | [[package]] 386 | name = "instant" 387 | version = "0.1.9" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 390 | dependencies = [ 391 | "cfg-if 1.0.0", 392 | ] 393 | 394 | [[package]] 395 | name = "iovec" 396 | version = "0.1.4" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 399 | dependencies = [ 400 | "libc", 401 | ] 402 | 403 | [[package]] 404 | name = "jni-sys" 405 | version = "0.3.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 408 | 409 | [[package]] 410 | name = "kernel32-sys" 411 | version = "0.2.2" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 414 | dependencies = [ 415 | "winapi 0.2.8", 416 | "winapi-build", 417 | ] 418 | 419 | [[package]] 420 | name = "lazy_static" 421 | version = "1.4.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 424 | 425 | [[package]] 426 | name = "lazycell" 427 | version = "1.3.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 430 | 431 | [[package]] 432 | name = "libc" 433 | version = "0.2.82" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929" 436 | 437 | [[package]] 438 | name = "libloading" 439 | version = "0.6.7" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" 442 | dependencies = [ 443 | "cfg-if 1.0.0", 444 | "winapi 0.3.9", 445 | ] 446 | 447 | [[package]] 448 | name = "lock_api" 449 | version = "0.4.2" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" 452 | dependencies = [ 453 | "scopeguard", 454 | ] 455 | 456 | [[package]] 457 | name = "log" 458 | version = "0.4.13" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2" 461 | dependencies = [ 462 | "cfg-if 0.1.10", 463 | ] 464 | 465 | [[package]] 466 | name = "malloc_buf" 467 | version = "0.0.6" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 470 | dependencies = [ 471 | "libc", 472 | ] 473 | 474 | [[package]] 475 | name = "mandelbrot" 476 | version = "0.1.0" 477 | dependencies = [ 478 | "shader_roy_metal_sl_interface", 479 | ] 480 | 481 | [[package]] 482 | name = "maplit" 483 | version = "1.0.2" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 486 | 487 | [[package]] 488 | name = "maybe-uninit" 489 | version = "2.0.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 492 | 493 | [[package]] 494 | name = "memchr" 495 | version = "2.3.4" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 498 | 499 | [[package]] 500 | name = "memmap2" 501 | version = "0.1.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "d9b70ca2a6103ac8b665dc150b142ef0e4e89df640c9e6cf295d189c3caebe5a" 504 | dependencies = [ 505 | "libc", 506 | ] 507 | 508 | [[package]] 509 | name = "metal" 510 | version = "0.20.1" 511 | source = "git+https://github.com/xixixao/metal-rs?branch=fix-errors-new-library#137fc413bc3af326e554ac8693d8988e24aa1682" 512 | dependencies = [ 513 | "bitflags", 514 | "block", 515 | "cocoa-foundation", 516 | "foreign-types", 517 | "log", 518 | "objc", 519 | ] 520 | 521 | [[package]] 522 | name = "metal_sl_prelude" 523 | version = "0.1.0" 524 | dependencies = [ 525 | "half", 526 | "num", 527 | "prelude_macros", 528 | "vek", 529 | ] 530 | 531 | [[package]] 532 | name = "mio" 533 | version = "0.6.23" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" 536 | dependencies = [ 537 | "cfg-if 0.1.10", 538 | "fuchsia-zircon", 539 | "fuchsia-zircon-sys", 540 | "iovec", 541 | "kernel32-sys", 542 | "libc", 543 | "log", 544 | "miow", 545 | "net2", 546 | "slab", 547 | "winapi 0.2.8", 548 | ] 549 | 550 | [[package]] 551 | name = "mio-extras" 552 | version = "2.0.6" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" 555 | dependencies = [ 556 | "lazycell", 557 | "log", 558 | "mio", 559 | "slab", 560 | ] 561 | 562 | [[package]] 563 | name = "miow" 564 | version = "0.2.2" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" 567 | dependencies = [ 568 | "kernel32-sys", 569 | "net2", 570 | "winapi 0.2.8", 571 | "ws2_32-sys", 572 | ] 573 | 574 | [[package]] 575 | name = "ndk" 576 | version = "0.3.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab" 579 | dependencies = [ 580 | "jni-sys", 581 | "ndk-sys", 582 | "num_enum", 583 | "thiserror", 584 | ] 585 | 586 | [[package]] 587 | name = "ndk-glue" 588 | version = "0.3.0" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "c5caf0c24d51ac1c905c27d4eda4fa0635bbe0de596b8f79235e0b17a4d29385" 591 | dependencies = [ 592 | "lazy_static", 593 | "libc", 594 | "log", 595 | "ndk", 596 | "ndk-macro", 597 | "ndk-sys", 598 | ] 599 | 600 | [[package]] 601 | name = "ndk-macro" 602 | version = "0.2.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" 605 | dependencies = [ 606 | "darling", 607 | "proc-macro-crate", 608 | "proc-macro2", 609 | "quote", 610 | "syn", 611 | ] 612 | 613 | [[package]] 614 | name = "ndk-sys" 615 | version = "0.2.1" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" 618 | 619 | [[package]] 620 | name = "net2" 621 | version = "0.2.37" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" 624 | dependencies = [ 625 | "cfg-if 0.1.10", 626 | "libc", 627 | "winapi 0.3.9", 628 | ] 629 | 630 | [[package]] 631 | name = "nix" 632 | version = "0.18.0" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055" 635 | dependencies = [ 636 | "bitflags", 637 | "cc", 638 | "cfg-if 0.1.10", 639 | "libc", 640 | ] 641 | 642 | [[package]] 643 | name = "nom" 644 | version = "6.0.1" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "88034cfd6b4a0d54dd14f4a507eceee36c0b70e5a02236c4e4df571102be17f0" 647 | dependencies = [ 648 | "memchr", 649 | "version_check", 650 | ] 651 | 652 | [[package]] 653 | name = "notify" 654 | version = "4.0.15" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" 657 | dependencies = [ 658 | "bitflags", 659 | "filetime", 660 | "fsevent", 661 | "fsevent-sys", 662 | "inotify", 663 | "libc", 664 | "mio", 665 | "mio-extras", 666 | "walkdir", 667 | "winapi 0.3.9", 668 | ] 669 | 670 | [[package]] 671 | name = "num" 672 | version = "0.3.1" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" 675 | dependencies = [ 676 | "num-bigint", 677 | "num-complex", 678 | "num-integer", 679 | "num-iter", 680 | "num-rational", 681 | "num-traits", 682 | ] 683 | 684 | [[package]] 685 | name = "num-bigint" 686 | version = "0.3.1" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf" 689 | dependencies = [ 690 | "autocfg", 691 | "num-integer", 692 | "num-traits", 693 | ] 694 | 695 | [[package]] 696 | name = "num-complex" 697 | version = "0.3.1" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" 700 | dependencies = [ 701 | "num-traits", 702 | ] 703 | 704 | [[package]] 705 | name = "num-integer" 706 | version = "0.1.44" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 709 | dependencies = [ 710 | "autocfg", 711 | "num-traits", 712 | ] 713 | 714 | [[package]] 715 | name = "num-iter" 716 | version = "0.1.42" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" 719 | dependencies = [ 720 | "autocfg", 721 | "num-integer", 722 | "num-traits", 723 | ] 724 | 725 | [[package]] 726 | name = "num-rational" 727 | version = "0.3.2" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" 730 | dependencies = [ 731 | "autocfg", 732 | "num-bigint", 733 | "num-integer", 734 | "num-traits", 735 | ] 736 | 737 | [[package]] 738 | name = "num-traits" 739 | version = "0.2.14" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 742 | dependencies = [ 743 | "autocfg", 744 | ] 745 | 746 | [[package]] 747 | name = "num_enum" 748 | version = "0.5.1" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "226b45a5c2ac4dd696ed30fa6b94b057ad909c7b7fc2e0d0808192bced894066" 751 | dependencies = [ 752 | "derivative", 753 | "num_enum_derive", 754 | ] 755 | 756 | [[package]] 757 | name = "num_enum_derive" 758 | version = "0.5.1" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e" 761 | dependencies = [ 762 | "proc-macro-crate", 763 | "proc-macro2", 764 | "quote", 765 | "syn", 766 | ] 767 | 768 | [[package]] 769 | name = "objc" 770 | version = "0.2.7" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 773 | dependencies = [ 774 | "malloc_buf", 775 | "objc_exception", 776 | ] 777 | 778 | [[package]] 779 | name = "objc_exception" 780 | version = "0.1.2" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" 783 | dependencies = [ 784 | "cc", 785 | ] 786 | 787 | [[package]] 788 | name = "once_cell" 789 | version = "1.5.2" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" 792 | 793 | [[package]] 794 | name = "owned_ttf_parser" 795 | version = "0.6.0" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" 798 | dependencies = [ 799 | "ttf-parser", 800 | ] 801 | 802 | [[package]] 803 | name = "parking_lot" 804 | version = "0.11.1" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 807 | dependencies = [ 808 | "instant", 809 | "lock_api", 810 | "parking_lot_core", 811 | ] 812 | 813 | [[package]] 814 | name = "parking_lot_core" 815 | version = "0.8.2" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" 818 | dependencies = [ 819 | "cfg-if 1.0.0", 820 | "instant", 821 | "libc", 822 | "redox_syscall 0.1.57", 823 | "smallvec", 824 | "winapi 0.3.9", 825 | ] 826 | 827 | [[package]] 828 | name = "path-absolutize" 829 | version = "3.0.6" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "7a6ab2aaa5faefed84db46e4398eab15fa51325606462b5da8b0e230af3ac59a" 832 | dependencies = [ 833 | "path-dedot", 834 | ] 835 | 836 | [[package]] 837 | name = "path-dedot" 838 | version = "3.0.7" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "658c6e985fce9c25289fe7c86c08a3cbe82c19a3cd5b3bc5945c8c632552e460" 841 | dependencies = [ 842 | "once_cell", 843 | ] 844 | 845 | [[package]] 846 | name = "percent-encoding" 847 | version = "2.1.0" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 850 | 851 | [[package]] 852 | name = "pkg-config" 853 | version = "0.3.19" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 856 | 857 | [[package]] 858 | name = "prelude_macros" 859 | version = "0.1.0" 860 | dependencies = [ 861 | "prelude_proc_macros", 862 | "quote", 863 | ] 864 | 865 | [[package]] 866 | name = "prelude_proc_macros" 867 | version = "0.1.0" 868 | dependencies = [ 869 | "quote", 870 | "syn", 871 | "voca_rs", 872 | ] 873 | 874 | [[package]] 875 | name = "proc-macro-crate" 876 | version = "0.1.5" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" 879 | dependencies = [ 880 | "toml", 881 | ] 882 | 883 | [[package]] 884 | name = "proc-macro2" 885 | version = "1.0.24" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 888 | dependencies = [ 889 | "unicode-xid", 890 | ] 891 | 892 | [[package]] 893 | name = "quote" 894 | version = "1.0.8" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" 897 | dependencies = [ 898 | "proc-macro2", 899 | ] 900 | 901 | [[package]] 902 | name = "raw-window-handle" 903 | version = "0.3.3" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" 906 | dependencies = [ 907 | "libc", 908 | ] 909 | 910 | [[package]] 911 | name = "raymarching_eyes" 912 | version = "0.1.0" 913 | dependencies = [ 914 | "shader_roy_metal_sl_interface", 915 | ] 916 | 917 | [[package]] 918 | name = "redox_syscall" 919 | version = "0.1.57" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 922 | 923 | [[package]] 924 | name = "redox_syscall" 925 | version = "0.2.4" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570" 928 | dependencies = [ 929 | "bitflags", 930 | ] 931 | 932 | [[package]] 933 | name = "regex" 934 | version = "1.4.3" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" 937 | dependencies = [ 938 | "aho-corasick", 939 | "memchr", 940 | "regex-syntax", 941 | "thread_local", 942 | ] 943 | 944 | [[package]] 945 | name = "regex-syntax" 946 | version = "0.6.22" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" 949 | 950 | [[package]] 951 | name = "regex_try" 952 | version = "0.1.0" 953 | dependencies = [ 954 | "regex", 955 | ] 956 | 957 | [[package]] 958 | name = "rust_to_metal_sl" 959 | version = "0.1.0" 960 | dependencies = [ 961 | "anyhow", 962 | "lazy_static", 963 | "maplit", 964 | "proc-macro2", 965 | "quote", 966 | "regex", 967 | "syn", 968 | "voca_rs", 969 | ] 970 | 971 | [[package]] 972 | name = "rustc_version" 973 | version = "0.2.3" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 976 | dependencies = [ 977 | "semver", 978 | ] 979 | 980 | [[package]] 981 | name = "rusttype" 982 | version = "0.9.2" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59" 985 | dependencies = [ 986 | "ab_glyph_rasterizer", 987 | "owned_ttf_parser", 988 | ] 989 | 990 | [[package]] 991 | name = "same-file" 992 | version = "1.0.6" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 995 | dependencies = [ 996 | "winapi-util", 997 | ] 998 | 999 | [[package]] 1000 | name = "scoped-tls" 1001 | version = "1.0.0" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 1004 | 1005 | [[package]] 1006 | name = "scopeguard" 1007 | version = "1.1.0" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1010 | 1011 | [[package]] 1012 | name = "semver" 1013 | version = "0.9.0" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 1016 | dependencies = [ 1017 | "semver-parser", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "semver-parser" 1022 | version = "0.7.0" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 1025 | 1026 | [[package]] 1027 | name = "serde" 1028 | version = "1.0.119" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3" 1031 | dependencies = [ 1032 | "serde_derive", 1033 | ] 1034 | 1035 | [[package]] 1036 | name = "serde_derive" 1037 | version = "1.0.119" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "552954ce79a059ddd5fd68c271592374bd15cab2274970380c000118aeffe1cd" 1040 | dependencies = [ 1041 | "proc-macro2", 1042 | "quote", 1043 | "syn", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "shader_roy" 1048 | version = "0.1.0" 1049 | dependencies = [ 1050 | "anyhow", 1051 | "chrono", 1052 | "cocoa", 1053 | "lazy_static", 1054 | "metal", 1055 | "notify", 1056 | "objc", 1057 | "path-absolutize", 1058 | "regex", 1059 | "regex_try", 1060 | "rust_to_metal_sl", 1061 | "toml", 1062 | "vek", 1063 | "winit", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "shader_roy_metal_sl_interface" 1068 | version = "0.1.0" 1069 | dependencies = [ 1070 | "metal_sl_prelude", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "simple" 1075 | version = "0.1.0" 1076 | dependencies = [ 1077 | "shader_roy_metal_sl_interface", 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "slab" 1082 | version = "0.4.2" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1085 | 1086 | [[package]] 1087 | name = "smallvec" 1088 | version = "1.6.1" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 1091 | 1092 | [[package]] 1093 | name = "smithay-client-toolkit" 1094 | version = "0.12.2" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "316e13a3eb853ce7bf72ad3530dc186cb2005c57c521ef5f4ada5ee4eed74de6" 1097 | dependencies = [ 1098 | "andrew", 1099 | "bitflags", 1100 | "calloop", 1101 | "dlib", 1102 | "lazy_static", 1103 | "log", 1104 | "memmap2", 1105 | "nix", 1106 | "wayland-client", 1107 | "wayland-cursor", 1108 | "wayland-protocols", 1109 | ] 1110 | 1111 | [[package]] 1112 | name = "static_assertions" 1113 | version = "1.1.0" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1116 | 1117 | [[package]] 1118 | name = "stfu8" 1119 | version = "0.2.4" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "4bf70433e3300a3c395d06606a700cdf4205f4f14dbae2c6833127c6bb22db77" 1122 | dependencies = [ 1123 | "lazy_static", 1124 | "regex", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "strsim" 1129 | version = "0.9.3" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" 1132 | 1133 | [[package]] 1134 | name = "syn" 1135 | version = "1.0.58" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" 1138 | dependencies = [ 1139 | "proc-macro2", 1140 | "quote", 1141 | "unicode-xid", 1142 | ] 1143 | 1144 | [[package]] 1145 | name = "thiserror" 1146 | version = "1.0.23" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" 1149 | dependencies = [ 1150 | "thiserror-impl", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "thiserror-impl" 1155 | version = "1.0.23" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" 1158 | dependencies = [ 1159 | "proc-macro2", 1160 | "quote", 1161 | "syn", 1162 | ] 1163 | 1164 | [[package]] 1165 | name = "thread_local" 1166 | version = "1.1.0" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447" 1169 | dependencies = [ 1170 | "lazy_static", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "time" 1175 | version = "0.1.44" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1178 | dependencies = [ 1179 | "libc", 1180 | "wasi", 1181 | "winapi 0.3.9", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "toml" 1186 | version = "0.5.8" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 1189 | dependencies = [ 1190 | "serde", 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "ttf-parser" 1195 | version = "0.6.2" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" 1198 | 1199 | [[package]] 1200 | name = "two_dimensional_sdf" 1201 | version = "0.1.0" 1202 | dependencies = [ 1203 | "shader_roy_metal_sl_interface", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "unicode-segmentation" 1208 | version = "1.7.1" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 1211 | 1212 | [[package]] 1213 | name = "unicode-xid" 1214 | version = "0.2.1" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1217 | 1218 | [[package]] 1219 | name = "vek" 1220 | version = "0.13.0" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "5882dab0f0e4b74eceeae3333611a6bf8af56a2c1d80227d23ddb3d2bbae462a" 1223 | dependencies = [ 1224 | "approx", 1225 | "num-integer", 1226 | "num-traits", 1227 | "rustc_version", 1228 | "serde", 1229 | "static_assertions", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "version_check" 1234 | version = "0.9.2" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 1237 | 1238 | [[package]] 1239 | name = "voca_rs" 1240 | version = "1.13.0" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "ec29ce40c253a1579092852bbea5cb4fbcf34c04b91d8127300202aa17c998fc" 1243 | dependencies = [ 1244 | "regex", 1245 | "stfu8", 1246 | "unicode-segmentation", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "walkdir" 1251 | version = "2.3.1" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1254 | dependencies = [ 1255 | "same-file", 1256 | "winapi 0.3.9", 1257 | "winapi-util", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "wasi" 1262 | version = "0.10.0+wasi-snapshot-preview1" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1265 | 1266 | [[package]] 1267 | name = "wayland-client" 1268 | version = "0.28.3" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "bdbdbe01d03b2267809f3ed99495b37395387fde789e0f2ebb78e8b43f75b6d7" 1271 | dependencies = [ 1272 | "bitflags", 1273 | "downcast-rs", 1274 | "libc", 1275 | "nix", 1276 | "scoped-tls", 1277 | "wayland-commons", 1278 | "wayland-scanner", 1279 | "wayland-sys", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "wayland-commons" 1284 | version = "0.28.3" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "480450f76717edd64ad04a4426280d737fc3d10a236b982df7b1aee19f0e2d56" 1287 | dependencies = [ 1288 | "nix", 1289 | "once_cell", 1290 | "smallvec", 1291 | "wayland-sys", 1292 | ] 1293 | 1294 | [[package]] 1295 | name = "wayland-cursor" 1296 | version = "0.28.3" 1297 | source = "registry+https://github.com/rust-lang/crates.io-index" 1298 | checksum = "d6eb122c160223a7660feeaf949d0100281d1279acaaed3720eb3c9894496e5f" 1299 | dependencies = [ 1300 | "nix", 1301 | "wayland-client", 1302 | "xcursor", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "wayland-protocols" 1307 | version = "0.28.3" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "319a82b4d3054dd25acc32d9aee0f84fa95b63bc983fffe4703b6b8d47e01a30" 1310 | dependencies = [ 1311 | "bitflags", 1312 | "wayland-client", 1313 | "wayland-commons", 1314 | "wayland-scanner", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "wayland-scanner" 1319 | version = "0.28.3" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "7010ba5767b3fcd350decc59055390b4ebe6bd1b9279a9feb1f1888987f1133d" 1322 | dependencies = [ 1323 | "proc-macro2", 1324 | "quote", 1325 | "xml-rs", 1326 | ] 1327 | 1328 | [[package]] 1329 | name = "wayland-sys" 1330 | version = "0.28.3" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "6793834e0c35d11fd96a97297abe03d37be627e1847da52e17d7e0e3b51cc099" 1333 | dependencies = [ 1334 | "dlib", 1335 | "lazy_static", 1336 | "pkg-config", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "winapi" 1341 | version = "0.2.8" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1344 | 1345 | [[package]] 1346 | name = "winapi" 1347 | version = "0.3.9" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1350 | dependencies = [ 1351 | "winapi-i686-pc-windows-gnu", 1352 | "winapi-x86_64-pc-windows-gnu", 1353 | ] 1354 | 1355 | [[package]] 1356 | name = "winapi-build" 1357 | version = "0.1.1" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1360 | 1361 | [[package]] 1362 | name = "winapi-i686-pc-windows-gnu" 1363 | version = "0.4.0" 1364 | source = "registry+https://github.com/rust-lang/crates.io-index" 1365 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1366 | 1367 | [[package]] 1368 | name = "winapi-util" 1369 | version = "0.1.5" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1372 | dependencies = [ 1373 | "winapi 0.3.9", 1374 | ] 1375 | 1376 | [[package]] 1377 | name = "winapi-x86_64-pc-windows-gnu" 1378 | version = "0.4.0" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1381 | 1382 | [[package]] 1383 | name = "winit" 1384 | version = "0.24.0" 1385 | source = "git+https://github.com/xixixao/winit?branch=with_outer_position#8c9c88597603900215ee019add4f4d6fe70f42ff" 1386 | dependencies = [ 1387 | "bitflags", 1388 | "cocoa", 1389 | "core-foundation 0.9.1", 1390 | "core-graphics 0.22.2", 1391 | "core-video-sys", 1392 | "dispatch", 1393 | "instant", 1394 | "lazy_static", 1395 | "libc", 1396 | "log", 1397 | "mio", 1398 | "mio-extras", 1399 | "ndk", 1400 | "ndk-glue", 1401 | "ndk-sys", 1402 | "objc", 1403 | "parking_lot", 1404 | "percent-encoding", 1405 | "raw-window-handle", 1406 | "smithay-client-toolkit", 1407 | "wayland-client", 1408 | "winapi 0.3.9", 1409 | "x11-dl", 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "ws2_32-sys" 1414 | version = "0.2.1" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1417 | dependencies = [ 1418 | "winapi 0.2.8", 1419 | "winapi-build", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "x11-dl" 1424 | version = "2.18.5" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8" 1427 | dependencies = [ 1428 | "lazy_static", 1429 | "libc", 1430 | "maybe-uninit", 1431 | "pkg-config", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "xcursor" 1436 | version = "0.3.3" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "3a9a231574ae78801646617cefd13bfe94be907c0e4fa979cfd8b770aa3c5d08" 1439 | dependencies = [ 1440 | "nom", 1441 | ] 1442 | 1443 | [[package]] 1444 | name = "xdg" 1445 | version = "2.2.0" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" 1448 | 1449 | [[package]] 1450 | name = "xml-rs" 1451 | version = "0.8.3" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" 1454 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | default-members=["shader_roy"] 3 | members = [ 4 | "shader_roy", 5 | "examples/mandelbrot", 6 | "examples/raymarching_eyes", 7 | "examples/two_dimensional_sdf", 8 | "examples/simple", 9 | ] 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShaderRoy 2 | 3 | ShaderToy clone in Rust, currently supporting MacOS. 4 | 5 | [![Demo Youtube Video](https://img.youtube.com/vi/FkOE-V9dh-U/maxresdefault.jpg)](https://www.youtube.com/watch?v=FkOE-V9dh-U) 6 | 7 | ## Features 8 | 9 | 1. `cargo run ` displays a single macOS window filled with a [Metal](https://developer.apple.com/metal/) framework [fragment shader](https://developer.apple.com/documentation/metal/using_a_render_pipeline_to_render_primitives#3682806). 10 | 2. You can edit and save the Rust project source code (in VS code or any other editor) to change the fragment shader output and the window will update in real time. 11 | 3. You write the shader in Rust but it is compiled to [Metal Shading Language](https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf) (a variation of C++) 12 | 4. In the shader source you can reference the `const` `INPUT` struct which provides inputs for each frame, similarly to _Input Uniforms_ in ShaderToy. You don't need to thread these values through your functions as arguments, despite Metal having no concept of global uniforms like WebGL does. 13 | 5. You can split the shader across multiple files using `mod ` and `use ::*`. 14 | 6. You can pause, restart and even run another shader file from the command line while the window is open. 15 | 16 | ## Instructions 17 | 18 | 1. clone this repo 19 | 2. run `cargo run raymarching_eyes` 20 | 3. edit `examples/raymarching_eyes.rs` 21 | 22 | Why Rust for the shader source? Better syntax, better editor integration and because it's a fun hack. It should feel exactly like writing Rust (which feels awesome!). Unlike in ShaderToy the Rust typechecker warns immediately about most errors one might make. 23 | 24 | ## Metal Shading Rust Language 25 | 26 | In general you will write Rust that closely resembles the C++ Metal Shading Language API, except for a few differences. 27 | 28 | The `INPUT` struct is documented in [shader_roy_metal_sl_interface](shader_roy_metal_sl_interface/src/shader_roy_metal_sl_interface.rs). 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 46 | 57 | 58 | 59 | 60 | 61 | 68 | 77 | 78 | 79 | 80 | 81 | 93 | 105 | 106 | 107 | 108 | 109 | 120 | 131 | 132 | 133 | 134 | 135 | 142 | 151 | 152 | 153 | 154 | 155 | 170 | 187 | 188 | 189 | 190 | 191 | 200 | 211 | 212 | 213 |
API MSL (C++) Rust
Scalar Types 38 | 39 | ```cpp 40 | float foo() { 41 | return 3.0; 42 | } 43 | ``` 44 | 45 | 47 | 48 | Use standard Rust types that correspond to the Metal types. 49 | 50 | ```rust 51 | fn foo() -> f32 { 52 | 3.0 53 | } 54 | ``` 55 | 56 |
Vector Types 62 | 63 | ```cpp 64 | void foo(float3 a, uint2 b) {} 65 | ``` 66 | 67 | 69 | 70 | Use the generic `Vec` type. Its default type argument is `f32`, for convenience. 71 | 72 | ```rust 73 | fn foo(a: Vec3, b: Vec2) {} 74 | ``` 75 | 76 |
Constructors 82 | 83 | vector constructors (`float4` etc.) can take arbitrary number of vector/scalar arguments, as long as they combine to the right length vector 84 | 85 | ```cpp 86 | auto x = float2(1.0); 87 | float4(x, x); 88 | 89 | uint2(0, 0); 90 | ``` 91 | 92 | 94 | 95 | In Rust, these follow the type names. You need to call these as methods. 96 | 97 | ```rust 98 | let x = 1.0.vec2(); 99 | (x, x).vec4(); 100 | 101 | vec2u32(0, 0); 102 | ``` 103 | 104 |
Access Constructors 110 | 111 | vector component selection constructors (`xxy` etc.) allows permutation and/or repetition of components: 112 | 113 | ```cpp 114 | auto pos = float2(1.0, 2.0); 115 | auto foo = pos.yyxy; 116 | // => float4(2.0, 2.0, 1.0, 2.0) 117 | ``` 118 | 119 | 121 | 122 | In Rust you need to call these as methods: 123 | 124 | ```rust 125 | let pos = (1.0, 2.0).vec2(); 126 | let foo = pos.yyxy(); 127 | // => (2.0, 2.0, 1.0, 2.0).vec4() 128 | ``` 129 | 130 |
Functions 136 | 137 | ```cpp 138 | min(x, y); 139 | ``` 140 | 141 | 143 | 144 | Math, geometric and common functions need to be called as methods 145 | 146 | ```rust 147 | x.min(y) 148 | ``` 149 | 150 |
Renamed functions 156 | 157 | ```cpp 158 | clamp(x, 0.3, 0.4); 159 | length(x); 160 | length_squared(x); 161 | normalize(x); 162 | faceforward(x, incident, surface); 163 | reflect(x, normal); 164 | refract(x, normal, eta); 165 | fmin(x, y); 166 | fmax(x, y); 167 | ``` 168 | 169 | 171 | 172 | Names follow [vek](https://docs.rs/vek/0.13.1/vek/vec/repr_c/vec3/struct.Vec3.html) 173 | 174 | ```rust 175 | x.clamped(0.3, 0.4); 176 | x.magnitude(); 177 | x.magnitude_squared(); 178 | x.normalized(); 179 | x.face_forward(incident, surface); 180 | x.reflected(normal); 181 | x.refracted(normal, eta); 182 | x.min(y); 183 | x.max(y); 184 | ``` 185 | 186 |
Methods with different argument order 192 | 193 | ```cpp 194 | mix(0.3, 0.4, a); 195 | smoothstep(0.3, 0.4, x); 196 | step(0.3, x); 197 | ``` 198 | 199 | 201 | 202 | When one argument is special from the others it is used as the receiver of the method call. 203 | 204 | ```rust 205 | a.mix(0.3, 0.4); 206 | x.smoothstep(0.3, 0.4); 207 | x.step(0.3); 208 | ``` 209 | 210 |
214 | 215 | ## Limitations 216 | 217 | ### Modules 218 | 219 | Right now only modules in the same directory as the main file will be watched. 220 | Only files in the same directory are supported for `mod `s, `/mod.rs` is not supported. 221 | There is no support for `path` attribute on `mod`s. 222 | 223 | ### Let bindings 224 | 225 | Variables cannot be redeclared. (_for now_) 226 | 227 | ### Constructors 228 | 229 | In Rust we could use a single generic `vec2` constructor for all `Vec2`s. But this would require actually using `rustc` to compile the constructors to the concrete Metal C++ constructors. To keep things simpler, the Rust bindings here require specifying the constructors directly (`vec2bool` -> `bool2`). 230 | 231 | ## Development 232 | 233 | Print the compiled shader without opening the window: 234 | 235 | ``` 236 | cargo test -- --nocapture 237 | ``` 238 | -------------------------------------------------------------------------------- /examples/mandelbrot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mandelbrot" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | path = "mandelbrot.rs" 8 | 9 | [dependencies] 10 | shader_roy_metal_sl_interface = { path="../../shader_roy_metal_sl_interface" } 11 | 12 | -------------------------------------------------------------------------------- /examples/mandelbrot/mandelbrot.rs: -------------------------------------------------------------------------------- 1 | use shader_roy_metal_sl_interface::*; 2 | 3 | pub fn pixel_color(coordinates: Vec2) -> Vec4 { 4 | let Vec2 { x: cx, y: cy } = screen_to_world(coordinates); 5 | let mut x: f32 = 0.0; 6 | let mut y = 0.0; 7 | let mut iteration = 0; 8 | let max_iteration = 1000; 9 | while (x * x + y * y) <= 4.0 && iteration < max_iteration { 10 | let xtemp = x * x - y * y + cx; 11 | y = 2.0 * x * y + cy; 12 | x = xtemp; 13 | iteration += 1; 14 | } 15 | 16 | (iteration as f32 / 80.0).vec4() 17 | } 18 | 19 | fn screen_to_world(screen: Vec2) -> Vec2 { 20 | let size = INPUT.window_size; 21 | let mut center = 2.0 * (INPUT.cursor_position / size - 0.5); 22 | center.x *= size.x / size.y; 23 | center.y *= -1.0; 24 | 25 | // let center = vec2(0.0, 1.0); 26 | // let time = 1.0; 27 | let time = INPUT.elapsed_time_secs.fmod(10.0) + 1.0; 28 | let zoom = 1.0 / time.pow(time / 2.0); 29 | let mut result = 2.0 * (screen / size - 0.5); 30 | result.x *= size.x / size.y; 31 | result.y *= -1.0; 32 | result -= center; 33 | result *= zoom; 34 | result += center; 35 | result 36 | } 37 | -------------------------------------------------------------------------------- /examples/raymarching_eyes/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "raymarching_eyes" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | path = "raymarching_eyes.rs" 8 | 9 | [dependencies] 10 | shader_roy_metal_sl_interface = { path="../../shader_roy_metal_sl_interface" } 11 | 12 | -------------------------------------------------------------------------------- /examples/raymarching_eyes/raymarching_eyes.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(clippy::float_cmp)] 3 | 4 | use sdf_utils::*; 5 | use shader_roy_metal_sl_interface::*; 6 | 7 | mod sdf_utils; 8 | 9 | pub fn pixel_color(coordinates: Vec2) -> Vec4 { 10 | let num_samples_per_axis = 3; 11 | let mut color = 0.0.vec4(); 12 | for y in 0..num_samples_per_axis { 13 | for x in 0..num_samples_per_axis { 14 | color += 15 | sample_color(coordinates + (x as f32, y as f32).vec2() / (num_samples_per_axis as f32)); 16 | } 17 | } 18 | color /= (num_samples_per_axis * num_samples_per_axis) as f32; 19 | color 20 | } 21 | 22 | pub fn sample_color(coordinates: Vec2) -> Vec4 { 23 | let cam_pos = (0.0, 0.0, -1.0).vec3(); 24 | let cam_target = (0.0, 0.0, 0.0).vec3(); 25 | 26 | let uv = screen_to_world(coordinates); 27 | let ray_dir = get_camera_ray_dir(uv, cam_pos, cam_target); 28 | 29 | let col = render(cam_pos, ray_dir, uv); 30 | let gamma_corrected = col.pow(0.4545); 31 | (gamma_corrected, 1.0).vec4() 32 | } 33 | 34 | fn scene(pos: Vec3) -> Vec2 { 35 | let copy = repeat(pos + (0.11, 0.0, 0.0).vec3(), 0.3, (1.0, 1.0, 0.0).vec3()); 36 | let grid = repeat(pos, 0.3, (1.0, 1.0, 0.0).vec3()); 37 | 38 | let eye_dir: Vec3; 39 | if INPUT.is_cursor_inside_window { 40 | eye_dir = ( 41 | screen_to_world(INPUT.cursor_position) / 2.0 - pos.xy(), 42 | -0.05, 43 | ) 44 | .vec3() 45 | .normalized(); 46 | } else { 47 | eye_dir = ( 48 | INPUT.elapsed_time_secs.sin(), 49 | INPUT.elapsed_time_secs.cos() + 0.5, 50 | -0.5, 51 | ) 52 | .vec3() 53 | .normalized(); 54 | } 55 | 56 | join( 57 | white(sd_sphere(grid, (0.0, 0.0, 0.0).vec3(), 0.05)), 58 | join( 59 | black(sd_sphere(grid, eye_dir / 80.0, 0.04)), 60 | join( 61 | white(sd_sphere(copy, (0.0, 0.0, 0.0).vec3(), 0.05)), 62 | black(sd_sphere(copy, eye_dir / 80.0, 0.04)), 63 | ), 64 | ), 65 | ) 66 | // red_plush(sd_sphere(q, vec3(0.0, 0.0, 0.0), 0.05)).min(sd_sphere( 67 | // grid, 68 | // vec3(0.0, 0.0, 0.0), 69 | // 0.01, 70 | // )) 71 | } 72 | 73 | fn infinite_repeat(pos: Vec3, period: f32) -> Vec3 { 74 | (pos.abs() + 0.5 * period).fmod(period) - 0.5 * period 75 | } 76 | 77 | fn repeat(pos: Vec3, period: f32, limit: Vec3) -> Vec3 { 78 | pos - period * (pos / period).round().clamped(-limit, limit) 79 | } 80 | 81 | fn sdf(pos: Vec3) -> f32 { 82 | scene(pos).x 83 | } 84 | 85 | fn render(ray_origin: Vec3, ray_dir: Vec3, uv: Vec2) -> Vec3 { 86 | let Vec2 { x: d, y: material } = cast_ray(ray_origin, ray_dir); 87 | if material <= 0.0 { 88 | // Skybox colour 89 | (0.30, 0.36, 0.60).vec3() - (ray_dir.y * 0.7) 90 | } else { 91 | let pos = ray_origin + ray_dir * d; 92 | let normal = calc_normal(pos); 93 | let light_dir: Vec3; 94 | if INPUT.is_cursor_inside_window { 95 | light_dir = (screen_to_world(INPUT.cursor_position) - uv, -0.5) 96 | .vec3() 97 | .normalized(); 98 | } else { 99 | light_dir = ( 100 | INPUT.elapsed_time_secs.sin(), 101 | INPUT.elapsed_time_secs.cos() + 0.5, 102 | -0.5, 103 | ) 104 | .vec3() 105 | .normalized(); 106 | } 107 | // L is vector from surface point to light, N is surface normal. N and L must be normalized! 108 | let brightness = normal.dot(light_dir).max(0.0); 109 | let light_color = (1.80, 1.27, 0.99).vec3() * brightness; 110 | let ambient = (0.03, 0.04, 0.1).vec3(); 111 | let diffuse = surface_color(material) * (light_color + ambient); 112 | let mut shadow = 0.0; 113 | let shadow_ray_origin = pos + normal * 0.01; 114 | let shadow_t = cast_ray(shadow_ray_origin, light_dir).x; 115 | if shadow_t >= -1.0 { 116 | shadow = 1.0; 117 | } 118 | shadow.mix(diffuse, diffuse * 0.8) 119 | // let N = calc_normal(pos); 120 | // N * 0.5.vec3() + 0.5.vec3() 121 | } 122 | } 123 | 124 | fn cast_ray(ray_origin: Vec3, ray_dir: Vec3) -> Vec2 { 125 | let mut t = 0.0; // Stores current distance along ray 126 | let z_clipping_distance = 6.0; 127 | 128 | for _ in 0..64 { 129 | let Vec2 { x: d, y: material } = scene(ray_origin + ray_dir * t); 130 | if d < (0.0001 * t) { 131 | return (t, material).vec2(); 132 | } 133 | t += d; 134 | if t > z_clipping_distance { 135 | return sky(); 136 | } 137 | } 138 | sky() 139 | } 140 | 141 | fn calc_normal(pos: Vec3) -> Vec3 { 142 | // Center sample 143 | let c = sdf(pos); 144 | // Use offset samples to compute gradient / normal 145 | let eps_zero = (0.001, 0.0).vec2(); 146 | (( 147 | sdf(pos + eps_zero.xyy()), 148 | sdf(pos + eps_zero.yxy()), 149 | sdf(pos + eps_zero.yyx()), 150 | ) 151 | .vec3() 152 | - c) 153 | .normalized() 154 | } 155 | 156 | fn get_camera_ray_dir(uv: Vec2, cam_pos: Vec3, cam_target: Vec3) -> Vec3 { 157 | // Calculate camera's "orthonormal basis", i.e. its transform matrix components 158 | let cam_forward = (cam_target - cam_pos).normalized(); 159 | let cam_right = ((0.0, 1.0, 0.0).vec3().cross(cam_forward)).normalized(); 160 | let cam_up = (cam_forward.cross(cam_right)).normalized(); 161 | 162 | let f_persp = 2.0; 163 | (uv.x * cam_right + uv.y * cam_up + cam_forward * f_persp).normalized() 164 | } 165 | 166 | fn sky() -> Vec2 { 167 | (-1.0, 0.0).vec2() 168 | } 169 | 170 | fn white(d: f32) -> Vec2 { 171 | (d, 1.0).vec2() 172 | } 173 | 174 | fn black(d: f32) -> Vec2 { 175 | (d, 2.0).vec2() 176 | } 177 | 178 | fn surface_color(material: f32) -> Vec3 { 179 | if material <= 1.0 { 180 | (0.9, 0.9, 0.9).vec3() 181 | } else { 182 | (0.1, 0.1, 0.1).vec3() 183 | } 184 | } 185 | 186 | fn join(a: Vec2, b: Vec2) -> Vec2 { 187 | if a.x < b.x { 188 | a 189 | } else { 190 | b 191 | } 192 | } 193 | 194 | // // --- Misc functions 195 | 196 | // // https://www.shadertoy.com/view/ll2GD3 197 | #[allow(clippy::many_single_char_names)] 198 | fn palette(mut t: f32, a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Vec3 { 199 | t = t.clamped(0., 1.); 200 | a + b * (6.28318 * (c * t + d)).cos() 201 | } 202 | 203 | fn screen_to_world(screen: Vec2) -> Vec2 { 204 | let size = INPUT.window_size; 205 | let mut result = 2.0 * (screen / size - 0.5); 206 | result.x *= size.x / size.y; 207 | result.y *= -1.0; 208 | result 209 | } 210 | 211 | fn shade(sd: f32) -> Vec3 { 212 | let max_dist: f32 = 2.0; 213 | let pal_col: Vec3 = palette( 214 | (0.5 - sd * 0.4).clamped(-max_dist, max_dist), 215 | (0.3, 0.3, 0.0).vec3(), 216 | (0.8, 0.8, 0.1).vec3(), 217 | (0.9, 0.7, 0.0).vec3(), 218 | (0.3, 0.9, 0.8).vec3(), 219 | ); 220 | 221 | let mut col: Vec3 = pal_col; 222 | // Darken around surface 223 | col = 0.4.mix(col, col * 1.0 - (-10.0 * sd.abs()).exp()); 224 | // repeating lines 225 | col *= 0.8 + 0.2 * (150.0 * sd).cos(); 226 | // White outline at surface 227 | col = (1.0 - sd.abs().smoothstep(0.0, 0.01)).mix(col, 1.0.vec3()); 228 | col 229 | } 230 | -------------------------------------------------------------------------------- /examples/raymarching_eyes/sdf_utils.rs: -------------------------------------------------------------------------------- 1 | use shader_roy_metal_sl_interface::*; 2 | 3 | // --- SDF utility library 4 | 5 | pub fn subtract(d1: f32, d2: f32) -> f32 { 6 | -d1.max(d2) 7 | } 8 | 9 | pub fn sd_sphere(p: Vec3, center: Vec3, radius: f32) -> f32 { 10 | p.distance(center) - radius 11 | } 12 | 13 | pub fn sd_box(p: Vec2, pos: Vec2, size: Vec2) -> f32 { 14 | let d: Vec2 = (p - pos).abs() - size; 15 | d.x.clamped(d.y, 0.0) + d.max(0.0).magnitude() 16 | } 17 | 18 | // polynomial smooth min (k = 0.1); 19 | pub fn smin_cubic(a: f32, b: f32, k: f32) -> f32 { 20 | let h: f32 = k - (a - b).abs().max(0.0); 21 | a.min(b) - h * h * h / (6.0 * k * k) 22 | } 23 | 24 | pub fn op_u(d1: f32, d2: f32) -> f32 { 25 | d1.min(d2) 26 | } 27 | 28 | pub fn op_blend(d1: f32, d2: f32) -> f32 { 29 | let k: f32 = 0.2; 30 | smin_cubic(d1, d2, k) 31 | } 32 | -------------------------------------------------------------------------------- /examples/simple/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | path = "simple.rs" 8 | 9 | [dependencies] 10 | shader_roy_metal_sl_interface = { path="../../shader_roy_metal_sl_interface" } 11 | 12 | -------------------------------------------------------------------------------- /examples/simple/simple.rs: -------------------------------------------------------------------------------- 1 | use shader_roy_metal_sl_interface::*; 2 | 3 | pub fn pixel_color(coordinates: Vec2) -> Vec4 { 4 | let mut uv = coordinates / INPUT.window_size; // 0 <-> 1 5 | uv -= 0.5; // -0.5 <-> 0.5 6 | uv.x *= INPUT.window_size.x / INPUT.window_size.y; // make uv uniform 7 | 8 | let d = uv.magnitude(); // distance to center 9 | let c = d.smoothstep(0.3, 0.29); // inverted to start in white 10 | (c.vec3(), 1.0).vec4() 11 | } 12 | -------------------------------------------------------------------------------- /examples/two_dimensional_sdf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "two_dimensional_sdf" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | path = "two_dimensional_sdf.rs" 8 | 9 | [dependencies] 10 | shader_roy_metal_sl_interface = { path="../../shader_roy_metal_sl_interface" } 11 | 12 | -------------------------------------------------------------------------------- /examples/two_dimensional_sdf/two_dimensional_sdf.rs: -------------------------------------------------------------------------------- 1 | // Ported from 2 | // https://github.com/electricsquare/raymarching-workshop#2d-sdf-demo 3 | 4 | use shader_roy_metal_sl_interface::*; 5 | 6 | pub fn pixel_color(coordinates: Vec2) -> Vec4 { 7 | // project screen coordinate into world 8 | let p = screen_to_world(coordinates, INPUT.window_size); 9 | // signed distance for scene 10 | let sd = sdf(p); 11 | // compute signed distance to a colour 12 | let col = shade(sd); 13 | (col, 1.0).vec4() 14 | } 15 | 16 | fn sdf(p: Vec2) -> f32 { 17 | // Example of the helpers 18 | op_blend( 19 | op_u( 20 | sd_circle(p, (-0.2, 0.3).vec2(), 0.2), 21 | sd_circle(p, (-0.5, 0.3).vec2(), 0.3), 22 | ), 23 | sd_box(p, (0.2, 0.3).vec2(), 0.3.vec2()), 24 | ) 25 | } 26 | 27 | // --- SDF utility library 28 | fn sd_circle(p: Vec2, pos: Vec2, radius: f32) -> f32 { 29 | p.distance(pos) - radius 30 | } 31 | 32 | fn sd_box(p: Vec2, pos: Vec2, size: Vec2) -> f32 { 33 | let d: Vec2 = (p - pos).abs() - size; 34 | d.x.clamped(d.y, 0.0) + d.max(0.0).magnitude() 35 | } 36 | 37 | // polynomial smooth min (k = 0.1); 38 | fn smin_cubic(a: f32, b: f32, k: f32) -> f32 { 39 | let h: f32 = k - (a - b).abs().max(0.0); 40 | a.min(b) - h * h * h / (6.0 * k * k) 41 | } 42 | 43 | fn op_u(d1: f32, d2: f32) -> f32 { 44 | d1.min(d2) 45 | } 46 | 47 | fn op_blend(d1: f32, d2: f32) -> f32 { 48 | let k: f32 = 0.2; 49 | smin_cubic(d1, d2, k) 50 | } 51 | 52 | // // --- Misc functions 53 | 54 | // // https://www.shadertoy.com/view/ll2GD3 55 | #[allow(clippy::many_single_char_names)] 56 | fn palette(mut t: f32, a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Vec3 { 57 | t = t.clamped(0., 1.); 58 | a + b * (6.28318 * (c * t + d)).cos() 59 | } 60 | 61 | fn screen_to_world(screen: Vec2, size: Vec2) -> Vec2 { 62 | let mut result: Vec2 = 2.0 * (screen / size - 0.5); 63 | result.x *= size.x / size.y; 64 | result 65 | } 66 | 67 | fn shade(sd: f32) -> Vec3 { 68 | let max_dist: f32 = 2.0; 69 | let pal_col: Vec3 = palette( 70 | (0.5 - sd * 0.4).clamped(-max_dist, max_dist), 71 | (0.3, 0.3, 0.0).vec3(), 72 | (0.8, 0.8, 0.1).vec3(), 73 | (0.9, 0.7, 0.0).vec3(), 74 | (0.3, 0.9, 0.8).vec3(), 75 | ); 76 | 77 | let mut col: Vec3 = pal_col; 78 | // Darken around surface 79 | col = 0.4.mix(col, col * 1.0 - (-10.0 * sd.abs()).exp()); 80 | // repeating lines 81 | col *= 0.8 + 0.2 * (150.0 * sd).cos(); 82 | // White outline at surface 83 | col = (1.0 - sd.abs().smoothstep(0.0, 0.01)).mix(col, 1.0.vec3()); 84 | col 85 | } 86 | -------------------------------------------------------------------------------- /metal_sl_prelude/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "metal_sl_prelude" 3 | version = "0.1.0" 4 | authors = ["Michal Srb "] 5 | edition = "2018" 6 | 7 | [lib] 8 | path = "src/metal_sl_prelude.rs" 9 | 10 | [dependencies] 11 | prelude_macros = { path="prelude_macros" } 12 | vek = "0.13.0" 13 | num = "0.3.1" 14 | half = "1.7.1" -------------------------------------------------------------------------------- /metal_sl_prelude/prelude_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prelude_macros" 3 | version = "0.1.0" 4 | authors = ["Michal Srb "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | prelude_proc_macros = { path="prelude_proc_macros" } 9 | quote = "1.0.8" -------------------------------------------------------------------------------- /metal_sl_prelude/prelude_macros/prelude_proc_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prelude_proc_macros" 3 | version = "0.1.0" 4 | authors = ["Michal Srb "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | quote = "1.0.8" 14 | syn = { version = "1.0.58", features=["full"] } 15 | voca_rs = "1.13.0" -------------------------------------------------------------------------------- /metal_sl_prelude/prelude_macros/prelude_proc_macros/src/access.rs: -------------------------------------------------------------------------------- 1 | pub fn implement(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 2 | let max_dimensions = 4; 3 | let vec_type = syn::parse_macro_input!(input as syn::Ident); 4 | let vec_type_name = vec_type.to_string(); 5 | let scalar_type_name = &vec_type_name[0..vec_type_name.len() - 1]; 6 | let scalar_fn_name = voca_rs::case::lower_first(scalar_type_name); 7 | let dimension = vec_type_name[vec_type_name.len() - 1..] 8 | .parse::() 9 | .unwrap(); 10 | let trait_name = quote::format_ident!("{}{}", "AccessFrom", dimension); 11 | 12 | let axes = "xyzw"; 13 | 14 | let trait_method_declarations: Vec = (dimension..=max_dimensions) 15 | .flat_map(|dim| { 16 | accessor_names(dim, &axes[0..dimension]) 17 | .into_iter() 18 | .map(move |name| { 19 | let ret_type = quote::format_ident!("{}{}", scalar_type_name, dim); 20 | let fn_name = quote::format_ident!("{}", name); 21 | syn::parse_quote!(fn #fn_name(self) -> #ret_type;) 22 | }) 23 | }) 24 | .collect(); 25 | 26 | let trait_method_definitions: Vec = (dimension..=max_dimensions) 27 | .flat_map(|dim| { 28 | accessor_names(dim, &axes[0..dimension]) 29 | .into_iter() 30 | .map(|name| { 31 | let ret_type = quote::format_ident!("{}{}", scalar_type_name, dim); 32 | let fn_name = quote::format_ident!("{}", name); 33 | let constuctor = quote::format_ident!("{}{}", scalar_fn_name, dim); 34 | let args: syn::punctuated::Punctuated<_, syn::Token![,]> = name 35 | .chars() 36 | .map(|ch| { 37 | let field = quote::format_ident!("{}", ch); 38 | let access: syn::Expr = syn::parse_quote!(self.#field); 39 | access 40 | }) 41 | .collect(); 42 | syn::parse_quote!(fn #fn_name(self) -> #ret_type {(#args).#constuctor()}) 43 | }) 44 | .collect::>() 45 | }) 46 | .collect(); 47 | 48 | let result = quote::quote!( 49 | pub trait #trait_name { 50 | #(#trait_method_declarations)* 51 | } 52 | 53 | impl #trait_name for #vec_type { 54 | #(#trait_method_definitions)* 55 | } 56 | ); 57 | // eprintln!("{}", result); 58 | proc_macro::TokenStream::from(result) 59 | } 60 | 61 | fn accessor_names(dims: usize, axes: &str) -> Vec { 62 | let mut result = vec!["".to_string()]; 63 | for _ in 0..dims { 64 | let mut next = vec![]; 65 | for ch in axes.chars() { 66 | for sofar in result.iter() { 67 | next.push(format!("{}{}", sofar, ch)); 68 | } 69 | } 70 | result = next; 71 | } 72 | result 73 | } 74 | -------------------------------------------------------------------------------- /metal_sl_prelude/prelude_macros/prelude_proc_macros/src/construct.rs: -------------------------------------------------------------------------------- 1 | pub fn define_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 2 | let (type_name, type_template) = type_name_and_template_input(input); 3 | let result_type_name = type_name_and_template_to_type(&type_name, &type_template); 4 | let trait_name = type_name_and_template_to_trait(&type_name, &type_template); 5 | let method_name = type_name_and_template_to_constructor_name(&type_name, &type_template); 6 | let ty: syn::Type = syn::parse_str(&result_type_name).unwrap(); 7 | let result = quote::quote!( 8 | pub trait #trait_name { 9 | fn #method_name(self) -> #ty; 10 | } 11 | ); 12 | // eprintln!("{}", result); 13 | proc_macro::TokenStream::from(result) 14 | } 15 | 16 | fn type_name_and_template_input(input: proc_macro::TokenStream) -> (String, String) { 17 | let mut it = input.into_iter(); 18 | let name = it.next().unwrap().to_string(); 19 | let template = it.next().unwrap().to_string(); 20 | (name, template) 21 | } 22 | 23 | struct ImplementTraitInput { 24 | type_name: String, 25 | result_type_template: String, 26 | arg_list: syn::punctuated::Punctuated, 27 | } 28 | 29 | // syn::FnArg is needlessly complicated to work with 30 | struct Arg { 31 | name: syn::Ident, 32 | ty: syn::Ident, 33 | } 34 | 35 | impl syn::parse::Parse for ImplementTraitInput { 36 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 37 | let type_name: syn::Ident = input.parse()?; 38 | let result_type_template: syn::Ident = input.parse()?; 39 | let content; 40 | syn::parenthesized!(content in input); 41 | let arg_list = content.parse_terminated(Arg::parse)?; 42 | Ok(ImplementTraitInput { 43 | type_name: type_name.to_string(), 44 | result_type_template: result_type_template.to_string(), 45 | arg_list, 46 | }) 47 | } 48 | } 49 | 50 | impl syn::parse::Parse for Arg { 51 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 52 | let name = input.parse()?; 53 | let _: syn::Token![:] = input.parse()?; 54 | let ty = input.parse()?; 55 | Ok(Arg { name, ty }) 56 | } 57 | } 58 | 59 | pub fn implement_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 60 | let ImplementTraitInput { 61 | type_name, 62 | result_type_template, 63 | arg_list, 64 | } = syn::parse_macro_input!(input); 65 | let num_args = arg_list.len(); 66 | let result_type_name = type_name_and_template_to_type(&type_name, &result_type_template); 67 | let trait_name = type_name_and_template_to_trait(&type_name, &result_type_template); 68 | let method_name = type_name_and_template_to_constructor_name(&type_name, &result_type_template); 69 | let result_arity = type_arity(&result_type_template); 70 | let arg_names: syn::punctuated::Punctuated<_, syn::Token![,]> = arg_list 71 | .iter() 72 | .map(|Arg { name, .. }| name.clone()) 73 | .collect(); 74 | let args_pattern = if num_args > 1 { 75 | quote::quote!((#arg_names)) 76 | } else { 77 | quote::quote!(#arg_names) 78 | }; 79 | let filled_arg_list: syn::punctuated::Punctuated<_, syn::Token![,]> = arg_list 80 | .iter() 81 | .map(|Arg { name, ty }| { 82 | let arg_type_template = ty.to_string(); 83 | let concrete_type_name = type_name_and_template_to_type(&type_name, &arg_type_template); 84 | let arity = type_arity(&arg_type_template); 85 | let concrete_type: syn::Type = syn::parse_str(&concrete_type_name).unwrap(); 86 | (name, concrete_type, arity) 87 | }) 88 | .collect(); 89 | let arg_types: syn::punctuated::Punctuated<_, syn::Token![,]> = filled_arg_list 90 | .iter() 91 | .map(|(_, ty, _)| ty.clone()) 92 | .collect(); 93 | let impl_type = if num_args > 1 { 94 | quote::quote!((#arg_types)) 95 | } else { 96 | quote::quote!(#arg_types) 97 | }; 98 | const FIELD_NAMES: [char; 4] = ['x', 'y', 'z', 'w']; 99 | let mut implementation_args = 100 | syn::punctuated::Punctuated::::new(); 101 | filled_arg_list.iter().for_each(|(name, _, arg_arity)| { 102 | let num_fields_from_this_arg = if num_args == 1 { 103 | result_arity 104 | } else { 105 | *arg_arity 106 | }; 107 | for arg_field_name_char in FIELD_NAMES.iter().take(num_fields_from_this_arg) { 108 | let arg_field_name = quote::format_ident!("{}", arg_field_name_char); 109 | let field_name = quote::format_ident!("{}", FIELD_NAMES[implementation_args.len()]); 110 | implementation_args.push(if *arg_arity == 1 { 111 | syn::parse_quote!(#field_name: #name) 112 | } else { 113 | syn::parse_quote!(#field_name: #name.#arg_field_name) 114 | }); 115 | } 116 | }); 117 | let ty: syn::Type = syn::parse_str(&result_type_name).unwrap(); 118 | let struct_name = quote::format_ident!("{}", result_type_template); 119 | let result = quote::quote!( 120 | impl #trait_name for #impl_type { 121 | fn #method_name(self) -> #ty { 122 | let #args_pattern = self; 123 | #struct_name {#implementation_args} 124 | } 125 | } 126 | ); 127 | // eprintln!("{}", result); 128 | proc_macro::TokenStream::from(result) 129 | } 130 | 131 | fn type_name_and_template_to_type(name: &str, template: &str) -> String { 132 | let arity_suffix = type_arity_suffix(&template); 133 | if arity_suffix == "" { 134 | name.to_owned() 135 | } else { 136 | format!( 137 | "Vec{}{}", 138 | arity_suffix, 139 | if name == "f32" { 140 | "".to_owned() 141 | } else { 142 | format!("<{}>", name) 143 | } 144 | ) 145 | } 146 | } 147 | 148 | fn type_name_and_template_to_trait(name: &str, template: &str) -> syn::Ident { 149 | quote::format_ident!("Construct{}{}", template, name) 150 | } 151 | 152 | fn type_name_and_template_to_constructor_name(name: &str, template: &str) -> syn::Ident { 153 | quote::format_ident!( 154 | "vec{}{}", 155 | type_arity_suffix(&template), 156 | if name == "f32" { "" } else { name } 157 | ) 158 | } 159 | 160 | fn type_arity_suffix(type_name: &str) -> String { 161 | let arity = type_arity(type_name); 162 | if arity == 1 { 163 | "".to_owned() 164 | } else { 165 | arity.to_string() 166 | } 167 | } 168 | 169 | fn type_arity(type_name: &str) -> usize { 170 | type_name 171 | .chars() 172 | .last() 173 | .and_then(|ch| char::to_digit(ch, 10)) 174 | .unwrap_or(1) as usize 175 | } 176 | -------------------------------------------------------------------------------- /metal_sl_prelude/prelude_macros/prelude_proc_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod access; 2 | mod construct; 3 | 4 | #[proc_macro] 5 | pub fn implement_accessors(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 6 | access::implement(input) 7 | } 8 | 9 | #[proc_macro] 10 | pub fn define_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 11 | construct::define_trait(input) 12 | } 13 | 14 | #[proc_macro] 15 | pub fn implement_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 16 | construct::implement_trait(input) 17 | } 18 | -------------------------------------------------------------------------------- /metal_sl_prelude/prelude_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub extern crate prelude_proc_macros; 2 | 3 | #[macro_export] 4 | macro_rules! implement_constructors { 5 | ([$($type_name:ident),*], $template:tt) => { 6 | $( 7 | $crate::implement_constructors_for_type! { 8 | $type_name, $template 9 | } 10 | )* 11 | }; 12 | } 13 | 14 | #[macro_export] 15 | macro_rules! implement_constructors_for_type { 16 | ($type_name:ident, {$($type:ident => $set:tt),*$(,)?}) => { 17 | $( 18 | $crate::prelude_proc_macros::define_trait! { $type_name $type } 19 | $crate::implement_constructor_trait! { 20 | $type_name: $type => $set 21 | } 22 | )* 23 | }; 24 | } 25 | 26 | #[macro_export] 27 | macro_rules! implement_constructor_trait { 28 | ($type_name:ident: $type:ident => {$($arg_list:tt),*$(,)?}) => { 29 | $( 30 | $crate::implement_constructor_trait! { 31 | $type_name: $type => $arg_list 32 | } 33 | )* 34 | }; 35 | ($type_name:ident: $type:ident => ($($arg_name:ident: $arg_type:ident),*$(,)?)) => { 36 | $crate::prelude_proc_macros::implement_trait! { 37 | $type_name $type ($($arg_name: $arg_type),*) 38 | } 39 | } 40 | } 41 | 42 | #[macro_export] 43 | macro_rules! implement_accessors { 44 | ($($type:ident),*$(,)?) => { 45 | $( 46 | $crate::prelude_proc_macros::implement_accessors! { $type } 47 | )* 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/access.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | prelude_macros::implement_accessors! { 4 | Vec2, 5 | Vec3, 6 | Vec4, 7 | // Uint2, 8 | // Uint3, 9 | // Uint4, 10 | } 11 | 12 | #[test] 13 | fn test_accessors() { 14 | let _ = 1.0.vec3().x; 15 | 1.0.vec3().xy(); 16 | 1.0.vec3().zyx(); 17 | } 18 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/common.rs: -------------------------------------------------------------------------------- 1 | //! Type definitions for ``. 2 | 3 | use super::*; 4 | 5 | pub trait Common: Sized { 6 | // Called clamped instead of clamp because std has claimed 7 | // the name. 8 | fn clamped(self, minval: U, maxval: V) -> R 9 | where 10 | Self: MinMax, 11 | R: MinMax, 12 | { 13 | self.max(minval).min(maxval) 14 | } 15 | 16 | fn mix(self, x: Tx, y: Ty) -> Tr 17 | where 18 | Tx: Copy, 19 | Ty: Op, 20 | Tr: Op, 21 | Tx: Op, 22 | { 23 | x + (y - x) * self 24 | } 25 | 26 | fn saturate(self) -> Self 27 | where 28 | Self: MinMax, 29 | { 30 | self.clamped(0.0, 1.0) 31 | } 32 | 33 | fn sign(self) -> Self 34 | where 35 | Self: Map, 36 | V: num::Signed, 37 | { 38 | self.map(num::signum) 39 | } 40 | 41 | fn smoothstep(self, edge0: Tx, edge1: Ty) -> Tr 42 | where 43 | Tx: Copy, 44 | Tr: Copy, 45 | Ty: Op, 46 | Self: Op, 47 | Tr: Op, 48 | Tr: MinMax, 49 | Tr: std::convert::From, 50 | { 51 | let t = ((self - edge0) / (edge1 - edge0)).clamped(0.0.into(), 1.0.into()); 52 | t * t * (Tr::from(3.0) - Tr::from(2.0) * t) 53 | } 54 | 55 | fn step(self, edge: Te) -> Tr 56 | where 57 | Self: Op, 58 | Tr: Map, 59 | V: PartialOrd, 60 | V: num::Signed, 61 | V: std::convert::From, 62 | { 63 | (self - edge) 64 | .sign() 65 | .map(|value| vek::partial_min(value, V::from(0.0))) 66 | } 67 | } 68 | 69 | impl Common for T {} 70 | 71 | #[test] 72 | fn test_clamp() { 73 | 1.0.clamped(0.2, 0.3); 74 | 1.0.vec3().clamped(0.2.vec3(), 0.3.vec3()); 75 | 1.0.vec3().clamped(0.2, 0.3.vec3()); 76 | } 77 | 78 | #[test] 79 | fn test_mix() { 80 | 1.0.mix(0.2, 0.3); 81 | 1.0.vec3().mix(0.2.vec3(), 0.3.vec3()); 82 | // Not supported: 1.0.vec3().mix(0.2, 0.3.vec3()); 83 | } 84 | 85 | #[test] 86 | fn test_saturate() { 87 | 1.0.saturate(); 88 | 1.0.vec3().saturate(); 89 | // sign 90 | 1.0.sign(); 91 | 1.0.vec3().sign(); 92 | } 93 | 94 | #[test] 95 | fn test_smoothstep() { 96 | 1.0f32.smoothstep(0.2, 0.3); // Rust defaults to f64 for which this is not implemented 97 | 1.0.vec3().smoothstep(0.2.vec3(), 0.3.vec3()); 98 | 1.0.vec3().smoothstep(0.2, 0.3.vec3()); 99 | } 100 | 101 | #[test] 102 | fn test_step() { 103 | 1.0f32.step(0.3); // Rust defaults to f64 for which this is not implemented 104 | 1.0.vec3().step(0.3.vec3()); 105 | 1.0.vec3().step(0.3); 106 | } 107 | 108 | use std::ops::*; 109 | 110 | pub trait Op: 111 | Sized + Add + Sub + Div + Mul 112 | { 113 | } 114 | 115 | impl Op for S where 116 | S: Sized + Add + Sub + Div + Mul 117 | { 118 | } 119 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/construct.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | prelude_macros::implement_constructors! { 4 | [i8, u8, i16, u16 , i32, u32, i64, u64, f16, f32], 5 | { 6 | Vec2 => { 7 | (all: Num), 8 | (x: Num, y: Num), 9 | (fr: Vec2), 10 | }, 11 | Vec3 => { 12 | (all: Num), 13 | (x: Num, y: Num, z: Num), 14 | (x: Num, b: Vec2), 15 | (a: Vec2, z: Num), 16 | (fr: Vec3), 17 | }, 18 | Vec4 => { 19 | (all: Num), 20 | (x: Num, y: Num, z: Num, w: Num), 21 | (a: Vec2, b: Vec2), 22 | (a: Vec2, z: Num, w: Num), 23 | (x: Num, y: Num, c: Vec2), 24 | (x: Num, b: Vec2, c: Num), 25 | (a: Vec3, w: Num), 26 | (x: Num, b: Vec3), 27 | (fr: Vec4), 28 | }, 29 | } 30 | } 31 | 32 | #[test] 33 | fn test_constructors() { 34 | 1.0.vec3(); 35 | (1.0, 2.0, 3.0).vec3(); 36 | (1.0.vec2(), 3.0).vec3(); 37 | 38 | 1.vec3i32(); 39 | (1, 2, 3).vec3i32(); 40 | (1.vec2i32(), 3).vec3i32(); 41 | } 42 | 43 | // TODO: All constructors 44 | pub fn mat2(val: f32) -> Mat2 { 45 | Mat2::broadcast_diagonal(val) 46 | } 47 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/generic.rs: -------------------------------------------------------------------------------- 1 | //! Internal module for implementing MSL utils 2 | //! 3 | //! It provides the Map and Map2 traits implemented for floating point scalars and vectors. 4 | //! When we want to implement only for vectors we use Vector. 5 | 6 | use super::*; 7 | 8 | pub trait Vector { 9 | fn map(self, f: F) -> Self 10 | where 11 | F: FnMut(T) -> T; 12 | 13 | fn map2(self, b: Self, f: F) -> Self 14 | where 15 | F: FnMut(T, T) -> T; 16 | } 17 | 18 | impl Vector for Vec2 { 19 | fn map(self, f: F) -> Self 20 | where 21 | F: FnMut(T) -> T, 22 | { 23 | self.map(f) 24 | } 25 | 26 | fn map2(self, b: Self, f: F) -> Self 27 | where 28 | F: FnMut(T, T) -> T, 29 | { 30 | self.map2(b, f) 31 | } 32 | } 33 | 34 | impl Vector for Vec3 { 35 | fn map(self, f: F) -> Self 36 | where 37 | F: FnMut(T) -> T, 38 | { 39 | self.map(f) 40 | } 41 | 42 | fn map2(self, b: Self, f: F) -> Self 43 | where 44 | F: FnMut(T, T) -> T, 45 | { 46 | self.map2(b, f) 47 | } 48 | } 49 | 50 | impl Vector for Vec4 { 51 | fn map(self, f: F) -> Self 52 | where 53 | F: FnMut(T) -> T, 54 | { 55 | self.map(f) 56 | } 57 | 58 | fn map2(self, b: Self, f: F) -> Self 59 | where 60 | F: FnMut(T, T) -> T, 61 | { 62 | self.map2(b, f) 63 | } 64 | } 65 | 66 | pub trait Map { 67 | fn map(self, f: F) -> Self 68 | where 69 | F: FnMut(T) -> T; 70 | } 71 | 72 | impl Map for f32 { 73 | fn map(self, mut f: F) -> Self 74 | where 75 | F: FnMut(f32) -> f32, 76 | { 77 | f(self) 78 | } 79 | } 80 | 81 | impl> Map for V { 82 | fn map(self, f: F) -> Self 83 | where 84 | F: FnMut(f32) -> f32, 85 | { 86 | self.map(f) 87 | } 88 | } 89 | 90 | pub trait Map2: Sized { 91 | fn map2(self, f: F) -> R 92 | where 93 | F: FnMut(T, T) -> T; 94 | } 95 | 96 | impl Map2 for (f32, f32) { 97 | fn map2(self, mut f: F) -> f32 98 | where 99 | F: FnMut(f32, f32) -> f32, 100 | { 101 | f(self.0, self.1) 102 | } 103 | } 104 | 105 | impl> Map2 for (T, T) { 106 | fn map2(self, f: F) -> T 107 | where 108 | F: FnMut(f32, f32) -> f32, 109 | { 110 | self.0.map2(self.1, f) 111 | } 112 | } 113 | 114 | impl> Map2 for (T, f32) { 115 | fn map2(self, mut f: F) -> T 116 | where 117 | F: FnMut(f32, f32) -> f32, 118 | { 119 | let (a, b) = self; 120 | a.map(|x| f(x, b)) 121 | } 122 | } 123 | 124 | impl> Map2 for (f32, T) { 125 | fn map2(self, mut f: F) -> T 126 | where 127 | F: FnMut(f32, f32) -> f32, 128 | { 129 | let (a, b) = self; 130 | b.map(|x| f(a, x)) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/geometric.rs: -------------------------------------------------------------------------------- 1 | //! Type definitions for ``. 2 | //! 3 | //! All methods are already declared in `vek` for any `Vec`: 4 | //! - `cross` 5 | //! - `distance` 6 | //! - `distance_squared` 7 | //! - `dot` 8 | //! - `face_forward` (`faceforward`) 9 | //! - `magnitude` (`length`) 10 | //! - `magnitude_squared` (`length_squared`) 11 | //! - `normalize` 12 | //! - `reflect` 13 | //! - `refract` 14 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/graphics.rs: -------------------------------------------------------------------------------- 1 | //! Type definitions for ``. 2 | 3 | // TODO: 4 | // T dfdx(T p) 5 | // T dfdy(T p) 6 | // T fwidth(T p) 7 | 8 | // uint get_num_samples() 9 | // float2 get_sample_position(uint index) 10 | 11 | // void discard_fragment(void) 12 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/integer.rs: -------------------------------------------------------------------------------- 1 | //! Type definitions for ``. 2 | 3 | // TODO (PRs welcome): 4 | // T abs(T x) 5 | // Tu absdiff(T x, T y) 6 | // T addsat(T x, T y) 7 | // T clamp(T x, T minval, T maxval) 8 | // T clz(T x) 9 | // T ctz(T x) 10 | // T extract_bits(T x, uint offset,uint bits) 11 | // T hadd(T x, T y) 12 | // T insert_bits(T base, T insert,uint offset, uint bits) 13 | // T32 mad24(T32 x, T32 y, T32 z) 14 | // T madhi(T a, T b, T c) 15 | // T madsat(T a, T b, T c) 16 | // T max(T x, T y) 17 | // T max3(T x, T y, T z) 18 | // Returns max(x, max(y, z) 19 | // T median3(T x, T y, T z) 20 | // T min(T x, T y) 21 | // T min3(T x, T y, T z) 22 | // Returns min(x, min(y, z) 23 | // T32 mul24(T32 x, T32 y) 24 | // T mulhi(T x, T y) 25 | // T popcount(T x) 26 | // T reverse_bits(T x) 27 | // T rhadd(T x, T y) 28 | // T rotate(T v, T i) 29 | // T subsat(T x, T y) 30 | 31 | #[test] 32 | fn test_abs() { 33 | 1i32.abs(); 34 | // TODO: 1.vec2i32().abs(); 35 | } 36 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/math.rs: -------------------------------------------------------------------------------- 1 | //! Type definitions for ``. 2 | 3 | use super::*; 4 | 5 | // TODO(PRs welcome): 6 | // T acos(T x) 7 | // T acosh(T x) 8 | // T asin(T x) 9 | // T asinh(T x) 10 | // T atan(T y_over_x) 11 | // T atan2(T y, T x) 12 | // T atanh(T x) 13 | // T ceil(T x) 14 | // T copysign(T x, T y) 15 | // T cosh(T x) 16 | // T cospi(T x) 17 | // T divide(T x, T y) 18 | // T exp(T x) 19 | // T exp2(T x) 20 | // T exp10(T x) 21 | // T fdim(T x, T y) 22 | // T floor(T x) 23 | // T fma(T a, T b, T c) 24 | // T fmax(T x, T y) 25 | // T max(T x, T y) 26 | // T fmax3(T x, T y, T z) 27 | // T max3(T x, T y, T z) 28 | // T fmedian3(T x, T y, T z) 29 | // T median3(T x, T y, T z) 30 | // T fmin(T x, T y) 31 | // T min(T x, T y) 32 | // T fmin3(T x, T y, T z) 33 | // T min3(T x, T y, T z) 34 | // T fmod(T x, T y) 35 | // T fract(T x) 36 | // T frexp(T x, Ti &exponent) 37 | // Ti ilogb(T x) 38 | // T ldexp(T x, Ti k) 39 | // T log(T x) 40 | // T log2(T x) 41 | // T log10(T x) 42 | // T modf(T x, T &intval) 43 | // T powr(T x, T y) 44 | // T rint(T x) 45 | // T round(T x) 46 | // T rsqrt(T x) 47 | // T sin(T x) 48 | // T sincos(T x, T &cosval) 49 | // T sinh(T x) 50 | // T sinpi(T x) 51 | // T sqrt(T x) 52 | // T tan(T x) 53 | // T tanh(T x) 54 | // T tanpi(T x) 55 | // T trunc(T x) 56 | 57 | pub const MAXFLOAT: f32 = 0.0; 58 | pub const HUGE_VALF: f32 = 0.0; 59 | pub const INFINITY: f32 = 0.0; 60 | pub const NAN: f32 = 0.0; 61 | pub const M_E_F: f32 = 0.0; 62 | pub const M_LOG2E_F: f32 = 0.0; 63 | pub const M_LOG10E_F: f32 = 0.0; 64 | pub const M_LN2_F: f32 = 0.0; 65 | pub const M_LN10_F: f32 = 0.0; 66 | pub const M_PI_F: f32 = 0.0; 67 | pub const M_PI_2_F: f32 = 0.0; 68 | pub const M_PI_4_F: f32 = 0.0; 69 | pub const M_1_PI_F: f32 = 0.0; 70 | pub const M_2_PI_F: f32 = 0.0; 71 | pub const M_2_SQRTPI_F: f32 = 0.0; 72 | pub const M_SQRT2_F: f32 = 0.0; 73 | pub const M_SQRT1_2_F: f32 = 0.0; 74 | 75 | pub const MAXHALF: f16 = f16::from_bits(0); 76 | pub const HUGE_VALH: f16 = f16::from_bits(0); 77 | pub const M_E_H: f16 = f16::from_bits(0); 78 | pub const M_LOG2E_H: f16 = f16::from_bits(0); 79 | pub const M_LOG10E_H: f16 = f16::from_bits(0); 80 | pub const M_LN2_H: f16 = f16::from_bits(0); 81 | pub const M_LN10_H: f16 = f16::from_bits(0); 82 | pub const M_PI_H: f16 = f16::from_bits(0); 83 | pub const M_PI_2_H: f16 = f16::from_bits(0); 84 | pub const M_PI_4_H: f16 = f16::from_bits(0); 85 | pub const M_1_PI_H: f16 = f16::from_bits(0); 86 | pub const M_2_PI_H: f16 = f16::from_bits(0); 87 | pub const M_2_SQRTPI_H: f16 = f16::from_bits(0); 88 | pub const M_SQRT2_H: f16 = f16::from_bits(0); 89 | pub const M_SQRT1_2_H: f16 = f16::from_bits(0); 90 | 91 | pub trait ComponentWiseMath { 92 | fn abs(self) -> Self; 93 | fn cos(self) -> Self; 94 | } 95 | 96 | impl> ComponentWiseMath for T { 97 | fn abs(self) -> Self { 98 | self.map(|x| x.abs()) 99 | } 100 | 101 | fn cos(self) -> Self { 102 | self.map(|x| x.cos()) 103 | } 104 | } 105 | 106 | pub trait MinMax: Sized { 107 | fn min(self, b: TOther) -> TResult; 108 | fn max(self, b: TOther) -> TResult; 109 | } 110 | 111 | impl MinMax for TSelf 112 | where 113 | (TSelf, TOther): Map2, 114 | { 115 | fn min(self, b: TOther) -> TResult { 116 | (self, b).map2(vek::ops::partial_min) 117 | } 118 | fn max(self, b: TOther) -> TResult { 119 | (self, b).map2(vek::ops::partial_max) 120 | } 121 | } 122 | 123 | pub trait Math: Sized { 124 | fn pow(self, b: V) -> R; 125 | fn fmod(self, b: V) -> R; 126 | } 127 | 128 | impl Math for T 129 | where 130 | (T, V): Map2, 131 | { 132 | fn pow(self, b: V) -> R { 133 | (self, b).map2(|a, b| a.powf(b)) 134 | } 135 | fn fmod(self, b: V) -> R { 136 | (self, b).map2(|a, b| a % b) 137 | } 138 | } 139 | 140 | #[test] 141 | fn test_abs() { 142 | let _ = 1.0f32.abs(); 143 | 1.0.vec2().abs(); 144 | } 145 | 146 | #[test] 147 | fn test_cos() { 148 | let _ = 1.0f32.cos(); 149 | 1.0.vec2().cos(); 150 | } 151 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/matrix.rs: -------------------------------------------------------------------------------- 1 | //! Type definitions for ``. 2 | 3 | // TODO(PRs welcome): 4 | // float determinant(floatnxn) 5 | // half determinant(halfnxn) 6 | // floatmxn transpose(floatnxm) 7 | // halfmxn transpose(halfnxm) 8 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/metal_sl_prelude.rs: -------------------------------------------------------------------------------- 1 | //! Provides type definitions for the Metal Standard Library, as if using ``. 2 | 3 | mod access; 4 | mod common; 5 | mod construct; 6 | mod generic; 7 | mod geometric; 8 | mod graphics; 9 | mod integer; 10 | mod math; 11 | mod matrix; 12 | mod relational; 13 | mod types; 14 | 15 | pub use access::*; 16 | pub use common::*; 17 | pub use construct::*; 18 | pub use generic::*; 19 | pub use geometric::*; 20 | pub use graphics::*; 21 | pub use integer::*; 22 | pub use math::*; 23 | pub use matrix::*; 24 | pub use relational::*; 25 | pub use types::*; 26 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/relational.rs: -------------------------------------------------------------------------------- 1 | //! Type definitions for ``. 2 | 3 | // TODO(PRs welcome): 4 | // bool all(Tb x) 5 | // bool any(Tb x) 6 | // Tb isfinite(T x) 7 | // Tb isinf(T x) 8 | // Tb isnan(T x) 9 | // Tb isnormal(T x) 10 | // Tb isordered(T x, T y) 11 | // Tb isunordered(T x, T y) 12 | // Tb not(Tb x) 13 | // T select(T a, T b, Tb c) 14 | // Ti select(Ti a, Ti b, Tb c) 15 | // Tb signbit(T x) 16 | 17 | #[test] 18 | fn test_not() { 19 | // TODO: true.not(); 20 | // TODO: bool.vec2bool().not(); 21 | } 22 | -------------------------------------------------------------------------------- /metal_sl_prelude/src/types.rs: -------------------------------------------------------------------------------- 1 | //! Vector types defaulted to f32 (C++ float). 2 | 3 | // Needs to be added because Rust stdlib doesn't include it 4 | pub use half::f16; 5 | 6 | pub type Vec2 = vek::Vec2; 7 | pub type Vec3 = vek::Vec3; 8 | pub type Vec4 = vek::Vec4; 9 | 10 | pub type Mat2 = vek::Mat2; 11 | pub type Mat3 = vek::Mat3; 12 | pub type Mat4 = vek::Mat4; 13 | // TODO: All matrix types. Needs custom definitions, since vek doesn't have them 14 | // pub type Mat2x3; 15 | // pub type Mat2x4; 16 | // pub type Mat3x2; 17 | // pub type Mat3x4; 18 | // pub type Mat4x2; 19 | // pub type Mat4x3; 20 | -------------------------------------------------------------------------------- /regex_try/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "regex_try" 3 | version = "0.1.0" 4 | authors = ["Michal Srb "] 5 | license = "MIT OR Apache-2.0" 6 | description = """ 7 | Replace methods for Regex propogating errors via Result. 8 | """ 9 | documentation = "https://docs.rs/regex_try" 10 | keywords=["regex", "regular-expressions", "result"] 11 | edition = "2018" 12 | 13 | 14 | [dependencies] 15 | regex = "1.4.3" 16 | -------------------------------------------------------------------------------- /regex_try/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | 3 | //! An extension of [`Regex`] supporting [`Result`] in `replace` methods. 4 | //! 5 | //! The [`replace`], [`replace_all`] and [`replacen`] methods of [`Regex`] accept a function 6 | //! returning the replacement for each matched substring, but they do not allow this 7 | //! function to return a [`Result`]. This crate provides [`try_replace`], [`try_replace_all`], 8 | //! and [`try_replacen`] which fill this gap. 9 | //! 10 | //! # Use 11 | //! Include `use regex_try::RegexTry` to use the additional methods provided by this crate. 12 | //! 13 | //! ## Example 14 | //! 15 | //! ```edition2018 16 | //! use regex::Regex; 17 | //! use regex_try::RegexTry; 18 | //! 19 | //! pub fn main() -> Result<(), std::io::Error> { 20 | //! let re = Regex::new(r"load! (\w+);").unwrap(); 21 | //! let template = std::fs::read_to_string("Cargo.toml")?; 22 | //! let result = re.try_replace_all(&template, |captures| 23 | //! // read_to_string returns a Result, and so it couldn't 24 | //! // be used with re.replace_all 25 | //! std::fs::read_to_string(&captures[1]) 26 | //! )?; 27 | //! println!("{}", result); 28 | //! Ok(()) 29 | //! } 30 | //! ``` 31 | //! 32 | //! [`Result`]: https://doc.rust-lang.org/std/result/ 33 | //! [`Regex`]: https://docs.rs/regex/*/regex/struct.Regex.html 34 | //! [`replace`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace 35 | //! [`replace_all`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace_all 36 | //! [`replacen`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replacen 37 | //! [`try_replace`]: ./trait.RegexTry.html#tymethod.replace 38 | //! [`try_replace_all`]: ./trait.RegexTry.html#tymethod.replace_all 39 | //! [`try_replacen`]: ./trait.RegexTry.html#tymethod.replacen 40 | 41 | use regex::Captures; 42 | use regex::Regex; 43 | use std::borrow::Cow; 44 | 45 | /// Defines the additional methods for Regex. 46 | /// 47 | /// The replacer is always a function of type `FnMut(&Captures) -> Result`. 48 | pub trait RegexTry { 49 | /// See [`Regex::replacen`] 50 | /// 51 | /// [`Regex::replacen`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replacen 52 | fn try_replacen<'t>(&self, text: &'t str, limit: usize, rep: F) -> Result, E>; 53 | 54 | /// See [`Regex::replace`] 55 | /// 56 | /// [`Regex::replace`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace 57 | fn try_replace<'t>(&self, text: &'t str, rep: F) -> Result, E>; 58 | 59 | /// See [`Regex::replace_all`] 60 | /// 61 | /// [`Regex::replace_all`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace_all 62 | fn try_replace_all<'t>(&self, text: &'t str, rep: F) -> Result, E>; 63 | } 64 | 65 | impl RegexTry for Regex 66 | where 67 | F: FnMut(&Captures) -> Result, 68 | { 69 | fn try_replacen<'t>(&self, text: &'t str, limit: usize, mut rep: F) -> Result, E> { 70 | let mut it = self.captures_iter(text).enumerate().peekable(); 71 | if it.peek().is_none() { 72 | return Ok(Cow::Borrowed(text)); 73 | } 74 | let mut new = String::with_capacity(text.len()); 75 | let mut last_match = 0; 76 | for (i, cap) in it { 77 | if limit > 0 && i >= limit { 78 | break; 79 | } 80 | // unwrap on 0 is OK because captures only reports matches 81 | let m = cap.get(0).unwrap(); 82 | new.push_str(&text[last_match..m.start()]); 83 | let replacement = rep(&cap)?; 84 | new.push_str(&replacement); 85 | last_match = m.end(); 86 | } 87 | new.push_str(&text[last_match..]); 88 | Ok(Cow::Owned(new)) 89 | } 90 | 91 | fn try_replace<'t>(&self, text: &'t str, rep: F) -> Result, E> { 92 | self.try_replacen(text, 1, rep) 93 | } 94 | 95 | fn try_replace_all<'t>(&self, text: &'t str, rep: F) -> Result, E> { 96 | self.try_replacen(text, 0, rep) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /rust_to_metal_sl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_to_metal_sl" 3 | version = "0.1.0" 4 | authors = ["Michal Srb "] 5 | edition = "2018" 6 | 7 | [lib] 8 | path = "src/rust_to_metal_sl.rs" 9 | 10 | [dependencies] 11 | syn = { version = "1.0.58", features = ["full", "visit-mut"]} 12 | quote = "1.0.8" 13 | anyhow = "1.0.38" 14 | proc-macro2 = "1.0.24" 15 | lazy_static = "1.4.0" 16 | regex = "1.4.3" 17 | voca_rs = "1.13.0" 18 | maplit = "1.0.2" 19 | -------------------------------------------------------------------------------- /rust_to_metal_sl/src/adapter.rs: -------------------------------------------------------------------------------- 1 | pub fn make_rust_ast_msl_compatible(mut rust_ast: syn::File) -> syn::File { 2 | use syn::visit_mut::VisitMut; 3 | AstAdapter.visit_file_mut(&mut rust_ast); 4 | rust_ast 5 | } 6 | 7 | const DEFAULT_GENERIC_TYPE_ARG: &str = "f32"; 8 | const GENERIC_TYPE_PREFIX: &str = "Vec"; 9 | const GENERIC_METHOD_PREFIX: &str = "vec"; 10 | 11 | lazy_static::lazy_static! { 12 | static ref RUST_TO_METAL_TYPES: std::collections::HashMap<&'static str, &'static str> = 13 | maplit::hashmap![ 14 | "i8" => "char", 15 | "u8" => "uchar", 16 | "i16" => "short", 17 | "u16" => "ushort", 18 | "i32" => "int", 19 | "u32" => "uint", 20 | "i32" => "long", 21 | "i32" => "ulong", 22 | "f16" => "half", 23 | "f32" => "float", 24 | ]; 25 | static ref ACCESS_METHODS: regex::Regex = regex::Regex::new(r"^[xywz]{1,4}$").unwrap(); 26 | static ref RENAMED_METHODS: std::collections::HashMap<&'static str, &'static str> = 27 | maplit::hashmap![ 28 | "clamped" => "clamp", 29 | "magnitude" => "length", 30 | "magnitude_squared" => "length_squared", 31 | "face_forward" => "faceforward", 32 | "normalized" => "normalize", 33 | "reflected" => "reflect", 34 | "refracted" => "refract", 35 | "min" => "fmin", 36 | "max" => "fmax", 37 | ]; 38 | static ref METHODS_WITH_RECEIVER_LAST: regex::RegexSet = regex::RegexSet::new( 39 | ["mix", "smoothstep", "step"] 40 | .iter() 41 | .map(|name| format!(r"^{}$", name)), 42 | ) 43 | .unwrap(); 44 | } 45 | 46 | struct AstAdapter; 47 | 48 | impl syn::visit_mut::VisitMut for AstAdapter { 49 | fn visit_expr_mut(&mut self, node: &mut syn::Expr) { 50 | if let syn::Expr::MethodCall(expr) = node { 51 | let syn::ExprMethodCall { 52 | receiver, 53 | args, 54 | method, 55 | .. 56 | } = expr; 57 | 58 | if let Some(new_method_name) = RENAMED_METHODS.get::(&method.to_string()) { 59 | *method = quote::format_ident!("{}", new_method_name); 60 | } 61 | 62 | let method_name = method.to_string(); 63 | 64 | *node = if ACCESS_METHODS.is_match(&method_name) { 65 | syn::parse_quote!( 66 | #receiver.#method 67 | ) 68 | } else if let Some(arity_and_type_suffix) = method_name.strip_prefix(GENERIC_METHOD_PREFIX) { 69 | let type_name = &arity_and_type_suffix[1..]; 70 | let new_type_name = RUST_TO_METAL_TYPES 71 | .get::(if type_name == "" { 72 | DEFAULT_GENERIC_TYPE_ARG 73 | } else { 74 | type_name 75 | }) 76 | .unwrap(); 77 | let new_method = quote::format_ident!("{}{}", new_type_name, arity_and_type_suffix[0..1]); 78 | if let syn::Expr::Tuple(syn::ExprTuple { 79 | elems: unwrapped_args, 80 | .. 81 | }) = &**receiver 82 | { 83 | syn::parse_quote!( 84 | #new_method(#unwrapped_args) 85 | ) 86 | } else { 87 | syn::parse_quote!( 88 | #new_method(#receiver) 89 | ) 90 | } 91 | } else if METHODS_WITH_RECEIVER_LAST.is_match(&method_name) { 92 | syn::parse_quote!( 93 | #method(#args, #receiver) 94 | ) 95 | } else { 96 | let new_args = std::iter::once(&**receiver) 97 | .chain(args.iter()) 98 | .collect::>(); 99 | syn::parse_quote!( 100 | #method(#new_args) 101 | ) 102 | } 103 | } 104 | 105 | // For now we will erase type casts, as properly compiling them 106 | // would require a full expression printer 107 | if let syn::Expr::Cast(syn::ExprCast { expr, .. }) = node { 108 | *node = syn::parse_quote!(#expr); 109 | } 110 | 111 | // Delegate to the default impl to visit nested expressions. 112 | syn::visit_mut::visit_expr_mut(self, node); 113 | } 114 | 115 | // Convert scalar/vector types 116 | // `scalar` or ```Vec`d`<`scalar`>``` 117 | fn visit_path_mut(&mut self, node: &mut syn::Path) { 118 | let (type_name, type_args) = { 119 | let mut path_segments_iter = node.segments.iter(); 120 | let ty = path_segments_iter.next().unwrap(); 121 | let arg_list = if let syn::PathArguments::AngleBracketed(args) = &ty.arguments { 122 | Some(quote::quote!(#args).to_string()) 123 | } else { 124 | None 125 | }; 126 | (ty.ident.to_string(), arg_list) 127 | }; 128 | if let Some(new_type_name) = RUST_TO_METAL_TYPES.get::(&type_name) { 129 | let new_type = quote::format_ident!("{}", new_type_name); 130 | *node = syn::parse_quote!(#new_type); 131 | } 132 | if let Some(arity_suffix) = type_name.strip_prefix(GENERIC_TYPE_PREFIX) { 133 | if let Some(new_type_name) = 134 | RUST_TO_METAL_TYPES.get::(&type_args.as_deref().unwrap_or(DEFAULT_GENERIC_TYPE_ARG)) 135 | { 136 | let new_type = quote::format_ident!("{}{}", new_type_name, arity_suffix); 137 | *node = syn::parse_quote!(#new_type); 138 | } 139 | } 140 | } 141 | 142 | // Removes trailing commas, since C++ doesn't allow them 143 | fn visit_expr_call_mut(&mut self, node: &mut syn::ExprCall) { 144 | let syn::ExprCall { args, .. } = node; 145 | if args.trailing_punct() { 146 | let last = args.pop().unwrap().into_value(); 147 | args.push(last); 148 | } 149 | syn::visit_mut::visit_expr_call_mut(self, node); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /rust_to_metal_sl/src/enhancer.rs: -------------------------------------------------------------------------------- 1 | // TODO: 2 | // Walk the call graph 3 | // - first preprocess the AST into a Map from name to definition 4 | // fns = Map 5 | // - another mutable Map stores the result - whether a function needs the input param or not 6 | // is_using_input = mut Map 7 | // - then start from pixel_color, find all function calls within it, 8 | // then visit them all from the fns Map 9 | // - if a function was visited already, it would have written the result, so we can early return 10 | // - if a function uses input, set is_using_input Map to true (and still visit all calls) 11 | // - if none of the function calls uses, set is_using_input to False 12 | // - finally make another pass over the AST, mutating every fn that needs input by adding it as argument 13 | // - and every call to such a function by adding the input as last argument 14 | // - for simplicity can use very unusual name to avoid name clashing 15 | 16 | use std::collections::{HashMap, HashSet}; 17 | 18 | pub struct EnhanceConfig { 19 | pub entry_point_fn_name: String, 20 | pub constant_name: String, 21 | pub param_type: String, 22 | } 23 | 24 | pub fn convert_constant_to_param(rust_ast: syn::File, properties: &EnhanceConfig) -> syn::File { 25 | let fn_items = FnItemsCollector::name_to_fn_items(&rust_ast); 26 | let is_using = { 27 | let mut is_using = HashMap::::new(); 28 | check_is_using( 29 | &properties.entry_point_fn_name, 30 | &fn_items, 31 | &mut is_using, 32 | &mut HashSet::new(), 33 | &properties, 34 | ); 35 | // Entry point always needs to include the parameter to maintain API, regardless of its use 36 | is_using.insert(properties.entry_point_fn_name.to_owned(), true); 37 | is_using 38 | }; 39 | FnItemsEnhancer::add_params(rust_ast, is_using, properties) 40 | } 41 | 42 | struct FnItemsCollector { 43 | fn_items: HashMap, 44 | } 45 | 46 | impl FnItemsCollector { 47 | fn name_to_fn_items(rust_ast: &syn::File) -> HashMap { 48 | use syn::visit::Visit; 49 | let mut collector = Self { 50 | fn_items: HashMap::new(), 51 | }; 52 | collector.visit_file(rust_ast); 53 | collector.fn_items 54 | } 55 | } 56 | 57 | impl syn::visit::Visit<'_> for FnItemsCollector { 58 | fn visit_item_fn(&mut self, node: &syn::ItemFn) { 59 | self 60 | .fn_items 61 | .insert(node.sig.ident.to_string(), node.clone()); 62 | } 63 | } 64 | 65 | fn check_is_using( 66 | fn_name: &str, 67 | fn_items: &HashMap, 68 | is_using: &mut HashMap, 69 | is_being_processed: &mut HashSet, 70 | properties: &EnhanceConfig, 71 | ) { 72 | if is_using.get(fn_name).is_some() { 73 | return; 74 | } 75 | if is_being_processed.contains(fn_name) { 76 | // Prevents infinite loops for mutually recursive functions 77 | return; 78 | } 79 | is_being_processed.insert(fn_name.to_owned()); 80 | if let Some(item_fn) = fn_items.get(fn_name) { 81 | // It's important to not short-circuit in any of this code 82 | #[allow(clippy::unnecessary_fold)] 83 | let is_any_called_fn_using = CalledFnsCollector::called_fns(item_fn) 84 | .iter() 85 | .map(|called_fn| { 86 | check_is_using( 87 | called_fn, 88 | fn_items, 89 | is_using, 90 | is_being_processed, 91 | properties, 92 | ); 93 | *is_using.get(called_fn).unwrap_or(&false) 94 | }) 95 | .fold(false, |so_far, is_called_using| so_far || is_called_using); 96 | let is_using_directly = 97 | ConstantDetector::contains_contant_access(item_fn, &properties.constant_name); 98 | is_using.insert( 99 | fn_name.to_owned(), 100 | is_any_called_fn_using || is_using_directly, 101 | ); 102 | } 103 | } 104 | 105 | struct ConstantDetector { 106 | constant_name: String, 107 | result: bool, 108 | } 109 | 110 | impl ConstantDetector { 111 | fn contains_contant_access(item_fn: &syn::ItemFn, constant_name: &str) -> bool { 112 | let mut detector = Self { 113 | constant_name: constant_name.to_owned(), 114 | result: false, 115 | }; 116 | use syn::visit::Visit; 117 | detector.visit_item_fn(item_fn); 118 | detector.result 119 | } 120 | } 121 | 122 | impl syn::visit::Visit<'_> for ConstantDetector { 123 | fn visit_expr_field(&mut self, field_access: &syn::ExprField) { 124 | if let syn::Expr::Path(syn::ExprPath { path, .. }) = &*field_access.base { 125 | if cp(path) == self.constant_name { 126 | self.result = true; 127 | return; 128 | } 129 | } 130 | syn::visit::visit_expr_field(self, field_access); 131 | } 132 | } 133 | 134 | struct CalledFnsCollector { 135 | called_fns: Vec, 136 | } 137 | 138 | impl CalledFnsCollector { 139 | fn called_fns(item_fn: &syn::ItemFn) -> Vec { 140 | use syn::visit::Visit; 141 | let mut collector = Self { called_fns: vec![] }; 142 | collector.visit_item_fn(item_fn); 143 | collector.called_fns 144 | } 145 | } 146 | 147 | impl syn::visit::Visit<'_> for CalledFnsCollector { 148 | fn visit_expr_call(&mut self, call: &syn::ExprCall) { 149 | if let syn::Expr::Path(syn::ExprPath { path, .. }) = &*call.func { 150 | self.called_fns.push(cp(path)) 151 | } 152 | } 153 | } 154 | 155 | struct FnItemsEnhancer<'a> { 156 | is_using: HashMap, 157 | properties: &'a EnhanceConfig, 158 | } 159 | 160 | impl<'a> FnItemsEnhancer<'a> { 161 | fn add_params( 162 | mut rust_ast: syn::File, 163 | is_using: HashMap, 164 | properties: &'a EnhanceConfig, 165 | ) -> syn::File { 166 | use syn::visit_mut::VisitMut; 167 | let mut enhancer = Self { 168 | is_using, 169 | properties, 170 | }; 171 | enhancer.visit_file_mut(&mut rust_ast); 172 | rust_ast 173 | } 174 | 175 | fn is_using(&self, fn_name: &str) -> bool { 176 | *self.is_using.get(fn_name).unwrap_or(&false) 177 | } 178 | } 179 | 180 | impl syn::visit_mut::VisitMut for FnItemsEnhancer<'_> { 181 | fn visit_item_fn_mut(&mut self, item_fn: &mut syn::ItemFn) { 182 | let fn_name = item_fn.sig.ident.to_string(); 183 | if self.is_using(&fn_name) { 184 | let param_type = quote::format_ident!("{}", self.properties.param_type); 185 | let constant_name = quote::format_ident!("{}", self.properties.constant_name); 186 | item_fn.sig.inputs.push(syn::parse_quote!( 187 | #[address_space(constant)] #constant_name: &#param_type 188 | )); 189 | } 190 | syn::visit_mut::visit_item_fn_mut(self, item_fn); 191 | } 192 | 193 | fn visit_expr_call_mut(&mut self, call: &mut syn::ExprCall) { 194 | if let syn::Expr::Path(syn::ExprPath { path, .. }) = &*call.func { 195 | if self.is_using(&cp(path)) { 196 | let constant_name = quote::format_ident!("{}", self.properties.constant_name); 197 | call.args.push(syn::parse_quote!(#constant_name)); 198 | } 199 | } 200 | syn::visit_mut::visit_expr_call_mut(self, call); 201 | } 202 | } 203 | 204 | fn cp(x: &T) -> String 205 | where 206 | T: quote::ToTokens, 207 | { 208 | quote::quote!(#x).to_string() 209 | } 210 | -------------------------------------------------------------------------------- /rust_to_metal_sl/src/parser.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | pub fn parse_rust_into_ast(rust_source: &str) -> Result { 4 | syn::parse_str(rust_source).with_context(|| "Parse error") 5 | } 6 | -------------------------------------------------------------------------------- /rust_to_metal_sl/src/printer.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | pub fn print_ast_into_msl(file: syn::File) -> Result { 4 | Ok(format!( 5 | "{}{}", 6 | AstPrinter::print(&file, PrinterMode::Declarations)?, 7 | AstPrinter::print(&file, PrinterMode::Definitions)? 8 | )) 9 | } 10 | 11 | enum PrinterMode { 12 | Declarations, 13 | Definitions, 14 | } 15 | 16 | struct AstPrinter { 17 | error: Option, 18 | output: String, 19 | mode: PrinterMode, 20 | context: Context, 21 | indent: String, 22 | unique_id: u64, 23 | } 24 | 25 | impl AstPrinter { 26 | fn print(file: &syn::File, mode: PrinterMode) -> Result { 27 | let mut printer = AstPrinter { 28 | error: None, 29 | output: String::new(), 30 | mode, 31 | context: Context::TopLevel, 32 | indent: String::new(), 33 | unique_id: 0, 34 | }; 35 | use syn::visit::Visit; 36 | printer.visit_file(file); 37 | if let Some(err) = printer.error { 38 | return Err(err); 39 | } 40 | Ok(printer.output) 41 | } 42 | 43 | fn process(&mut self, processor: F) 44 | where 45 | F: FnOnce(&mut Self) -> Result<()>, 46 | { 47 | if self.error.is_some() { 48 | return; 49 | } 50 | 51 | if let Err(err) = processor(self) { 52 | self.error = Some(err); 53 | } 54 | } 55 | 56 | fn process_with_context(&mut self, context: Context, processor: F) 57 | where 58 | F: FnOnce(&mut Self) -> Result<()>, 59 | { 60 | self.with_context(context, |_self| { 61 | _self.process(processor); 62 | }); 63 | } 64 | 65 | fn maybe_process_with_context(&mut self, maybe_context: Option, processor: F) 66 | where 67 | F: FnOnce(&mut Self) -> Result<()>, 68 | { 69 | match maybe_context { 70 | Some(context) => self.with_context(context, |_self| { 71 | _self.process(processor); 72 | }), 73 | None => self.process(processor), 74 | } 75 | } 76 | 77 | fn with_context(&mut self, context: Context, processor: F) 78 | where 79 | F: FnOnce(&mut Self), 80 | { 81 | let outer_context = std::mem::replace(&mut self.context, context); 82 | processor(self); 83 | self.context = outer_context; 84 | } 85 | 86 | fn append(&mut self, string: S) 87 | where 88 | S: AsRef, 89 | { 90 | self.output.push_str(string.as_ref()); 91 | } 92 | 93 | fn appendln(&mut self, string: S) 94 | where 95 | S: AsRef, 96 | { 97 | self.append(string); 98 | self.output.push('\n'); 99 | } 100 | 101 | fn add(&mut self, string: S) 102 | where 103 | S: AsRef, 104 | { 105 | self.output.push_str(&self.indent); 106 | self.append(string.as_ref()); 107 | } 108 | 109 | fn addln(&mut self, string: S) 110 | where 111 | S: AsRef, 112 | { 113 | self.add(string); 114 | self.output.push('\n'); 115 | } 116 | 117 | fn indent(&mut self, processor: F) 118 | where 119 | F: FnOnce(&mut Self), 120 | { 121 | self.indent.push_str(" "); 122 | processor(self); 123 | self.indent.pop(); 124 | self.indent.pop(); 125 | } 126 | 127 | fn current_var(&self) -> String { 128 | format!("__var__{}", self.unique_id) 129 | } 130 | 131 | fn done_with_var(&mut self) { 132 | self.unique_id += 1; 133 | } 134 | } 135 | 136 | enum Context { 137 | TopLevel, 138 | ItemFn, 139 | LetBinding(String), 140 | LetBindingResult(String), 141 | NormalStmt, 142 | ReturnStmt, 143 | } 144 | 145 | impl syn::visit::Visit<'_> for AstPrinter { 146 | fn visit_item_struct(&mut self, strct: &syn::ItemStruct) { 147 | if !matches!(self.mode, PrinterMode::Declarations) { 148 | return; 149 | } 150 | self.addln(format!("struct {} {{", strct.ident.to_string())); 151 | self.indent(|_self| { 152 | if let syn::Fields::Named(syn::FieldsNamed { named, .. }) = &strct.fields { 153 | for field in named.iter() { 154 | _self.visit_type(&field.ty); 155 | _self.append(format!(" {}", field.ident.as_ref().unwrap().to_string())); 156 | _self.appendln(";"); 157 | } 158 | } 159 | }); 160 | self.addln("};\n"); 161 | } 162 | 163 | fn visit_item_const(&mut self, _: &syn::ItemConst) {} 164 | 165 | fn visit_item_fn(&mut self, fun: &syn::ItemFn) { 166 | self.process_with_context(Context::ItemFn, |_self| { 167 | let ret_type = match &fun.sig.output { 168 | syn::ReturnType::Default => "void".to_string(), 169 | syn::ReturnType::Type(_, x) => cp(x), 170 | }; 171 | let name = cp(&fun.sig.ident); 172 | _self.add(format!("{} {} (", ret_type, name)); 173 | 174 | let inputs = &fun.sig.inputs; 175 | let num_inputs = inputs.len(); 176 | let is_last = |i: usize| i == num_inputs - 1; 177 | for (i, param) in inputs.iter().enumerate() { 178 | match param { 179 | syn::FnArg::Typed(syn::PatType { ty, pat, attrs, .. }) => match &**pat { 180 | syn::Pat::Ident(syn::PatIdent { ident, .. }) => { 181 | attrs.iter().for_each(|attr| { 182 | if cp(&attr.path) == "address_space" { 183 | if let Ok(syn::Meta::List(syn::MetaList { nested, .. })) = attr.parse_meta() { 184 | if let Some(syn::NestedMeta::Meta(syn::Meta::Path(name))) = nested.first() { 185 | _self.add(format!("{} ", cp(name))); 186 | } 187 | } 188 | } 189 | }); 190 | _self.visit_type(ty); 191 | _self.add(format!(" {}", ident)); 192 | if !is_last(i) { 193 | _self.add(", "); 194 | } 195 | } 196 | _ => anyhow::bail!("Unsupported argument type"), 197 | }, 198 | _ => anyhow::bail!("Unsupported argument type"), 199 | } 200 | } 201 | _self.add(")"); 202 | match _self.mode { 203 | PrinterMode::Declarations => { 204 | _self.append(";\n"); 205 | } 206 | PrinterMode::Definitions => _self.visit_block(&*fun.block), 207 | } 208 | _self.output.push('\n'); 209 | Ok(()) 210 | }); 211 | } 212 | 213 | fn visit_type(&mut self, ty: &syn::Type) { 214 | match ty { 215 | syn::Type::Reference(syn::TypeReference { elem, .. }) => { 216 | self.visit_type(elem); 217 | self.add("&"); 218 | } 219 | _ => self.add(cp(ty)), 220 | } 221 | } 222 | 223 | fn visit_block(&mut self, block: &syn::Block) { 224 | self.addln("{"); 225 | self.indent(|_self| { 226 | if matches!(_self.context, Context::ItemFn | Context::ReturnStmt) { 227 | let num_stmts = block.stmts.len(); 228 | let is_last = |i: usize| i == num_stmts - 1; 229 | block.stmts.iter().enumerate().for_each(|(i, statement)| { 230 | _self.with_context( 231 | if is_last(i) { 232 | Context::ReturnStmt 233 | } else { 234 | Context::NormalStmt 235 | }, 236 | |_self| _self.visit_stmt(statement), 237 | ) 238 | }); 239 | } else { 240 | syn::visit::visit_block(_self, block); 241 | } 242 | }); 243 | self.addln("}"); 244 | } 245 | 246 | fn visit_stmt(&mut self, statement: &syn::Stmt) { 247 | syn::visit::visit_stmt(self, statement); 248 | if matches!(statement, syn::Stmt::Semi(_, _)) { 249 | self.append(";\n"); 250 | } 251 | } 252 | 253 | fn visit_local(&mut self, local: &syn::Local) { 254 | self.process(|_self| { 255 | let ty = match &local.pat { 256 | syn::Pat::Type(syn::PatType { ty, .. }) => cp(ty), 257 | syn::Pat::Struct(syn::PatStruct { path, .. }) => cp(path), 258 | _ => "auto".to_string(), 259 | }; 260 | let var_name = match match &local.pat { 261 | syn::Pat::Type(syn::PatType { pat, .. }) => pat, 262 | pat => pat, 263 | } { 264 | syn::Pat::Ident(syn::PatIdent { ident, .. }) => cp(ident), 265 | syn::Pat::Struct(_) => _self.current_var(), 266 | _ => anyhow::bail!("Unsupported assignment pattern"), 267 | }; 268 | _self.add(format!("{} {}", ty, var_name)); 269 | match &local.init { 270 | Some((_, expression)) => _self.with_context(Context::LetBinding(var_name), |_self| { 271 | _self.visit_expr(expression); 272 | }), 273 | None => _self.appendln(";"), 274 | } 275 | if let syn::Pat::Struct(syn::PatStruct { fields, .. }) = &local.pat { 276 | let var_name = _self.current_var(); 277 | for field in fields { 278 | if let syn::Member::Named(name) = &field.member { 279 | if let syn::Pat::Ident(syn::PatIdent { ident, .. }) = &*field.pat { 280 | _self.add(format!("; auto {} = {}.{}", ident, var_name, name)); 281 | } else { 282 | anyhow::bail!("Unsupported struct member in pattern"); 283 | } 284 | } else { 285 | anyhow::bail!("Unsupported struct member in pattern"); 286 | } 287 | } 288 | _self.done_with_var(); 289 | _self.appendln(";"); 290 | } 291 | Ok(()) 292 | }); 293 | } 294 | 295 | fn visit_expr(&mut self, expression: &syn::Expr) { 296 | self.process(|_self| { 297 | match expression { 298 | syn::Expr::ForLoop(syn::ExprForLoop { 299 | pat, expr, body, .. 300 | }) => match &**expr { 301 | syn::Expr::Range(syn::ExprRange { 302 | from: Some(from), 303 | to: Some(to), 304 | limits, 305 | .. 306 | }) => { 307 | _self.addln(format!( 308 | "for (auto {pat} = {from}; {pat} <{limit} {to}; {pat}++)", 309 | pat = cp(pat), 310 | from = cp(&*from), 311 | to = cp(&*to), 312 | limit = if matches!(limits, syn::RangeLimits::Closed(_)) { 313 | "=" 314 | } else { 315 | "" 316 | }, 317 | )); 318 | _self.visit_block(body); 319 | } 320 | _ => anyhow::bail!("Unsupported for loop expression"), 321 | }, 322 | syn::Expr::While(syn::ExprWhile { cond, body, .. }) => { 323 | _self.addln(format!("while ({})", cp(cond))); 324 | _self.visit_block(body); 325 | } 326 | syn::Expr::If(if_expression) => { 327 | let binding_context = if let Context::LetBinding(var_name) = &_self.context { 328 | Some(Context::LetBindingResult(var_name.clone())) 329 | } else { 330 | None 331 | }; 332 | let syn::ExprIf { 333 | cond, 334 | then_branch, 335 | else_branch, 336 | .. 337 | } = if_expression; 338 | let is_in_binding = binding_context.is_some(); 339 | if is_in_binding && is_if_simple_ternary(if_expression) { 340 | _self.append(format!(" = ({}) ? ", cp(cond))); 341 | _self.with_context(Context::NormalStmt, |_self| { 342 | _self.visit_stmt(then_branch.stmts.first().unwrap()); 343 | _self.append(" : "); 344 | let else_clause = &*else_branch.as_ref().unwrap().1; 345 | match else_clause { 346 | syn::Expr::If(_) => _self.visit_expr(else_clause), 347 | syn::Expr::Block(syn::ExprBlock { block, .. }) => { 348 | _self.visit_stmt(block.stmts.first().unwrap()) 349 | } 350 | // Never happens 351 | _ => {} 352 | } 353 | _self.appendln(";"); 354 | }) 355 | } else { 356 | if is_in_binding { 357 | // close the variable declaration 358 | _self.appendln(";"); 359 | } 360 | _self.maybe_process_with_context(binding_context, |_self| { 361 | _self.addln(format!("if ({})", cp(cond))); 362 | _self.visit_block(then_branch); 363 | if let Some((_, else_branch)) = else_branch { 364 | _self.addln("else"); 365 | _self.visit_expr(&**else_branch); 366 | } 367 | Ok(()) 368 | }); 369 | } 370 | } 371 | syn::Expr::Block(syn::ExprBlock { block, .. }) => _self.visit_block(block), 372 | syn::Expr::Return(_) => _self.add(cp(expression)), 373 | // We're gonna assume that any other expression is a bare expression in C++ 374 | _ => match &_self.context { 375 | Context::ReturnStmt => _self.addln(format!("return {};", cp(expression))), 376 | Context::LetBinding(_) => { 377 | _self.appendln(format!(" = {};", cp(expression))); 378 | } 379 | Context::LetBindingResult(var_name) => { 380 | let var_name = var_name.clone(); 381 | _self.addln(format!("{} = {};", var_name, cp(expression))); 382 | } 383 | _ => _self.add(cp(expression)), 384 | }, 385 | } 386 | Ok(()) 387 | }); 388 | } 389 | } 390 | 391 | fn cp(x: &T) -> String 392 | where 393 | T: quote::ToTokens, 394 | { 395 | quote::quote!(#x).to_string() 396 | } 397 | 398 | fn is_else_clause_simple_ternary_clause( 399 | else_branch: &Option<(syn::token::Else, Box)>, 400 | ) -> bool { 401 | if let Some((_, expression)) = else_branch { 402 | match &**expression { 403 | syn::Expr::If(if_expression) => is_if_simple_ternary(if_expression), 404 | syn::Expr::Block(syn::ExprBlock { block, .. }) => is_block_simple_ternary_clause(block), 405 | // This never happens 406 | _ => false, 407 | } 408 | } else { 409 | true 410 | } 411 | } 412 | 413 | fn is_block_simple_ternary_clause(block: &syn::Block) -> bool { 414 | let syn::Block { stmts, .. } = block; 415 | if stmts.len() != 1 { 416 | return false; 417 | } 418 | return is_stmt_simple_expr(stmts.first().unwrap()); 419 | } 420 | 421 | fn is_stmt_simple_expr(statement: &syn::Stmt) -> bool { 422 | match statement { 423 | syn::Stmt::Expr(expression) => is_expr_simple_expr(expression), 424 | _ => false, 425 | } 426 | } 427 | 428 | fn is_expr_simple_expr(expression: &syn::Expr) -> bool { 429 | match expression { 430 | syn::Expr::ForLoop(_) => false, 431 | syn::Expr::While(_) => false, 432 | syn::Expr::Block(_) => false, 433 | syn::Expr::Return(_) => false, 434 | syn::Expr::If(if_expression) => is_if_simple_ternary(if_expression), 435 | // We're gonna assume that any other expression is a bare expression in C++ 436 | _ => true, 437 | } 438 | } 439 | 440 | fn is_if_simple_ternary(expression: &syn::ExprIf) -> bool { 441 | let syn::ExprIf { 442 | then_branch, 443 | else_branch, 444 | .. 445 | } = expression; 446 | is_block_simple_ternary_clause(then_branch) && is_else_clause_simple_ternary_clause(else_branch) 447 | } 448 | -------------------------------------------------------------------------------- /rust_to_metal_sl/src/rust_to_metal_sl.rs: -------------------------------------------------------------------------------- 1 | mod adapter; 2 | mod enhancer; 3 | mod parser; 4 | mod printer; 5 | 6 | use anyhow::Result; 7 | 8 | pub use enhancer::EnhanceConfig; 9 | 10 | /// Returns Metal Shader Language source code. 11 | pub fn transpile(rust_source: &str, config: &EnhanceConfig) -> Result { 12 | let rust_ast = parser::parse_rust_into_ast(rust_source)?; 13 | let rust_ast_enhanced = enhancer::convert_constant_to_param(rust_ast, config); 14 | let msl_compatible_ast = adapter::make_rust_ast_msl_compatible(rust_ast_enhanced); 15 | printer::print_ast_into_msl(msl_compatible_ast) 16 | } 17 | -------------------------------------------------------------------------------- /shader_roy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shader_roy" 3 | version = "0.1.0" 4 | authors = ["Michal Srb "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "shader_roy" 9 | path = "src/shader_roy.rs" 10 | 11 | [dependencies] 12 | rust_to_metal_sl = { path="../rust_to_metal_sl" } 13 | metal = { git = "https://github.com/xixixao/metal-rs", branch = "fix-errors-new-library" } 14 | objc = { version = "0.2.4", features = ["objc_exception"] } 15 | winit = { git = "https://github.com/xixixao/winit", branch = "with_outer_position" } 16 | regex_try = { path="../regex_try" } 17 | cocoa = "0.24.0" 18 | anyhow = "1.0.38" 19 | vek = "0.13.0" 20 | lazy_static = "1.4.0" 21 | notify = "4.0.15" 22 | toml = "0.5.8" 23 | path-absolutize = "3.0.6" 24 | regex = "1.4.3" 25 | chrono = "0.4.19" -------------------------------------------------------------------------------- /shader_roy/src/shader_compiler.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use path_absolutize::Absolutize; 3 | 4 | const ENTRY_POINT_FN_NAME: &str = "pixel_color"; 5 | 6 | lazy_static::lazy_static! { 7 | pub static ref ROOT_PATH: std::path::PathBuf = 8 | std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); 9 | pub static ref SHADER_PRELUDE_PATH: std::path::PathBuf = 10 | ROOT_PATH.join("src/shader_prelude.metal"); 11 | pub static ref SHADER_INTERFACE_PATH: std::path::PathBuf = 12 | ROOT_PATH.join("../shader_roy_metal_sl_interface/src/shader_roy_metal_sl_interface.rs"); 13 | } 14 | 15 | pub fn compile_shader( 16 | shader_file_path: &std::path::Path, 17 | device: &metal::Device, 18 | mut on_compiled: F, 19 | ) -> Result 20 | where 21 | F: FnMut(String), 22 | { 23 | let shader_prelude = std::fs::read_to_string(&*SHADER_PRELUDE_PATH)?; 24 | let shader_interface = std::fs::read_to_string(&*SHADER_INTERFACE_PATH)?; 25 | let combined_shader = { 26 | let fragment_shader_in_rust = read_shader_sources(shader_file_path)?; 27 | let config = rust_to_metal_sl::EnhanceConfig { 28 | entry_point_fn_name: ENTRY_POINT_FN_NAME.to_owned(), 29 | constant_name: "INPUT".to_owned(), 30 | param_type: "Input".to_owned(), 31 | }; 32 | let fragment_interface_in_msl = rust_to_metal_sl::transpile(&shader_interface, &config)?; 33 | let fragment_shader_in_msl = rust_to_metal_sl::transpile(&fragment_shader_in_rust, &config)?; 34 | let combined_shader = 35 | // voca_rs::manipulate::replace(&shader_prelude, "/// SHADER_RS", &fragment_shader_in_msl); 36 | shader_prelude.replace( 37 | "SHADER_RS_ENTRYPOINT", 38 | "pixel_color", 39 | ).replace( 40 | "/// SHADER_RS", 41 | &format!("{}{}", fragment_interface_in_msl, fragment_shader_in_msl), 42 | ); 43 | on_compiled(fragment_shader_in_msl); 44 | combined_shader 45 | }; 46 | let library = device 47 | .new_library_with_source(&combined_shader, &metal::CompileOptions::new()) 48 | .map_err(anyhow::Error::msg)?; 49 | Ok(library) 50 | } 51 | 52 | fn read_shader_sources(shader_file_path: &std::path::Path) -> Result { 53 | let fragment_shader_in_rust = std::fs::read_to_string(shader_file_path).with_context(|| { 54 | format!( 55 | "Failed to read shader from `{:?}`", 56 | shader_file_path.absolutize().unwrap() 57 | ) 58 | })?; 59 | lazy_static::lazy_static! { 60 | static ref MODULE_DECLARATION: regex::Regex = 61 | regex::Regex::new(r"mod\s+(?P\w+)\s*;").unwrap(); 62 | } 63 | let shader_directory = shader_file_path.parent().unwrap().to_owned(); 64 | 65 | use regex_try::RegexTry; 66 | MODULE_DECLARATION 67 | .try_replace_all(&fragment_shader_in_rust, |captured: ®ex::Captures| { 68 | read_shader_sources( 69 | &shader_directory 70 | .join(&captured["module_name"]) 71 | .with_extension("rs"), 72 | ) 73 | }) 74 | .map(|result| result.into_owned()) 75 | } 76 | -------------------------------------------------------------------------------- /shader_roy/src/shader_file_path_arg.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Context, Result}; 2 | use path_absolutize::Absolutize; 3 | 4 | lazy_static::lazy_static! { 5 | pub static ref ROOT_PATH: std::path::PathBuf = 6 | std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); 7 | } 8 | 9 | pub fn get_path() -> Result { 10 | let args: Vec = std::env::args().collect(); 11 | get_path_for_argument(args.get(1).ok_or_else(|| { 12 | anyhow!("Cannot start ShaderRoy: Missing shader entry point file path argument") 13 | })?) 14 | } 15 | 16 | pub fn get_path_for_argument(argument: &str) -> Result { 17 | let arg_path = &std::path::PathBuf::from(argument); 18 | path_or_lib_src(arg_path).map_err(|error| { 19 | anyhow!( 20 | "Cannot start ShaderRoy: {:?}\ 21 | \n\nFor argument {:?} (absolute path {:?})", 22 | error, 23 | arg_path, 24 | arg_path.absolutize().unwrap() 25 | ) 26 | }) 27 | } 28 | 29 | fn path_or_lib_src(arg_path: &std::path::PathBuf) -> Result { 30 | let (resolved_path, path_info) = std::fs::metadata(arg_path) 31 | .map(|info| (arg_path.to_owned(), info)) 32 | .or_else(|_| { 33 | let example_path = ROOT_PATH.join("../examples").join(arg_path); 34 | std::fs::metadata(&example_path).map(|info| (example_path, info)) 35 | }) 36 | .with_context(|| "File path argument doesn\'t match an existing directory or file.")?; 37 | Ok(if path_info.is_dir() { 38 | let cargo_manifest_path = &resolved_path.join("Cargo.toml"); 39 | let cargo_manifest = std::fs::read_to_string(cargo_manifest_path).with_context(|| { 40 | "File path matches a directory, but the directory is missing the expected Cargo.toml file." 41 | })?; 42 | let parsed_cargo_manifest = cargo_manifest.parse::().with_context(|| { 43 | format!( 44 | "Could not parse Cargo.toml file at {:?}", 45 | cargo_manifest_path 46 | ) 47 | })?; 48 | if let Some(path) = parsed_cargo_manifest["lib"]["path"].as_str() { 49 | let source_path = resolved_path.join(path); 50 | std::fs::metadata(&source_path).with_context(|| { 51 | format!( 52 | "Could not find the source file at {:?} based on [lib][path] {} specified in Cargo.toml file at {:?}", 53 | source_path, 54 | path, 55 | cargo_manifest_path 56 | ) 57 | })?; 58 | source_path 59 | } else { 60 | resolved_path.join("src/lib.rs") 61 | } 62 | } else { 63 | resolved_path 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /shader_roy/src/shader_prelude.metal: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace metal; 4 | 5 | /// SHADER_RS 6 | 7 | // The vertex shader 8 | float2 rect_vert(float4 rect, uint vid); 9 | vertex float4 vertex_shader( 10 | const device float4 *clear_rect [[ buffer(0) ]], 11 | unsigned int vertex_index [[ vertex_id ]] 12 | ) { 13 | return float4(rect_vert(*clear_rect, vertex_index), 0, 1); 14 | } 15 | 16 | // The fragment shader 17 | fragment float4 fragment_shader( 18 | float4 in [[position]], 19 | constant Input& input [[ buffer(0) ]] 20 | ) { 21 | return SHADER_RS_ENTRYPOINT(float2(in.x, in.y), input); 22 | } 23 | 24 | // Helper for computing pixel position in vertext shader 25 | float2 rect_vert(float4 rect, uint vertex_index) { 26 | float left = rect.x; 27 | float right = rect.x + rect.z; 28 | float bottom = rect.y; 29 | float top = rect.y + rect.w; 30 | 31 | switch (vertex_index) { 32 | case 0: 33 | return float2(right, top); 34 | case 1: 35 | return float2(left, top); 36 | case 2: 37 | return float2(right, bottom); 38 | case 3: 39 | return float2(left, bottom); 40 | } 41 | } -------------------------------------------------------------------------------- /shader_roy/src/shader_roy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 metal-rs developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | extern crate objc; 9 | 10 | mod shader_compiler; 11 | mod shader_file_path_arg; 12 | 13 | use anyhow::{Context, Result}; 14 | 15 | use cocoa::{appkit::NSView, base::id as cocoa_id}; 16 | 17 | use metal::*; 18 | use objc::{rc::autoreleasepool, runtime::YES}; 19 | use std::mem; 20 | use winit::platform::macos::WindowExtMacOS; 21 | 22 | use winit::{ 23 | event::{Event, WindowEvent}, 24 | event_loop::ControlFlow, 25 | }; 26 | 27 | // Used for passing per-frame input to pixel shader and for setting up the vertex shader 28 | #[repr(C)] 29 | #[derive(Copy, Clone)] 30 | struct Float2 { 31 | pub x: f32, 32 | pub y: f32, 33 | } 34 | #[repr(C)] 35 | #[derive(Copy, Clone)] 36 | struct Float4 { 37 | pub x: f32, 38 | pub y: f32, 39 | pub z: f32, 40 | pub w: f32, 41 | } 42 | #[repr(C)] 43 | struct Input { 44 | window_size: Float2, 45 | window_position: Float2, 46 | cursor_position: Float2, 47 | is_cursor_inside_window: bool, 48 | elapsed_time_secs: f32, 49 | elapsed_time_since_last_frame_secs: f32, 50 | frame_count: f32, 51 | year_month_day_tz: Float4, 52 | } 53 | 54 | enum CLICommand { 55 | Pause, 56 | Restart, 57 | Run(String), 58 | } 59 | 60 | fn main() -> Result<()> { 61 | let events_loop = winit::event_loop::EventLoop::new(); 62 | let (window_size, window_position) = window_sizing((0.4, 0.4), &events_loop); 63 | let window = winit::window::WindowBuilder::new() 64 | .with_inner_size(window_size) 65 | .with_outer_position(window_position) 66 | .with_title("ShaderRoy") 67 | .with_always_on_top(true) 68 | .build(&events_loop) 69 | .unwrap(); 70 | 71 | let device = Device::system_default().expect("no device found"); 72 | 73 | let layer = MetalLayer::new(); 74 | layer.set_device(&device); 75 | layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm); 76 | layer.set_presents_with_transaction(false); 77 | 78 | unsafe { 79 | let view = window.ns_view() as cocoa_id; 80 | view.setWantsLayer(YES); 81 | view.setLayer(mem::transmute(layer.as_ref())); 82 | } 83 | 84 | let draw_size = window.inner_size(); 85 | layer.set_drawable_size(CGSize::new(draw_size.width as f64, draw_size.height as f64)); 86 | 87 | let vector_rect = vec![Float4 { 88 | x: -1.0, 89 | y: -1.0, 90 | z: 2.0, 91 | w: 2.0, 92 | }]; 93 | 94 | let vector_buffer = device.new_buffer_with_data( 95 | vector_rect.as_ptr() as *const _, 96 | mem::size_of::() as u64, 97 | MTLResourceOptions::CPUCacheModeDefaultCache | MTLResourceOptions::StorageModeManaged, 98 | ); 99 | 100 | let command_queue = device.new_command_queue(); 101 | let mut run_error: Option<()> = None; 102 | 103 | let mut shader_file_path = shader_file_path_arg::get_path()?; 104 | let mut shader_files_watcher = ShaderFilesWatcher::new(std::time::Duration::from_secs(1))?; 105 | shader_files_watcher.watch_directory(&shader_file_path)?; 106 | let cli_commands_watcher = CLICommandsWatcher::new(); 107 | let mut pipeline_state: Option = None; 108 | let mut frame_rate_reporter = FrameRateReporter::new(); 109 | let mut input_computer = InputComputer::new(); 110 | 111 | events_loop.run(move |event, _, control_flow| { 112 | autoreleasepool(|| { 113 | let res = (|| -> Result<(), Box> { 114 | *control_flow = ControlFlow::WaitUntil( 115 | std::time::Instant::now() + std::time::Duration::from_secs_f64(1.0 / 60.0), 116 | ); 117 | 118 | match event { 119 | Event::WindowEvent { event, .. } => match event { 120 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 121 | WindowEvent::Resized(size) => { 122 | layer.set_drawable_size(CGSize::new( 123 | size.width as f64, 124 | size.height as f64, 125 | )); 126 | } 127 | WindowEvent::Focused(is_focused) => { 128 | input_computer.set_is_focused(is_focused); 129 | } 130 | WindowEvent::CursorEntered { .. } => { 131 | input_computer.set_cursor_presence(true); 132 | } 133 | WindowEvent::CursorLeft { .. } => { 134 | input_computer.set_cursor_presence(false); 135 | } 136 | WindowEvent::CursorMoved { position, .. } => { 137 | input_computer.set_cursor_position(position); 138 | } 139 | WindowEvent::MouseInput { state, button, .. } => { 140 | input_computer.set_cursor_click_state(button, state) 141 | } 142 | _ => (), 143 | }, 144 | Event::MainEventsCleared => { 145 | window.request_redraw(); 146 | } 147 | Event::RedrawRequested(_) => { 148 | match cli_commands_watcher.try_recv() { 149 | Some(CLICommand::Pause) => { 150 | input_computer.toggle_paused(); 151 | CLICommandsWatcher::print_prompt(); 152 | } 153 | Some(CLICommand::Restart) => { 154 | input_computer.reset(); 155 | CLICommandsWatcher::print_prompt(); 156 | } 157 | Some(CLICommand::Run(new_path)) => { 158 | shader_file_path = 159 | shader_file_path_arg::get_path_for_argument(&new_path)?; 160 | shader_files_watcher.watch_directory(&shader_file_path)?; 161 | pipeline_state = None; 162 | run_error = None; 163 | input_computer.reset(); 164 | } 165 | None => {} 166 | } 167 | if input_computer.is_paused() { 168 | return Ok(()); 169 | } 170 | 171 | if pipeline_state.is_none() && run_error.is_none() 172 | || shader_files_watcher.has_changes() 173 | { 174 | let library = shader_compiler::compile_shader( 175 | &shader_file_path, 176 | &device, 177 | |fragment_shader_in_msl| { 178 | println!("{}", fragment_shader_in_msl); 179 | CLICommandsWatcher::print_instructions(); 180 | }, 181 | )?; 182 | pipeline_state = Some(prepare_pipeline_state( 183 | &device, 184 | &library, 185 | "vertex_shader", 186 | "fragment_shader", 187 | )?); 188 | } 189 | if pipeline_state.is_none() { 190 | return Ok(()); 191 | } 192 | 193 | let render_pass_descriptor = RenderPassDescriptor::new(); 194 | 195 | let drawable = layer.next_drawable().ok_or("No drawable")?; 196 | prepare_render_pass_descriptor(&render_pass_descriptor, drawable.texture()); 197 | 198 | let command_buffer = command_queue.new_command_buffer(); 199 | let encoder = 200 | command_buffer.new_render_command_encoder(&render_pass_descriptor); 201 | 202 | encoder.set_render_pipeline_state(pipeline_state.as_ref().unwrap()); 203 | encoder.set_vertex_buffer(0, Some(&vector_buffer), 0); 204 | encoder.set_fragment_bytes( 205 | 0, 206 | std::mem::size_of::() as u64, 207 | &input_computer.current_input(&window) as *const Input as *const _, 208 | ); 209 | encoder.draw_primitives_instanced( 210 | metal::MTLPrimitiveType::TriangleStrip, 211 | 0, 212 | 4, 213 | 1, 214 | ); 215 | 216 | encoder.end_encoding(); 217 | command_buffer.present_drawable(&drawable); 218 | command_buffer.commit(); 219 | 220 | frame_rate_reporter.calculate_frame_rate_and_maybe_report(&window); 221 | } 222 | _ => {} 223 | }; 224 | Ok(()) 225 | })(); 226 | if let Err(err) = res { 227 | println!("{:?}", err); 228 | CLICommandsWatcher::print_instructions(); 229 | run_error = Some(()); 230 | } 231 | }); 232 | }); 233 | } 234 | 235 | struct CLICommandsWatcher { 236 | #[allow(dead_code)] // Required to avoid dropping child 237 | watcher_thread: std::thread::JoinHandle<()>, 238 | receiver: std::sync::mpsc::Receiver, 239 | } 240 | 241 | impl CLICommandsWatcher { 242 | fn new() -> Self { 243 | let (tx, receiver) = std::sync::mpsc::channel(); 244 | let watcher_thread = std::thread::spawn(move || loop { 245 | let mut cli_input = String::new(); 246 | let _ = std::io::stdin().read_line(&mut cli_input); 247 | let event = match &cli_input[0..1] { 248 | "p" => CLICommand::Pause, 249 | "r" => match &cli_input[1..2] { 250 | " " => CLICommand::Run(cli_input[2..cli_input.len() - 1].to_owned()), 251 | _ => CLICommand::Restart, 252 | }, 253 | _ => { 254 | continue; 255 | } 256 | }; 257 | tx.send(event).unwrap(); 258 | }); 259 | 260 | CLICommandsWatcher { 261 | watcher_thread, 262 | receiver, 263 | } 264 | } 265 | 266 | fn try_recv(&self) -> Option { 267 | self.receiver.try_recv().ok() 268 | } 269 | 270 | fn print_instructions() { 271 | println!("[p] to pause, [r] to restart, [r ] to run another shader "); 272 | Self::print_prompt(); 273 | } 274 | 275 | fn print_prompt() { 276 | print!("> "); 277 | use std::io::Write; 278 | let _ = std::io::stdout().flush(); 279 | } 280 | } 281 | 282 | struct InputComputer { 283 | start_time: std::time::Instant, 284 | last_frame_time: std::time::Instant, 285 | frame_count: u64, 286 | is_window_focused: bool, 287 | is_cursor_inside_window: bool, 288 | cursor_position: Float2, 289 | pointer_device_state: Float2, 290 | date: Float4, 291 | pause_time: Option, 292 | } 293 | 294 | impl InputComputer { 295 | fn new() -> Self { 296 | Self { 297 | start_time: std::time::Instant::now(), 298 | last_frame_time: std::time::Instant::now(), 299 | frame_count: 0, 300 | is_window_focused: true, 301 | is_cursor_inside_window: false, 302 | cursor_position: Float2 { x: 0.0, y: 0.0 }, 303 | pointer_device_state: Float2 { x: 0.0, y: 0.0 }, 304 | date: current_date_as_float4(), 305 | pause_time: None, 306 | } 307 | } 308 | 309 | fn reset(&mut self) { 310 | self.start_time = std::time::Instant::now(); 311 | self.last_frame_time = std::time::Instant::now(); 312 | self.frame_count = 0; 313 | self.date = current_date_as_float4(); 314 | self.pause_time = None; 315 | } 316 | 317 | fn toggle_paused(&mut self) { 318 | if let Some(pause_time) = self.pause_time { 319 | let pause_duration = pause_time.elapsed(); 320 | self.start_time += pause_duration; 321 | self.last_frame_time = std::time::Instant::now(); 322 | self.pause_time = None; 323 | } else { 324 | self.pause_time = Some(std::time::Instant::now()); 325 | } 326 | } 327 | 328 | fn is_paused(&self) -> bool { 329 | self.pause_time.is_some() 330 | } 331 | 332 | fn set_is_focused(&mut self, is_focused: bool) { 333 | self.is_window_focused = is_focused; 334 | } 335 | 336 | fn set_cursor_presence(&mut self, is_present: bool) { 337 | self.is_cursor_inside_window = self.is_window_focused && is_present; 338 | } 339 | 340 | fn set_cursor_position(&mut self, position: winit::dpi::PhysicalPosition) { 341 | self.is_cursor_inside_window = true; 342 | self.cursor_position = Float2 { 343 | x: position.x as f32, 344 | y: position.y as f32, 345 | }; 346 | } 347 | 348 | fn set_cursor_click_state( 349 | &mut self, 350 | button: winit::event::MouseButton, 351 | state: winit::event::ElementState, 352 | ) { 353 | if !self.is_cursor_inside_window { 354 | return; 355 | } 356 | let state_value = match state { 357 | winit::event::ElementState::Pressed => 1.0, 358 | winit::event::ElementState::Released => 0.0, 359 | }; 360 | match button { 361 | winit::event::MouseButton::Left => { 362 | self.pointer_device_state.x = state_value; 363 | } 364 | winit::event::MouseButton::Right => { 365 | self.pointer_device_state.y = state_value; 366 | } 367 | _ => {} 368 | } 369 | } 370 | 371 | fn current_input(&mut self, window: &winit::window::Window) -> Input { 372 | let physical_size = window.inner_size(); 373 | let physical_position = window.inner_position().unwrap(); 374 | self.frame_count += 1; 375 | let result = Input { 376 | window_size: Float2 { 377 | x: physical_size.width as f32, 378 | y: physical_size.height as f32, 379 | }, 380 | window_position: Float2 { 381 | x: physical_position.x as f32, 382 | y: physical_position.y as f32, 383 | }, 384 | cursor_position: self.cursor_position, 385 | is_cursor_inside_window: self.is_cursor_inside_window, 386 | elapsed_time_since_last_frame_secs: self.last_frame_time.elapsed().as_secs_f32(), 387 | elapsed_time_secs: self.start_time.elapsed().as_secs_f32(), 388 | frame_count: self.frame_count as f32, 389 | year_month_day_tz: self.date, 390 | }; 391 | self.last_frame_time = std::time::Instant::now(); 392 | result 393 | } 394 | } 395 | 396 | fn current_date_as_float4() -> Float4 { 397 | let today = chrono::prelude::Local::now(); 398 | use chrono::Datelike; 399 | Float4 { 400 | x: today.year() as f32, 401 | y: today.month() as f32, 402 | z: today.day() as f32, 403 | w: today.offset().local_minus_utc() as f32, 404 | } 405 | } 406 | 407 | fn prepare_pipeline_state( 408 | device: &DeviceRef, 409 | library: &LibraryRef, 410 | vertex_shader: &str, 411 | fragment_shader: &str, 412 | ) -> Result> { 413 | let vert = library.get_function(vertex_shader, None)?; 414 | let frag = library.get_function(fragment_shader, None)?; 415 | 416 | let pipeline_state_descriptor = RenderPipelineDescriptor::new(); 417 | pipeline_state_descriptor.set_vertex_function(Some(&vert)); 418 | pipeline_state_descriptor.set_fragment_function(Some(&frag)); 419 | let attachment = pipeline_state_descriptor 420 | .color_attachments() 421 | .object_at(0) 422 | .ok_or("No attachment")?; 423 | attachment.set_pixel_format(MTLPixelFormat::BGRA8Unorm); 424 | Ok(device.new_render_pipeline_state(&pipeline_state_descriptor)?) 425 | } 426 | 427 | fn prepare_render_pass_descriptor(descriptor: &RenderPassDescriptorRef, texture: &TextureRef) { 428 | let color_attachment = descriptor.color_attachments().object_at(0).unwrap(); 429 | color_attachment.set_texture(Some(texture)); 430 | color_attachment.set_load_action(MTLLoadAction::Clear); 431 | color_attachment.set_clear_color(MTLClearColor::new(0.0, 0.0, 0.0, 1.0)); 432 | color_attachment.set_store_action(MTLStoreAction::Store); 433 | } 434 | 435 | fn window_sizing( 436 | scale: (f32, f32), 437 | events_loop: &winit::event_loop::EventLoop<()>, 438 | ) -> ( 439 | winit::dpi::LogicalSize, 440 | winit::dpi::LogicalPosition, 441 | ) { 442 | let size_scale: vek::Vec2<_> = scale.into(); 443 | let screen = events_loop.primary_monitor().unwrap(); 444 | let screen_size: vek::Vec2 = { 445 | let size: (f32, f32) = screen 446 | .size() 447 | .to_logical::(screen.scale_factor()) 448 | .into(); 449 | size.into() 450 | }; 451 | let window_size = (screen_size * size_scale).into_tuple().into(); 452 | let window_position = ((screen_size * (vek::Vec2::from(1.0) - size_scale)).into_tuple()).into(); 453 | (window_size, window_position) 454 | } 455 | 456 | struct ShaderFilesWatcher { 457 | watcher: notify::RecommendedWatcher, 458 | receiver: std::sync::mpsc::Receiver, 459 | watched_path: Option, 460 | } 461 | 462 | impl ShaderFilesWatcher { 463 | fn new(delay: std::time::Duration) -> Result { 464 | let (tx, receiver) = std::sync::mpsc::channel(); 465 | let watcher = notify::watcher(tx, delay)?; 466 | let mut files_watcher = ShaderFilesWatcher { 467 | watcher, 468 | receiver, 469 | watched_path: None, 470 | }; 471 | files_watcher.start_watching(&*shader_compiler::SHADER_PRELUDE_PATH)?; 472 | files_watcher.start_watching(&*shader_compiler::SHADER_INTERFACE_PATH)?; 473 | Ok(files_watcher) 474 | } 475 | 476 | fn watch_directory(&mut self, path: &std::path::Path) -> Result<()> { 477 | use notify::Watcher; 478 | if let Some(watched_path) = &self.watched_path { 479 | let _ = self.watcher.unwatch(watched_path); 480 | } 481 | self.start_watching(path.parent().unwrap())?; 482 | self.watched_path = Some(path.to_owned()); 483 | Ok(()) 484 | } 485 | 486 | fn has_changes(&self) -> bool { 487 | self.receiver.try_recv().is_ok() 488 | } 489 | 490 | fn start_watching(&mut self, path: &std::path::Path) -> Result<()> { 491 | use notify::Watcher; 492 | self.watcher 493 | .watch(path, notify::RecursiveMode::Recursive) 494 | .with_context(|| format!("Failed to watch path {:?}", path))?; 495 | Ok(()) 496 | } 497 | } 498 | 499 | struct FrameRateReporter { 500 | frame_start_time: std::time::Instant, 501 | frame_rate_in_frames_per_sec: f64, 502 | rate_limiter: RateLimiter, 503 | } 504 | 505 | impl FrameRateReporter { 506 | fn new() -> Self { 507 | Self { 508 | frame_start_time: std::time::Instant::now(), 509 | frame_rate_in_frames_per_sec: 30.0, // initial guess 510 | rate_limiter: RateLimiter::new(std::time::Duration::from_secs(1)), 511 | } 512 | } 513 | 514 | fn calculate_frame_rate_and_maybe_report(&mut self, window: &winit::window::Window) { 515 | let frame_duration = self.frame_start_time.elapsed().as_millis().max(1) as f64; 516 | let num_frames_averaged = 10.0; 517 | self.frame_rate_in_frames_per_sec += 518 | (1000.0 / frame_duration - self.frame_rate_in_frames_per_sec) / num_frames_averaged; 519 | let frame_rate_in_frames_per_sec = self.frame_rate_in_frames_per_sec; 520 | self.rate_limiter.maybe_call(|| { 521 | window.set_title(&format!( 522 | "ShaderRoy [FPS: {:.0}]", 523 | frame_rate_in_frames_per_sec 524 | )); 525 | use std::io::Write; 526 | let _ = std::io::stdout().lock().flush(); 527 | }); 528 | self.frame_start_time = std::time::Instant::now(); 529 | } 530 | } 531 | 532 | struct RateLimiter { 533 | delay: std::time::Duration, 534 | last_call_time: std::time::Instant, 535 | } 536 | 537 | impl RateLimiter { 538 | fn new(delay: std::time::Duration) -> Self { 539 | Self { 540 | delay, 541 | last_call_time: std::time::Instant::now(), 542 | } 543 | } 544 | 545 | fn maybe_call(&mut self, func: F) { 546 | if self.last_call_time.elapsed() > self.delay { 547 | func(); 548 | self.last_call_time = std::time::Instant::now(); 549 | } 550 | } 551 | } 552 | 553 | #[test] 554 | fn test() -> Result<()> { 555 | let examples_dir = shader_compiler::ROOT_PATH.join("../examples"); 556 | for example in std::fs::read_dir(examples_dir)? { 557 | let example_name = example?.file_name().into_string().unwrap(); 558 | let path = shader_file_path_arg::get_path_for_argument(&example_name)?; 559 | shader_compiler::compile_shader( 560 | &path, 561 | &metal::Device::system_default().unwrap(), 562 | |shader_in_msl| println!("{:?}\n\n{}", &path, shader_in_msl), 563 | )?; 564 | } 565 | Ok(()) 566 | } 567 | -------------------------------------------------------------------------------- /shader_roy_metal_sl_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shader_roy_metal_sl_interface" 3 | version = "0.1.0" 4 | authors = ["Michal Srb "] 5 | edition = "2018" 6 | 7 | [lib] 8 | path = "src/shader_roy_metal_sl_interface.rs" 9 | 10 | [dependencies] 11 | metal_sl_prelude = { path="../metal_sl_prelude" } 12 | 13 | -------------------------------------------------------------------------------- /shader_roy_metal_sl_interface/src/shader_roy_metal_sl_interface.rs: -------------------------------------------------------------------------------- 1 | pub use metal_sl_prelude::*; 2 | 3 | /// This is the `constant` information passed to the pixel shader from ShaderRoy on each frame. 4 | #[derive(Copy, Clone)] 5 | pub struct Input { 6 | /// Window size in physical units (at the native resolution of the display device). 7 | pub window_size: Vec2, 8 | /// Window's top left corner position in physical units. 9 | pub window_position: Vec2, 10 | /// The cursor left (x) and top (y) position in physical units. 11 | pub cursor_position: Vec2, 12 | /// Whether the cursor is inside the window and the window is focused. 13 | /// 14 | /// Defaults to false until the cursor moves inside or enters the window. 15 | pub is_cursor_inside_window: bool, 16 | /// Time since starting the program, in fractions of seconds. 17 | pub elapsed_time_secs: f32, 18 | /// Time since the rendering of the previous frame. 19 | pub elapsed_time_since_last_frame_secs: f32, 20 | /// Number of frames rendered so far, starting with 1. 21 | pub frame_count: f32, 22 | /// Local calendar year, month, day. tz is UTC timezone offset in secs. 23 | pub year_month_day_tz: Vec4, 24 | } 25 | 26 | // The values here are dummy values for type checking, real values are passed at runtime. 27 | pub const INPUT: Input = Input { 28 | window_size: Vec2 { x: 0.0, y: 0.0 }, 29 | window_position: Vec2 { x: 0.0, y: 0.0 }, 30 | cursor_position: Vec2 { x: 0.0, y: 0.0 }, 31 | is_cursor_inside_window: false, 32 | elapsed_time_secs: 0.0, 33 | elapsed_time_since_last_frame_secs: 0.0, 34 | frame_count: 0.0, 35 | year_month_day_tz: Vec4 { 36 | x: 1970.0, 37 | y: 12.0, 38 | z: 24.0, 39 | w: 1.0, 40 | }, 41 | }; 42 | --------------------------------------------------------------------------------