├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── LISP.md ├── README.md ├── demos ├── base │ ├── shader.frag │ └── shader.graph ├── curl_noise │ ├── curl_noise.frag │ ├── shader.graph │ └── simplex_noise.frag ├── gravity │ ├── add.frag │ ├── channel.frag │ ├── grid.frag │ ├── mass.frag │ ├── shader.graph │ └── wander.frag ├── life │ ├── color.frag │ ├── life.frag │ ├── life.gif │ └── shader.graph └── raymarcher │ ├── avg.frag │ ├── denoise.frag │ ├── render.frag │ ├── scale.frag │ ├── shader.graph │ └── sobel.frag ├── rustfmt.toml └── src ├── graph ├── compute_node.rs ├── mod.rs ├── node.rs ├── shader_node.rs └── uniform.rs ├── input.rs ├── lib.rs ├── lisp ├── env.rs ├── load.rs ├── mod.rs └── val.rs ├── main.rs ├── map.rs ├── png.rs ├── reload ├── mod.rs ├── shader_dir.rs └── watcher.rs └── util ├── mod.rs ├── texture.frag └── texture.vert /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /out 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ab_glyph_rasterizer" 7 | version = "0.1.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9fe5e32de01730eb1f6b7f5b51c17e03e2325bf40a74f754f04f130043affff" 10 | 11 | [[package]] 12 | name = "addr2line" 13 | version = "0.16.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" 16 | dependencies = [ 17 | "gimli", 18 | ] 19 | 20 | [[package]] 21 | name = "adler" 22 | version = "1.0.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 25 | 26 | [[package]] 27 | name = "adler32" 28 | version = "1.2.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 31 | 32 | [[package]] 33 | name = "andrew" 34 | version = "0.3.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "8c4afb09dd642feec8408e33f92f3ffc4052946f6b20f32fb99c1f58cd4fa7cf" 37 | dependencies = [ 38 | "bitflags", 39 | "rusttype", 40 | "walkdir", 41 | "xdg", 42 | "xml-rs", 43 | ] 44 | 45 | [[package]] 46 | name = "android_glue" 47 | version = "0.2.3" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" 50 | 51 | [[package]] 52 | name = "ansi_term" 53 | version = "0.11.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 56 | dependencies = [ 57 | "winapi", 58 | ] 59 | 60 | [[package]] 61 | name = "anyhow" 62 | version = "1.0.43" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" 65 | 66 | [[package]] 67 | name = "atty" 68 | version = "0.2.14" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 71 | dependencies = [ 72 | "hermit-abi", 73 | "libc", 74 | "winapi", 75 | ] 76 | 77 | [[package]] 78 | name = "autocfg" 79 | version = "1.0.1" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 82 | 83 | [[package]] 84 | name = "backtrace" 85 | version = "0.3.61" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" 88 | dependencies = [ 89 | "addr2line", 90 | "cc", 91 | "cfg-if 1.0.0", 92 | "libc", 93 | "miniz_oxide 0.4.4", 94 | "object", 95 | "rustc-demangle", 96 | ] 97 | 98 | [[package]] 99 | name = "bindgen" 100 | version = "0.54.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36" 103 | dependencies = [ 104 | "bitflags", 105 | "cexpr", 106 | "cfg-if 0.1.10", 107 | "clang-sys", 108 | "lazy_static", 109 | "lazycell", 110 | "peeking_take_while", 111 | "proc-macro2", 112 | "quote", 113 | "regex", 114 | "rustc-hash", 115 | "shlex", 116 | ] 117 | 118 | [[package]] 119 | name = "bitflags" 120 | version = "1.3.2" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 123 | 124 | [[package]] 125 | name = "block" 126 | version = "0.1.6" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 129 | 130 | [[package]] 131 | name = "bytemuck" 132 | version = "1.7.2" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" 135 | 136 | [[package]] 137 | name = "byteorder" 138 | version = "1.4.3" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 141 | 142 | [[package]] 143 | name = "calloop" 144 | version = "0.6.5" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "0b036167e76041694579972c28cf4877b4f92da222560ddb49008937b6a6727c" 147 | dependencies = [ 148 | "log", 149 | "nix 0.18.0", 150 | ] 151 | 152 | [[package]] 153 | name = "cc" 154 | version = "1.0.69" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" 157 | 158 | [[package]] 159 | name = "cexpr" 160 | version = "0.4.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" 163 | dependencies = [ 164 | "nom 5.1.2", 165 | ] 166 | 167 | [[package]] 168 | name = "cfg-if" 169 | version = "0.1.10" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 172 | 173 | [[package]] 174 | name = "cfg-if" 175 | version = "1.0.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 178 | 179 | [[package]] 180 | name = "cgl" 181 | version = "0.3.2" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" 184 | dependencies = [ 185 | "libc", 186 | ] 187 | 188 | [[package]] 189 | name = "clang-sys" 190 | version = "0.29.3" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" 193 | dependencies = [ 194 | "glob", 195 | "libc", 196 | "libloading 0.5.2", 197 | ] 198 | 199 | [[package]] 200 | name = "clap" 201 | version = "2.33.3" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 204 | dependencies = [ 205 | "ansi_term", 206 | "atty", 207 | "bitflags", 208 | "strsim 0.8.0", 209 | "textwrap", 210 | "unicode-width", 211 | "vec_map", 212 | ] 213 | 214 | [[package]] 215 | name = "cocoa" 216 | version = "0.24.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" 219 | dependencies = [ 220 | "bitflags", 221 | "block", 222 | "cocoa-foundation", 223 | "core-foundation 0.9.1", 224 | "core-graphics 0.22.2", 225 | "foreign-types", 226 | "libc", 227 | "objc", 228 | ] 229 | 230 | [[package]] 231 | name = "cocoa-foundation" 232 | version = "0.1.0" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" 235 | dependencies = [ 236 | "bitflags", 237 | "block", 238 | "core-foundation 0.9.1", 239 | "core-graphics-types", 240 | "foreign-types", 241 | "libc", 242 | "objc", 243 | ] 244 | 245 | [[package]] 246 | name = "color_quant" 247 | version = "1.1.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 250 | 251 | [[package]] 252 | name = "core-foundation" 253 | version = "0.7.0" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" 256 | dependencies = [ 257 | "core-foundation-sys 0.7.0", 258 | "libc", 259 | ] 260 | 261 | [[package]] 262 | name = "core-foundation" 263 | version = "0.9.1" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" 266 | dependencies = [ 267 | "core-foundation-sys 0.8.2", 268 | "libc", 269 | ] 270 | 271 | [[package]] 272 | name = "core-foundation-sys" 273 | version = "0.7.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" 276 | 277 | [[package]] 278 | name = "core-foundation-sys" 279 | version = "0.8.2" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" 282 | 283 | [[package]] 284 | name = "core-graphics" 285 | version = "0.19.2" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" 288 | dependencies = [ 289 | "bitflags", 290 | "core-foundation 0.7.0", 291 | "foreign-types", 292 | "libc", 293 | ] 294 | 295 | [[package]] 296 | name = "core-graphics" 297 | version = "0.22.2" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86" 300 | dependencies = [ 301 | "bitflags", 302 | "core-foundation 0.9.1", 303 | "core-graphics-types", 304 | "foreign-types", 305 | "libc", 306 | ] 307 | 308 | [[package]] 309 | name = "core-graphics-types" 310 | version = "0.1.1" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" 313 | dependencies = [ 314 | "bitflags", 315 | "core-foundation 0.9.1", 316 | "foreign-types", 317 | "libc", 318 | ] 319 | 320 | [[package]] 321 | name = "core-video-sys" 322 | version = "0.1.4" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" 325 | dependencies = [ 326 | "cfg-if 0.1.10", 327 | "core-foundation-sys 0.7.0", 328 | "core-graphics 0.19.2", 329 | "libc", 330 | "objc", 331 | ] 332 | 333 | [[package]] 334 | name = "crc32fast" 335 | version = "1.2.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 338 | dependencies = [ 339 | "cfg-if 1.0.0", 340 | ] 341 | 342 | [[package]] 343 | name = "crossbeam" 344 | version = "0.8.1" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" 347 | dependencies = [ 348 | "cfg-if 1.0.0", 349 | "crossbeam-channel", 350 | "crossbeam-deque", 351 | "crossbeam-epoch", 352 | "crossbeam-queue", 353 | "crossbeam-utils", 354 | ] 355 | 356 | [[package]] 357 | name = "crossbeam-channel" 358 | version = "0.5.1" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" 361 | dependencies = [ 362 | "cfg-if 1.0.0", 363 | "crossbeam-utils", 364 | ] 365 | 366 | [[package]] 367 | name = "crossbeam-deque" 368 | version = "0.8.1" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" 371 | dependencies = [ 372 | "cfg-if 1.0.0", 373 | "crossbeam-epoch", 374 | "crossbeam-utils", 375 | ] 376 | 377 | [[package]] 378 | name = "crossbeam-epoch" 379 | version = "0.9.5" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" 382 | dependencies = [ 383 | "cfg-if 1.0.0", 384 | "crossbeam-utils", 385 | "lazy_static", 386 | "memoffset", 387 | "scopeguard", 388 | ] 389 | 390 | [[package]] 391 | name = "crossbeam-queue" 392 | version = "0.3.2" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" 395 | dependencies = [ 396 | "cfg-if 1.0.0", 397 | "crossbeam-utils", 398 | ] 399 | 400 | [[package]] 401 | name = "crossbeam-utils" 402 | version = "0.8.5" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" 405 | dependencies = [ 406 | "cfg-if 1.0.0", 407 | "lazy_static", 408 | ] 409 | 410 | [[package]] 411 | name = "darling" 412 | version = "0.10.2" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" 415 | dependencies = [ 416 | "darling_core", 417 | "darling_macro", 418 | ] 419 | 420 | [[package]] 421 | name = "darling_core" 422 | version = "0.10.2" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" 425 | dependencies = [ 426 | "fnv", 427 | "ident_case", 428 | "proc-macro2", 429 | "quote", 430 | "strsim 0.9.3", 431 | "syn", 432 | ] 433 | 434 | [[package]] 435 | name = "darling_macro" 436 | version = "0.10.2" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" 439 | dependencies = [ 440 | "darling_core", 441 | "quote", 442 | "syn", 443 | ] 444 | 445 | [[package]] 446 | name = "deflate" 447 | version = "0.8.6" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" 450 | dependencies = [ 451 | "adler32", 452 | "byteorder", 453 | ] 454 | 455 | [[package]] 456 | name = "derivative" 457 | version = "2.2.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 460 | dependencies = [ 461 | "proc-macro2", 462 | "quote", 463 | "syn", 464 | ] 465 | 466 | [[package]] 467 | name = "dispatch" 468 | version = "0.2.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" 471 | 472 | [[package]] 473 | name = "dlib" 474 | version = "0.4.2" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76" 477 | dependencies = [ 478 | "libloading 0.6.7", 479 | ] 480 | 481 | [[package]] 482 | name = "dlib" 483 | version = "0.5.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" 486 | dependencies = [ 487 | "libloading 0.7.0", 488 | ] 489 | 490 | [[package]] 491 | name = "downcast-rs" 492 | version = "1.2.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 495 | 496 | [[package]] 497 | name = "either" 498 | version = "1.6.1" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 501 | 502 | [[package]] 503 | name = "ffmpeg-next" 504 | version = "4.4.0" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "4676cda947a87a1e8a42e154059c567e75de64860252cce52c684acd8c074fa0" 507 | dependencies = [ 508 | "bitflags", 509 | "ffmpeg-sys-next", 510 | "libc", 511 | ] 512 | 513 | [[package]] 514 | name = "ffmpeg-sys-next" 515 | version = "4.4.0" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "de57234f2c49c6e093fe67bbbaa9142c228f6e2d5533ef27980993d5b6adef2a" 518 | dependencies = [ 519 | "bindgen", 520 | "cc", 521 | "libc", 522 | "num_cpus", 523 | "pkg-config", 524 | "vcpkg", 525 | ] 526 | 527 | [[package]] 528 | name = "filetime" 529 | version = "0.2.15" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" 532 | dependencies = [ 533 | "cfg-if 1.0.0", 534 | "libc", 535 | "redox_syscall", 536 | "winapi", 537 | ] 538 | 539 | [[package]] 540 | name = "fnv" 541 | version = "1.0.7" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 544 | 545 | [[package]] 546 | name = "foreign-types" 547 | version = "0.3.2" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 550 | dependencies = [ 551 | "foreign-types-shared", 552 | ] 553 | 554 | [[package]] 555 | name = "foreign-types-shared" 556 | version = "0.1.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 559 | 560 | [[package]] 561 | name = "fsevent-sys" 562 | version = "4.0.0" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "5c0e564d24da983c053beff1bb7178e237501206840a3e6bf4e267b9e8ae734a" 565 | dependencies = [ 566 | "libc", 567 | ] 568 | 569 | [[package]] 570 | name = "gif" 571 | version = "0.11.2" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de" 574 | dependencies = [ 575 | "color_quant", 576 | "weezl", 577 | ] 578 | 579 | [[package]] 580 | name = "gimli" 581 | version = "0.25.0" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" 584 | 585 | [[package]] 586 | name = "gl_generator" 587 | version = "0.14.0" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" 590 | dependencies = [ 591 | "khronos_api", 592 | "log", 593 | "xml-rs", 594 | ] 595 | 596 | [[package]] 597 | name = "glium" 598 | version = "0.30.2" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "506a2aa1564891d447ae5d1ba37519a8efd6d01ea3e7952da81aa30430c90007" 601 | dependencies = [ 602 | "backtrace", 603 | "fnv", 604 | "gl_generator", 605 | "glutin", 606 | "lazy_static", 607 | "memoffset", 608 | "smallvec", 609 | "takeable-option", 610 | ] 611 | 612 | [[package]] 613 | name = "glob" 614 | version = "0.3.0" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 617 | 618 | [[package]] 619 | name = "glutin" 620 | version = "0.27.0" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "762d6cd2e1b855d99668ebe591cc9058659d85ac39a9a2078000eb122ddba8f0" 623 | dependencies = [ 624 | "android_glue", 625 | "cgl", 626 | "cocoa", 627 | "core-foundation 0.9.1", 628 | "glutin_egl_sys", 629 | "glutin_emscripten_sys", 630 | "glutin_gles2_sys", 631 | "glutin_glx_sys", 632 | "glutin_wgl_sys", 633 | "lazy_static", 634 | "libloading 0.7.0", 635 | "log", 636 | "objc", 637 | "osmesa-sys", 638 | "parking_lot", 639 | "wayland-client", 640 | "wayland-egl", 641 | "winapi", 642 | "winit", 643 | ] 644 | 645 | [[package]] 646 | name = "glutin_egl_sys" 647 | version = "0.1.5" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211" 650 | dependencies = [ 651 | "gl_generator", 652 | "winapi", 653 | ] 654 | 655 | [[package]] 656 | name = "glutin_emscripten_sys" 657 | version = "0.1.1" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1" 660 | 661 | [[package]] 662 | name = "glutin_gles2_sys" 663 | version = "0.1.5" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103" 666 | dependencies = [ 667 | "gl_generator", 668 | "objc", 669 | ] 670 | 671 | [[package]] 672 | name = "glutin_glx_sys" 673 | version = "0.1.7" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351" 676 | dependencies = [ 677 | "gl_generator", 678 | "x11-dl", 679 | ] 680 | 681 | [[package]] 682 | name = "glutin_wgl_sys" 683 | version = "0.1.5" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696" 686 | dependencies = [ 687 | "gl_generator", 688 | ] 689 | 690 | [[package]] 691 | name = "heck" 692 | version = "0.3.3" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 695 | dependencies = [ 696 | "unicode-segmentation", 697 | ] 698 | 699 | [[package]] 700 | name = "hermit-abi" 701 | version = "0.1.19" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 704 | dependencies = [ 705 | "libc", 706 | ] 707 | 708 | [[package]] 709 | name = "ident_case" 710 | version = "1.0.1" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 713 | 714 | [[package]] 715 | name = "image" 716 | version = "0.23.14" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" 719 | dependencies = [ 720 | "bytemuck", 721 | "byteorder", 722 | "color_quant", 723 | "gif", 724 | "jpeg-decoder", 725 | "num-iter", 726 | "num-rational", 727 | "num-traits", 728 | "png", 729 | "scoped_threadpool", 730 | "tiff", 731 | ] 732 | 733 | [[package]] 734 | name = "include_dir" 735 | version = "0.6.1" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "31a924bd335356c7622dff9ee33d06920afcf7f762a1a991236645e08c8a484b" 738 | dependencies = [ 739 | "glob", 740 | "include_dir_impl", 741 | "proc-macro-hack", 742 | ] 743 | 744 | [[package]] 745 | name = "include_dir_impl" 746 | version = "0.6.1" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "afae3917f781921d7c7813992ccadff7e816f7e6ecb4b70a9ec3e740d51da3d6" 749 | dependencies = [ 750 | "anyhow", 751 | "proc-macro-hack", 752 | "proc-macro2", 753 | "quote", 754 | "syn", 755 | ] 756 | 757 | [[package]] 758 | name = "inotify" 759 | version = "0.9.3" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "b031475cb1b103ee221afb806a23d35e0570bf7271d7588762ceba8127ed43b3" 762 | dependencies = [ 763 | "bitflags", 764 | "inotify-sys", 765 | "libc", 766 | ] 767 | 768 | [[package]] 769 | name = "inotify-sys" 770 | version = "0.1.5" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 773 | dependencies = [ 774 | "libc", 775 | ] 776 | 777 | [[package]] 778 | name = "instant" 779 | version = "0.1.10" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" 782 | dependencies = [ 783 | "cfg-if 1.0.0", 784 | ] 785 | 786 | [[package]] 787 | name = "itoa" 788 | version = "0.4.8" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 791 | 792 | [[package]] 793 | name = "jni-sys" 794 | version = "0.3.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 797 | 798 | [[package]] 799 | name = "jpeg-decoder" 800 | version = "0.1.22" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" 803 | dependencies = [ 804 | "rayon", 805 | ] 806 | 807 | [[package]] 808 | name = "khronos_api" 809 | version = "3.1.0" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" 812 | 813 | [[package]] 814 | name = "kqueue" 815 | version = "1.0.4" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "058a107a784f8be94c7d35c1300f4facced2e93d2fbe5b1452b44e905ddca4a9" 818 | dependencies = [ 819 | "kqueue-sys", 820 | "libc", 821 | ] 822 | 823 | [[package]] 824 | name = "kqueue-sys" 825 | version = "1.0.3" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" 828 | dependencies = [ 829 | "bitflags", 830 | "libc", 831 | ] 832 | 833 | [[package]] 834 | name = "lazy_static" 835 | version = "1.4.0" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 838 | 839 | [[package]] 840 | name = "lazycell" 841 | version = "1.3.0" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 844 | 845 | [[package]] 846 | name = "lexpr" 847 | version = "0.2.6" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "ceee0b80e0043f17bf81130471e1b0975179af75fe657af45577d80e2698fe3b" 850 | dependencies = [ 851 | "itoa", 852 | "lexpr-macros", 853 | "proc-macro-hack", 854 | "ryu", 855 | ] 856 | 857 | [[package]] 858 | name = "lexpr-macros" 859 | version = "0.2.1" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "cd627fb38e19c00d8d068618259205f7a91c91aeade5c15bc35dbca037bb1c35" 862 | dependencies = [ 863 | "proc-macro-hack", 864 | "proc-macro2", 865 | "quote", 866 | ] 867 | 868 | [[package]] 869 | name = "libc" 870 | version = "0.2.100" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" 873 | 874 | [[package]] 875 | name = "libloading" 876 | version = "0.5.2" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" 879 | dependencies = [ 880 | "cc", 881 | "winapi", 882 | ] 883 | 884 | [[package]] 885 | name = "libloading" 886 | version = "0.6.7" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" 889 | dependencies = [ 890 | "cfg-if 1.0.0", 891 | "winapi", 892 | ] 893 | 894 | [[package]] 895 | name = "libloading" 896 | version = "0.7.0" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" 899 | dependencies = [ 900 | "cfg-if 1.0.0", 901 | "winapi", 902 | ] 903 | 904 | [[package]] 905 | name = "lock_api" 906 | version = "0.4.4" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" 909 | dependencies = [ 910 | "scopeguard", 911 | ] 912 | 913 | [[package]] 914 | name = "log" 915 | version = "0.4.14" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 918 | dependencies = [ 919 | "cfg-if 1.0.0", 920 | ] 921 | 922 | [[package]] 923 | name = "malloc_buf" 924 | version = "0.0.6" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 927 | dependencies = [ 928 | "libc", 929 | ] 930 | 931 | [[package]] 932 | name = "maybe-uninit" 933 | version = "2.0.0" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 936 | 937 | [[package]] 938 | name = "memchr" 939 | version = "2.4.1" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 942 | 943 | [[package]] 944 | name = "memmap2" 945 | version = "0.1.0" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "d9b70ca2a6103ac8b665dc150b142ef0e4e89df640c9e6cf295d189c3caebe5a" 948 | dependencies = [ 949 | "libc", 950 | ] 951 | 952 | [[package]] 953 | name = "memoffset" 954 | version = "0.6.4" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" 957 | dependencies = [ 958 | "autocfg", 959 | ] 960 | 961 | [[package]] 962 | name = "minimal-lexical" 963 | version = "0.1.2" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "6595bb28ed34f43c3fe088e48f6cfb2e033cab45f25a5384d5fdf564fbc8c4b2" 966 | 967 | [[package]] 968 | name = "miniz_oxide" 969 | version = "0.3.7" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 972 | dependencies = [ 973 | "adler32", 974 | ] 975 | 976 | [[package]] 977 | name = "miniz_oxide" 978 | version = "0.4.4" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 981 | dependencies = [ 982 | "adler", 983 | "autocfg", 984 | ] 985 | 986 | [[package]] 987 | name = "mio" 988 | version = "0.7.13" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" 991 | dependencies = [ 992 | "libc", 993 | "log", 994 | "miow", 995 | "ntapi", 996 | "winapi", 997 | ] 998 | 999 | [[package]] 1000 | name = "mio-misc" 1001 | version = "1.2.1" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "0ddf05411bb159cdb5801bb10002afb66cb4572be656044315e363460ce69dc2" 1004 | dependencies = [ 1005 | "crossbeam", 1006 | "crossbeam-queue", 1007 | "log", 1008 | "mio", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "miow" 1013 | version = "0.3.7" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 1016 | dependencies = [ 1017 | "winapi", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "ndk" 1022 | version = "0.3.0" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab" 1025 | dependencies = [ 1026 | "jni-sys", 1027 | "ndk-sys", 1028 | "num_enum", 1029 | "thiserror", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "ndk-glue" 1034 | version = "0.3.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "c5caf0c24d51ac1c905c27d4eda4fa0635bbe0de596b8f79235e0b17a4d29385" 1037 | dependencies = [ 1038 | "lazy_static", 1039 | "libc", 1040 | "log", 1041 | "ndk", 1042 | "ndk-macro", 1043 | "ndk-sys", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "ndk-macro" 1048 | version = "0.2.0" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" 1051 | dependencies = [ 1052 | "darling", 1053 | "proc-macro-crate 0.1.5", 1054 | "proc-macro2", 1055 | "quote", 1056 | "syn", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "ndk-sys" 1061 | version = "0.2.1" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" 1064 | 1065 | [[package]] 1066 | name = "nix" 1067 | version = "0.18.0" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055" 1070 | dependencies = [ 1071 | "bitflags", 1072 | "cc", 1073 | "cfg-if 0.1.10", 1074 | "libc", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "nix" 1079 | version = "0.20.0" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" 1082 | dependencies = [ 1083 | "bitflags", 1084 | "cc", 1085 | "cfg-if 1.0.0", 1086 | "libc", 1087 | ] 1088 | 1089 | [[package]] 1090 | name = "nom" 1091 | version = "5.1.2" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 1094 | dependencies = [ 1095 | "memchr", 1096 | "version_check", 1097 | ] 1098 | 1099 | [[package]] 1100 | name = "nom" 1101 | version = "7.0.0" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1" 1104 | dependencies = [ 1105 | "memchr", 1106 | "minimal-lexical", 1107 | "version_check", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "notify" 1112 | version = "5.0.0-pre.12" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "20a629259bb2c87a884bb76f6086c8637919de6d074754341c12e5dd3aed6326" 1115 | dependencies = [ 1116 | "bitflags", 1117 | "crossbeam-channel", 1118 | "filetime", 1119 | "fsevent-sys", 1120 | "inotify", 1121 | "kqueue", 1122 | "libc", 1123 | "mio", 1124 | "walkdir", 1125 | "winapi", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "ntapi" 1130 | version = "0.3.6" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 1133 | dependencies = [ 1134 | "winapi", 1135 | ] 1136 | 1137 | [[package]] 1138 | name = "num-integer" 1139 | version = "0.1.44" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 1142 | dependencies = [ 1143 | "autocfg", 1144 | "num-traits", 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "num-iter" 1149 | version = "0.1.42" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" 1152 | dependencies = [ 1153 | "autocfg", 1154 | "num-integer", 1155 | "num-traits", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "num-rational" 1160 | version = "0.3.2" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" 1163 | dependencies = [ 1164 | "autocfg", 1165 | "num-integer", 1166 | "num-traits", 1167 | ] 1168 | 1169 | [[package]] 1170 | name = "num-traits" 1171 | version = "0.2.14" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 1174 | dependencies = [ 1175 | "autocfg", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "num_cpus" 1180 | version = "1.13.0" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 1183 | dependencies = [ 1184 | "hermit-abi", 1185 | "libc", 1186 | ] 1187 | 1188 | [[package]] 1189 | name = "num_enum" 1190 | version = "0.5.4" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" 1193 | dependencies = [ 1194 | "derivative", 1195 | "num_enum_derive", 1196 | ] 1197 | 1198 | [[package]] 1199 | name = "num_enum_derive" 1200 | version = "0.5.4" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" 1203 | dependencies = [ 1204 | "proc-macro-crate 1.0.0", 1205 | "proc-macro2", 1206 | "quote", 1207 | "syn", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "objc" 1212 | version = "0.2.7" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 1215 | dependencies = [ 1216 | "malloc_buf", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "object" 1221 | version = "0.26.1" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "ee2766204889d09937d00bfbb7fec56bb2a199e2ade963cab19185d8a6104c7c" 1224 | dependencies = [ 1225 | "memchr", 1226 | ] 1227 | 1228 | [[package]] 1229 | name = "once_cell" 1230 | version = "1.8.0" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 1233 | 1234 | [[package]] 1235 | name = "osmesa-sys" 1236 | version = "0.1.2" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" 1239 | dependencies = [ 1240 | "shared_library", 1241 | ] 1242 | 1243 | [[package]] 1244 | name = "owned_ttf_parser" 1245 | version = "0.6.0" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" 1248 | dependencies = [ 1249 | "ttf-parser", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "parking_lot" 1254 | version = "0.11.1" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 1257 | dependencies = [ 1258 | "instant", 1259 | "lock_api", 1260 | "parking_lot_core", 1261 | ] 1262 | 1263 | [[package]] 1264 | name = "parking_lot_core" 1265 | version = "0.8.3" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 1268 | dependencies = [ 1269 | "cfg-if 1.0.0", 1270 | "instant", 1271 | "libc", 1272 | "redox_syscall", 1273 | "smallvec", 1274 | "winapi", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "peeking_take_while" 1279 | version = "0.1.2" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 1282 | 1283 | [[package]] 1284 | name = "percent-encoding" 1285 | version = "2.1.0" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1288 | 1289 | [[package]] 1290 | name = "pkg-config" 1291 | version = "0.3.19" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 1294 | 1295 | [[package]] 1296 | name = "png" 1297 | version = "0.16.8" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" 1300 | dependencies = [ 1301 | "bitflags", 1302 | "crc32fast", 1303 | "deflate", 1304 | "miniz_oxide 0.3.7", 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "proc-macro-crate" 1309 | version = "0.1.5" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" 1312 | dependencies = [ 1313 | "toml", 1314 | ] 1315 | 1316 | [[package]] 1317 | name = "proc-macro-crate" 1318 | version = "1.0.0" 1319 | source = "registry+https://github.com/rust-lang/crates.io-index" 1320 | checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" 1321 | dependencies = [ 1322 | "thiserror", 1323 | "toml", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "proc-macro-error" 1328 | version = "1.0.4" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1331 | dependencies = [ 1332 | "proc-macro-error-attr", 1333 | "proc-macro2", 1334 | "quote", 1335 | "syn", 1336 | "version_check", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "proc-macro-error-attr" 1341 | version = "1.0.4" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1344 | dependencies = [ 1345 | "proc-macro2", 1346 | "quote", 1347 | "version_check", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "proc-macro-hack" 1352 | version = "0.5.19" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 1355 | 1356 | [[package]] 1357 | name = "proc-macro2" 1358 | version = "1.0.28" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" 1361 | dependencies = [ 1362 | "unicode-xid", 1363 | ] 1364 | 1365 | [[package]] 1366 | name = "quote" 1367 | version = "1.0.9" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 1370 | dependencies = [ 1371 | "proc-macro2", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "raw-window-handle" 1376 | version = "0.3.3" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" 1379 | dependencies = [ 1380 | "libc", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "rayon" 1385 | version = "1.5.1" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" 1388 | dependencies = [ 1389 | "autocfg", 1390 | "crossbeam-deque", 1391 | "either", 1392 | "rayon-core", 1393 | ] 1394 | 1395 | [[package]] 1396 | name = "rayon-core" 1397 | version = "1.9.1" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" 1400 | dependencies = [ 1401 | "crossbeam-channel", 1402 | "crossbeam-deque", 1403 | "crossbeam-utils", 1404 | "lazy_static", 1405 | "num_cpus", 1406 | ] 1407 | 1408 | [[package]] 1409 | name = "redox_syscall" 1410 | version = "0.2.10" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 1413 | dependencies = [ 1414 | "bitflags", 1415 | ] 1416 | 1417 | [[package]] 1418 | name = "regex" 1419 | version = "1.5.4" 1420 | source = "registry+https://github.com/rust-lang/crates.io-index" 1421 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 1422 | dependencies = [ 1423 | "regex-syntax", 1424 | ] 1425 | 1426 | [[package]] 1427 | name = "regex-syntax" 1428 | version = "0.6.25" 1429 | source = "registry+https://github.com/rust-lang/crates.io-index" 1430 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 1431 | 1432 | [[package]] 1433 | name = "rustc-demangle" 1434 | version = "0.1.21" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" 1437 | 1438 | [[package]] 1439 | name = "rustc-hash" 1440 | version = "1.1.0" 1441 | source = "registry+https://github.com/rust-lang/crates.io-index" 1442 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1443 | 1444 | [[package]] 1445 | name = "rusttype" 1446 | version = "0.9.2" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59" 1449 | dependencies = [ 1450 | "ab_glyph_rasterizer", 1451 | "owned_ttf_parser", 1452 | ] 1453 | 1454 | [[package]] 1455 | name = "ryu" 1456 | version = "1.0.5" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 1459 | 1460 | [[package]] 1461 | name = "same-file" 1462 | version = "1.0.6" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1465 | dependencies = [ 1466 | "winapi-util", 1467 | ] 1468 | 1469 | [[package]] 1470 | name = "scoped-tls" 1471 | version = "1.0.0" 1472 | source = "registry+https://github.com/rust-lang/crates.io-index" 1473 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 1474 | 1475 | [[package]] 1476 | name = "scoped_threadpool" 1477 | version = "0.1.9" 1478 | source = "registry+https://github.com/rust-lang/crates.io-index" 1479 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 1480 | 1481 | [[package]] 1482 | name = "scopeguard" 1483 | version = "1.1.0" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1486 | 1487 | [[package]] 1488 | name = "serde" 1489 | version = "1.0.129" 1490 | source = "registry+https://github.com/rust-lang/crates.io-index" 1491 | checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" 1492 | 1493 | [[package]] 1494 | name = "shadergarden" 1495 | version = "0.2.0" 1496 | dependencies = [ 1497 | "ffmpeg-next", 1498 | "glium", 1499 | "image", 1500 | "include_dir", 1501 | "lexpr", 1502 | "notify", 1503 | "structopt", 1504 | ] 1505 | 1506 | [[package]] 1507 | name = "shared_library" 1508 | version = "0.1.9" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" 1511 | dependencies = [ 1512 | "lazy_static", 1513 | "libc", 1514 | ] 1515 | 1516 | [[package]] 1517 | name = "shlex" 1518 | version = "0.1.1" 1519 | source = "registry+https://github.com/rust-lang/crates.io-index" 1520 | checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" 1521 | 1522 | [[package]] 1523 | name = "smallvec" 1524 | version = "1.6.1" 1525 | source = "registry+https://github.com/rust-lang/crates.io-index" 1526 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 1527 | 1528 | [[package]] 1529 | name = "smithay-client-toolkit" 1530 | version = "0.12.3" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "4750c76fd5d3ac95fa3ed80fe667d6a3d8590a960e5b575b98eea93339a80b80" 1533 | dependencies = [ 1534 | "andrew", 1535 | "bitflags", 1536 | "calloop", 1537 | "dlib 0.4.2", 1538 | "lazy_static", 1539 | "log", 1540 | "memmap2", 1541 | "nix 0.18.0", 1542 | "wayland-client", 1543 | "wayland-cursor", 1544 | "wayland-protocols", 1545 | ] 1546 | 1547 | [[package]] 1548 | name = "strsim" 1549 | version = "0.8.0" 1550 | source = "registry+https://github.com/rust-lang/crates.io-index" 1551 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1552 | 1553 | [[package]] 1554 | name = "strsim" 1555 | version = "0.9.3" 1556 | source = "registry+https://github.com/rust-lang/crates.io-index" 1557 | checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" 1558 | 1559 | [[package]] 1560 | name = "structopt" 1561 | version = "0.3.22" 1562 | source = "registry+https://github.com/rust-lang/crates.io-index" 1563 | checksum = "69b041cdcb67226aca307e6e7be44c8806423d83e018bd662360a93dabce4d71" 1564 | dependencies = [ 1565 | "clap", 1566 | "lazy_static", 1567 | "structopt-derive", 1568 | ] 1569 | 1570 | [[package]] 1571 | name = "structopt-derive" 1572 | version = "0.4.15" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "7813934aecf5f51a54775e00068c237de98489463968231a51746bbbc03f9c10" 1575 | dependencies = [ 1576 | "heck", 1577 | "proc-macro-error", 1578 | "proc-macro2", 1579 | "quote", 1580 | "syn", 1581 | ] 1582 | 1583 | [[package]] 1584 | name = "syn" 1585 | version = "1.0.75" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" 1588 | dependencies = [ 1589 | "proc-macro2", 1590 | "quote", 1591 | "unicode-xid", 1592 | ] 1593 | 1594 | [[package]] 1595 | name = "takeable-option" 1596 | version = "0.5.0" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "36ae8932fcfea38b7d3883ae2ab357b0d57a02caaa18ebb4f5ece08beaec4aa0" 1599 | 1600 | [[package]] 1601 | name = "textwrap" 1602 | version = "0.11.0" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1605 | dependencies = [ 1606 | "unicode-width", 1607 | ] 1608 | 1609 | [[package]] 1610 | name = "thiserror" 1611 | version = "1.0.26" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" 1614 | dependencies = [ 1615 | "thiserror-impl", 1616 | ] 1617 | 1618 | [[package]] 1619 | name = "thiserror-impl" 1620 | version = "1.0.26" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" 1623 | dependencies = [ 1624 | "proc-macro2", 1625 | "quote", 1626 | "syn", 1627 | ] 1628 | 1629 | [[package]] 1630 | name = "tiff" 1631 | version = "0.6.1" 1632 | source = "registry+https://github.com/rust-lang/crates.io-index" 1633 | checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" 1634 | dependencies = [ 1635 | "jpeg-decoder", 1636 | "miniz_oxide 0.4.4", 1637 | "weezl", 1638 | ] 1639 | 1640 | [[package]] 1641 | name = "toml" 1642 | version = "0.5.8" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 1645 | dependencies = [ 1646 | "serde", 1647 | ] 1648 | 1649 | [[package]] 1650 | name = "ttf-parser" 1651 | version = "0.6.2" 1652 | source = "registry+https://github.com/rust-lang/crates.io-index" 1653 | checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" 1654 | 1655 | [[package]] 1656 | name = "unicode-segmentation" 1657 | version = "1.8.0" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 1660 | 1661 | [[package]] 1662 | name = "unicode-width" 1663 | version = "0.1.8" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 1666 | 1667 | [[package]] 1668 | name = "unicode-xid" 1669 | version = "0.2.2" 1670 | source = "registry+https://github.com/rust-lang/crates.io-index" 1671 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1672 | 1673 | [[package]] 1674 | name = "vcpkg" 1675 | version = "0.2.15" 1676 | source = "registry+https://github.com/rust-lang/crates.io-index" 1677 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1678 | 1679 | [[package]] 1680 | name = "vec_map" 1681 | version = "0.8.2" 1682 | source = "registry+https://github.com/rust-lang/crates.io-index" 1683 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1684 | 1685 | [[package]] 1686 | name = "version_check" 1687 | version = "0.9.3" 1688 | source = "registry+https://github.com/rust-lang/crates.io-index" 1689 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1690 | 1691 | [[package]] 1692 | name = "walkdir" 1693 | version = "2.3.2" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 1696 | dependencies = [ 1697 | "same-file", 1698 | "winapi", 1699 | "winapi-util", 1700 | ] 1701 | 1702 | [[package]] 1703 | name = "wayland-client" 1704 | version = "0.28.6" 1705 | source = "registry+https://github.com/rust-lang/crates.io-index" 1706 | checksum = "e3ab332350e502f159382201394a78e3cc12d0f04db863429260164ea40e0355" 1707 | dependencies = [ 1708 | "bitflags", 1709 | "downcast-rs", 1710 | "libc", 1711 | "nix 0.20.0", 1712 | "scoped-tls", 1713 | "wayland-commons", 1714 | "wayland-scanner", 1715 | "wayland-sys", 1716 | ] 1717 | 1718 | [[package]] 1719 | name = "wayland-commons" 1720 | version = "0.28.6" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "a21817947c7011bbd0a27e11b17b337bfd022e8544b071a2641232047966fbda" 1723 | dependencies = [ 1724 | "nix 0.20.0", 1725 | "once_cell", 1726 | "smallvec", 1727 | "wayland-sys", 1728 | ] 1729 | 1730 | [[package]] 1731 | name = "wayland-cursor" 1732 | version = "0.28.6" 1733 | source = "registry+https://github.com/rust-lang/crates.io-index" 1734 | checksum = "be610084edd1586d45e7bdd275fe345c7c1873598caa464c4fb835dee70fa65a" 1735 | dependencies = [ 1736 | "nix 0.20.0", 1737 | "wayland-client", 1738 | "xcursor", 1739 | ] 1740 | 1741 | [[package]] 1742 | name = "wayland-egl" 1743 | version = "0.28.6" 1744 | source = "registry+https://github.com/rust-lang/crates.io-index" 1745 | checksum = "99ba1ab1e18756b23982d36f08856d521d7df45015f404a2d7c4f0b2d2f66956" 1746 | dependencies = [ 1747 | "wayland-client", 1748 | "wayland-sys", 1749 | ] 1750 | 1751 | [[package]] 1752 | name = "wayland-protocols" 1753 | version = "0.28.6" 1754 | source = "registry+https://github.com/rust-lang/crates.io-index" 1755 | checksum = "286620ea4d803bacf61fa087a4242ee316693099ee5a140796aaba02b29f861f" 1756 | dependencies = [ 1757 | "bitflags", 1758 | "wayland-client", 1759 | "wayland-commons", 1760 | "wayland-scanner", 1761 | ] 1762 | 1763 | [[package]] 1764 | name = "wayland-scanner" 1765 | version = "0.28.6" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "ce923eb2deb61de332d1f356ec7b6bf37094dc5573952e1c8936db03b54c03f1" 1768 | dependencies = [ 1769 | "proc-macro2", 1770 | "quote", 1771 | "xml-rs", 1772 | ] 1773 | 1774 | [[package]] 1775 | name = "wayland-sys" 1776 | version = "0.28.6" 1777 | source = "registry+https://github.com/rust-lang/crates.io-index" 1778 | checksum = "d841fca9aed7febf9bed2e9796c49bf58d4152ceda8ac949ebe00868d8f0feb8" 1779 | dependencies = [ 1780 | "dlib 0.5.0", 1781 | "lazy_static", 1782 | "pkg-config", 1783 | ] 1784 | 1785 | [[package]] 1786 | name = "weezl" 1787 | version = "0.1.5" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" 1790 | 1791 | [[package]] 1792 | name = "winapi" 1793 | version = "0.3.9" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1796 | dependencies = [ 1797 | "winapi-i686-pc-windows-gnu", 1798 | "winapi-x86_64-pc-windows-gnu", 1799 | ] 1800 | 1801 | [[package]] 1802 | name = "winapi-i686-pc-windows-gnu" 1803 | version = "0.4.0" 1804 | source = "registry+https://github.com/rust-lang/crates.io-index" 1805 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1806 | 1807 | [[package]] 1808 | name = "winapi-util" 1809 | version = "0.1.5" 1810 | source = "registry+https://github.com/rust-lang/crates.io-index" 1811 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1812 | dependencies = [ 1813 | "winapi", 1814 | ] 1815 | 1816 | [[package]] 1817 | name = "winapi-x86_64-pc-windows-gnu" 1818 | version = "0.4.0" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1821 | 1822 | [[package]] 1823 | name = "winit" 1824 | version = "0.25.0" 1825 | source = "registry+https://github.com/rust-lang/crates.io-index" 1826 | checksum = "79610794594d5e86be473ef7763f604f2159cbac8c94debd00df8fb41e86c2f8" 1827 | dependencies = [ 1828 | "bitflags", 1829 | "cocoa", 1830 | "core-foundation 0.9.1", 1831 | "core-graphics 0.22.2", 1832 | "core-video-sys", 1833 | "dispatch", 1834 | "instant", 1835 | "lazy_static", 1836 | "libc", 1837 | "log", 1838 | "mio", 1839 | "mio-misc", 1840 | "ndk", 1841 | "ndk-glue", 1842 | "ndk-sys", 1843 | "objc", 1844 | "parking_lot", 1845 | "percent-encoding", 1846 | "raw-window-handle", 1847 | "scopeguard", 1848 | "smithay-client-toolkit", 1849 | "wayland-client", 1850 | "winapi", 1851 | "x11-dl", 1852 | ] 1853 | 1854 | [[package]] 1855 | name = "x11-dl" 1856 | version = "2.18.5" 1857 | source = "registry+https://github.com/rust-lang/crates.io-index" 1858 | checksum = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8" 1859 | dependencies = [ 1860 | "lazy_static", 1861 | "libc", 1862 | "maybe-uninit", 1863 | "pkg-config", 1864 | ] 1865 | 1866 | [[package]] 1867 | name = "xcursor" 1868 | version = "0.3.4" 1869 | source = "registry+https://github.com/rust-lang/crates.io-index" 1870 | checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" 1871 | dependencies = [ 1872 | "nom 7.0.0", 1873 | ] 1874 | 1875 | [[package]] 1876 | name = "xdg" 1877 | version = "2.2.0" 1878 | source = "registry+https://github.com/rust-lang/crates.io-index" 1879 | checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" 1880 | 1881 | [[package]] 1882 | name = "xml-rs" 1883 | version = "0.8.4" 1884 | source = "registry+https://github.com/rust-lang/crates.io-index" 1885 | checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" 1886 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shadergarden" 3 | version = "0.2.0" 4 | edition = "2018" 5 | authors = ["Isaac Clayton "] 6 | description = "Create evolving gardens of shaders with Lisp and GLSL." 7 | license = "MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/tonarino/shadergarden" 10 | homepage = "https://blog.tonari.no/shadergarden" 11 | keywords = ["shader", "glsl", "lisp", "reload", "garden"] 12 | 13 | [features] 14 | default = ["ffmpeg"] 15 | ffmpeg = ["ffmpeg-next"] 16 | 17 | [dependencies] 18 | glium = "0.30.2" 19 | image = "0.23" 20 | notify = "5.0.0-pre.12" 21 | lexpr = "0.2.6" 22 | include_dir = "0.6" 23 | ffmpeg-next = { version = "4.4", optional = true } 24 | structopt = "0.3" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2021 tonari株式会社, 一般社団法人tonari 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /LISP.md: -------------------------------------------------------------------------------- 1 | # A Brief Introduction to Shader Garden Lisp 2 | A `ShaderGraph` defines a directed graph of computation. The easiest way to create one for general use is through a small lispy configuration language. This language isn't Turing complete as it's meant for configuration - in this sense, the graph doesn't do any computation itself, it just *describes* how computation should be done. 3 | 4 | ## Getting Started 5 | Let's look at a really basic example: 6 | 7 | ```clojure 8 | (input texture) 9 | (output texture) 10 | ``` 11 | 12 | This shader graph has a single node called `texture`. All it does is immediately return the input texture. This isn't very exciting, but it works. To evaluate this shader graph from Rust, you need to hook it up to a glium context: 13 | 14 | ```rust 15 | use shadergarden::{map, program::{graph_from_sexp, load_shaders}}; 16 | 17 | // Create the graph within a context 18 | let context = /* create a glium context */; 19 | let (mut graph, inputs, outputs) = graph_from_sexp( 20 | context, 21 | load_string!("..."), // shader graph source code 22 | load_shaders("..."), // path to shader folder 23 | ); 24 | 25 | // Pass a texture through the graph 26 | let texture = /* create a glium Texture2d */; 27 | let results = graph.forward(map! { 28 | inputs[0] => &texture, 29 | }) 30 | let output = results[outputs[0]]; 31 | ``` 32 | 33 | First, we build a graph within a specific glium context with the `graph_from_sexp` function. This function takes a shader graph lisp configuration and a table of shaders, and returns three things: the final graph, the `NodeId`s of all the inputs, and the `NodeId`s of all the outputs. 34 | 35 | A shader graph is made of `Node`s, and can have multiple inputs and outputs. Because the graph owns the `Node`s inside it, we pass around `NodeId`s to refer to specific `Node`s within a graph. These IDs are lightweight (internally just a number), and can be copied freely. When passing things in and out of the shader graph, you have to have handles on the right `NodeIDs`. 36 | 37 | ## Using a Shader 38 | Now that we know how to set everything up, let's write something a bit more complex - a shader that combines multiple textures! 39 | 40 | Shader Garden use GLSL as its shading language. It's not too hard to pick up, but if you'd like to learn more before continuing I highly recommend you read through [The Book of Shaders](https://thebookofshaders.com/) and [Inigo Quilez's Website](https://iquilezles.org/). A shader is a program that runs a simple function on each pixel in parallel to produce an output. 41 | 42 | A shader receives inputs through *uniforms*, which can be things like time, vectors, or even textures. By convention, we use the `u_` prefix to denote uniforms. 43 | 44 | Here's a GLSL shader that takes two input textures, and displays them split-screen. 45 | 46 | ```glsl 47 | // split_screen.frag 48 | #version 140 49 | 50 | uniform sampler2D u_texture_0; 51 | uniform sampler2D u_texture_1; 52 | 53 | in vec2 coords; 54 | out vec4 color; 55 | 56 | void main() { 57 | if (coords.x < 0.5) { 58 | color = texture(u_texture_0, coords); 59 | } else { 60 | color = texture(u_texture_1, coords); 61 | } 62 | } 63 | ``` 64 | 65 | (We're using an old version of GLSL for compatibility, but newer versions are supported as well.) 66 | 67 | Let's break this down! As you can see, this shader takes two uniforms: `u_texture_0` and `u_texture_1`. In a shader graph, a shader may take N inputs, written `u_texture_0, u_texture_1, ..., u_texture_`. 68 | 69 | In addition to these two uniforms, there are two things present in all shaders. We have an input, `coords`, and an output, `color`. `coords` is the pixel's coordinate pair between 0 and 1, with the bottom-left corner being (0, 0). `color` is an RGBA vector that we assign to to color the output. 70 | 71 | Let's use this shader in our shader graph now: 72 | 73 | ```clojure 74 | (input left) 75 | (input right) 76 | 77 | (let combined 78 | (shader "split_screen" 512 512 left right)) 79 | 80 | (output combined) 81 | ``` 82 | 83 | As you can see, our graph takes two inputs, `left` and `right`, and produces a single output. This output is created by joining the inputs together in a shader! To understand how this works, let's go over the two new keywords we've introduced: `let` and `shader`. 84 | 85 | ```clojure 86 | (let ) 87 | ``` 88 | 89 | `let` assigns the value of an expression to a symbol. In this case, the value that `(shader ...)` produces is of type `NodeId`, so `combined` must also be a `NodeId`. Inputs and outputs must also be node IDs, but other types do exist, as you'll see soon. 90 | 91 | ```clojure 92 | (shader ) 93 | ``` 94 | 95 | `shader` is a bit more complicated. It's a built-in function that takes a number of arguments, and creates a shader node in the shader graph with those properties. Here's a breakdown of the arguments it expects: 96 | 97 | - `name` is a string. If you use `load_shaders` when calling `graph_from_sexp`, each shader will be named after its file stem, i.e. `.frag`. 98 | 99 | - `width` and `height` are both natural numbers that set the resolution of the output texture in pixels. For example, a `shader` with a `width` and `height` of 300 by 100 will produce an output 300 by 100 pixels large. This output texture is used when chaining shaders together. 100 | 101 | - `inputs...` - all trailing arguments are inputs that are passed into the shader, as `u_texture`s. Each input must be a `NodeID`, of course. 102 | 103 | So, returning to this line: 104 | 105 | ```clojure 106 | (let combined 107 | (shader "split_screen" 512 512 left right)) 108 | ``` 109 | 110 | Here we create a node in the shader graph that takes two inputs and runs them through the `split_screen` shader, producing an output that is 512x512 pixels large. 111 | 112 | ## Composing Shaders 113 | This is cool and all, but it's a bit boring. Isn't the whole point of a shader graph the ability to *compose* shaders? 114 | 115 | Yep! Here's a slightly more complex example: 116 | 117 | ```clojure 118 | (input image) 119 | 120 | ; define a function 121 | (define (sharpen width height image iter) 122 | (let edges 123 | (shader "sobel" width height image)) 124 | ; iteratively sharpen the image 125 | (let out image) 126 | (repeat iter 127 | (let out 128 | (shader "sharpen" width height edges image))) 129 | ; return the output 130 | out) 131 | 132 | (let sharpened (sharpen 1080 1920 image 7)) 133 | (output sharpened) 134 | ``` 135 | 136 | If you're reeling right now from all the lisp, no worries. We'll break it down. 137 | 138 | (And if you're still uncomfortable after this, you can always use Rust to build a shader graph directly.) 139 | 140 | So, let's break it down! The first keyword new keyword we run into is `define`. `define`, quite sensibly, defines a new function for later use. It looks like this: 141 | 142 | ```clojure 143 | (define ( ) 144 | 145 | ) 146 | ``` 147 | 148 | Shader graph lisp is a lisp 2 (meaning functions and variables exist in separate namespaces), and does not support higher order functions (nor does it have any operations to operate on lists, so its uefulness as a lisp is debatable ;). 149 | 150 | Anyway, 151 | 152 | - `symbol` is the name of the function to be defined, and `arguments...` are the list of symbols to be bound in the new scope where `body...` and `output` are evaluated. 153 | 154 | - `body...` is made of a number of *statements*. A statement is something like `let`, a form that doesn't produce an output. 155 | 156 | - `define` itself is a statement, but the function it defines must be used as an *expression*, which means it must produce an output. The last item in `define`, `output`, is an *expression*. An expression is something like `(shader ...)` or a variable - i.e. a form that *does* produce an output. 157 | 158 | I'm more of a lisp 1, 'everything is an expression' kinda guy myself, but I appreciate how separating namespaces and making the distinction between statements and expressions allows one to enforce language constraints in an elegant manner (e.g. guaranteed termination). 159 | 160 | Onward! Inside the definition of `sharpen`, we encounter a new statement called `repeat`. 161 | 162 | ```clojure 163 | (repeat 164 | ) 165 | ``` 166 | 167 | `repeat` is a fairly rigid thing, but it might be made more flexible in the future. All it really does is copy-paste the statements inside `body...` a bunch of times. How many times, exactly? `times` times, of course! (`times` must be a positive integer.) 168 | 169 | We can call this function in a pretty standard way: 170 | 171 | ```clojure 172 | (let sharpened (sharpen 1080 1920 image 7)) 173 | ``` 174 | 175 | Functions can only return one argument, which is a bit of a limitation at the moment. For this reason, I suggest using functions for linear portions of the shader graph. This may be changed in the future, but I'd like to do so in a way that doesn't introduce generalized list processing. 176 | 177 | > What I'm thinking is something like this: 178 | > ```clojure 179 | > (define (pair one two) [one two]) 180 | > ; destructuring 181 | > (let [one two] (pair one two)) 182 | > ``` 183 | > But this has the potential to become complicated fairly fast. We'll see. 184 | > 185 | > It's important to note that this wouldn't introduce any additional overhead. These programs define a shader graph, and two exactly-the-same shader graphs, even if defined in different ways, will run with exactly the same performance. 186 | 187 | Finally, we'll cover some of Shader Garden Lisp's more advanced features. 188 | 189 | ## Advanced Features 190 | When writing GLSL shaders, it's common to use `#define` statements to define useful constants. For instance: 191 | 192 | ```glsl 193 | #define SEARCH 10 194 | 195 | for (int i = 0; i < SEARCH; i++) { 196 | // ... 197 | } 198 | ``` 199 | 200 | Loops in GLSL are unrolled at compile time, which means they must have a fixed number of iterations, also known at compile time. For this reason, we *can't* set the number of iterations through a dynamic mechanism, like a uniform. 201 | 202 | But what if you have a shader that can applied in a lot of different situations, with each situation requiring slightly different constants? For example, what if we want to `SEARCH` to be smaller at high resolutions (so we do less work), or what if we want to search *backwards* by starting at `SEARCH` and decrementing `i` in other situations? 203 | 204 | It may sound a bit crazy, but Shader Garden Lisp comes equipped has a *preprocessor preprocessor*. Like `#define`, this *pre-preprocessor* inserts useful constants at compiletime for later use. Unlike `#define`, however, these constants can be passed in through Shader Garden Lisp. 205 | 206 | Here's a simple example: 207 | 208 | ```glsl 209 | 210 | 211 | 212 | #ifdef BACKWARDS 213 | for (int i = 0; i > -SEARCH; i--) { 214 | // ... 215 | } 216 | #else 217 | for (int i = 0; i < SEARCH; i++) { 218 | // ... 219 | } 220 | #endif 221 | ``` 222 | 223 | `` and `` are *prepreprocessor* hooks that will be expanded into `#define` macros. We refer to shaders that take hooks as *parameterized shaders*. If you call a shader without the right hooks in place, you'll get a compile-time error. So, how can we set up these hooks? Like this! 224 | 225 | ```clojure 226 | (let found 227 | (shader-param 228 | ("search" 100 100 texture) 229 | (define "SEARCH" 10) 230 | (ifdef "BACKWARDS" #t))) 231 | ``` 232 | 233 | `shader-param` is an expression that loads a shader, while also replacing the prepreprocessor hooks. Here's the form it follows: 234 | 235 | ```clojure 236 | (shader-param 237 | ( ) 238 | ) 239 | ``` 240 | 241 | The first form in `shader-param` defines the parameterized shader to use. If you look closely, this is same way we normally define shaders (i.e. `(shader ...)`), only without the `shader` keyword. 242 | 243 | After this form, we list as many hooks as needed. There are two types of hooks currently supported: 244 | 245 | 1. `(define )` takes a `HOOK`, which must be a string, and a `value`, which much be representable as a string, and expands the corresponding `` in glsl into `#define HOOK value`. 246 | 247 | 2. `(ifdef )` also takes a hook, but it needs a boolean as well. In lisp, `#t` and `#f` are used to represent true and false, respectively. If `boolean` is true, `` will be expanded to `#define 1`. Otherwise, `` will be removed. 248 | 249 | With these two mechanisms, it's possible to define parameterized shaders that work well in many different circumstances. 250 | 251 | Define macros in GLSL also support arguments. This is beyond crazy, but I'm obliged to include it for completion: 252 | 253 | ```glsl 254 | // channel_op.frag 255 | // Apply an operation to a pair of pixels in each color channel. 256 | 257 | // This will be replaced: 258 | 259 | 260 | uniform sampler2D u_texture_0; 261 | uniform sampler2D u_texture_1; 262 | 263 | in vec2 coords; 264 | out vec2 color; 265 | 266 | void main() { 267 | vec3 first = texture2D(u_texture_0, coords, 0.).rgb; 268 | vec3 second = texture2D(u_texture_0, coords, 0.).rgb; 269 | 270 | color = vec4( 271 | OP(first.r, second.r), 272 | OP(first.g, second.g), 273 | OP(first.b, second.b), 274 | 1.); 275 | } 276 | ``` 277 | 278 | Then, in the shader graph: 279 | 280 | ```clojure 281 | (input first) 282 | (input second) 283 | 284 | ; make the param shader nicer to use 285 | ; this is what abstraction is for 286 | (define (channel_op width height op first second) 287 | (shader-param 288 | ("channel_op" width height first second) 289 | (define "OP(a, b)" op))) 290 | 291 | (let w 640) 292 | (let h 480) 293 | 294 | ; take the average of two images 295 | (let average 296 | (channel_op w h "(.5*(a+b))" first second)) 297 | 298 | ; take the channel-wise minimum of two images 299 | (let minimum 300 | (channel_op w h "min(a, b)" first second)) 301 | 302 | ; take the difference of the average and the minimum 303 | (let difference 304 | (channel_op w h "(a-b)" average minimum)) 305 | 306 | (output difference) 307 | ``` 308 | 309 | Yep. Crazy, right? 310 | 311 | ## Other Node Types 312 | 313 | > TODO 314 | 315 | ## Hot Code Reloading 316 | Why go through the trouble of defining a new language? Any why couldn't we just use something like JSON and be done with it? 317 | 318 | The answer to the first question is hot code reloading; the answer to the second question is ease of prototyping. 319 | 320 | Hot code reloading allows us to define shader graphs, then rebuild them live *at runtime*. As anyone who has ever messed around with shaders before can attest, the most fun part is seeing your changes update live. By making not only the shaders, but also the pipeline in which they are embedded in reloadable, it's insanely easy (and a heck-ton of fun) to experiment with different shaders and how they work together. 321 | 322 | In this sense, hot code reloading and ease of prototyping go hand-in-hand; the former enables the latter. 323 | 324 | To try out hot code reloading, `cd` into `resource` and type `cargo run --release` (using `prime-run` if you have a GPU and don't want your computer to die.) This will load the shader graph specified in `shader.graph`, and begin executing it if no issues exist. 325 | 326 | When you save after editing a shader, or the graph itself, `shadergarden` should detect your changes and recompile everything. If compilation succeeds, it'll switch out the old graph with the new; otherwise, it'll print the error and keep running the old one. 327 | 328 | Happy hacking! 329 | 330 | ## Appendix 331 | Here's a quick reference for common uniforms and stuff. 332 | 333 | ### Input/Output 334 | - Input: `coords`, the coordinate of the current pixel from 0 to 1, with the bottom-left being the origin. This is a `in vec2`. 335 | - Output: `color`, an RGBA pixel. This is a `out vec4`. 336 | 337 | ### Uniforms 338 | - Textures: `u_texture_` is the Nth texture passed into the shader. It is a `uniform sampler2D`. 339 | - Previous: `u_previous` is the output of the previous frame in recurrent shaders. 340 | - Time: `u_time` is the time, in seconds, since the shader last started running. it is a `uniform float` 341 | - Resolution: `u_resolution` is the output resolution size, in pixels. This is a `uniform vec2`. 342 | 343 | ### Common Definitions 344 | I thought there would be more, but: 345 | ```glsl 346 | // the size of a pixel with respect to coords. 347 | // i.e. coords.x + PIXEL.x is exactly one pixel over. 348 | #define PIXEL (1.0 / u_resolution) 349 | 350 | // ... 351 | ``` 352 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `shadergarden` 2 | 3 | Shadergarden is a tool for building hot-code-reloadable shader pipelines. For a tutorial for how to get started, consult the [introductory blog post](https://blog.tonari.no/shadergarden) or the [shadergarden lisp language documentation](./LISP.md). 4 | 5 | ## Usage 6 | Once you've installed shadergarden via `cargo install shadergarden`, test to see that it is installed properly by running: 7 | 8 | ``` 9 | shadergarden --help 10 | ``` 11 | 12 | This should print out some usage information. To create a new project, run: 13 | 14 | ``` 15 | shadergarden new path/to/project 16 | ``` 17 | 18 | This will create a new example project in the specified directory. To run a shadergarden, cd into the directory of a project and run: 19 | 20 | ``` 21 | shadergarden run 22 | ``` 23 | 24 | This should open a new window and start running your graph. Don't close the window if you want to make changes; instead, open the project in an editor of your choice - the graph will update on save. 25 | 26 | If a build error is encountered while reloading, `shadergarden` will log the error and continue executing the old graph. 27 | 28 | ### Fancier Usage 29 | You can pass input images and videos to shadergarden using the `-i` flag. This flag takes a list of paths to photos/videos - you must pass the same number of input photos/videos as the number of `(input ...)`s specified in `shader.graph`. 30 | 31 | Once you've got a nice shadergarden, to render out a png sequence, use the `render` subcommand. This subcommand works exactly the same as `run`, but requires an output directory. To render the game of life demo out into a gif, run: 32 | 33 | ``` 34 | mkdir out 35 | shadergarden render demos/life -o out -s 30 -e 430 36 | ffmpeg -i "out/frame-%4d.png" -framerate 30 life.gif 37 | ``` 38 | 39 | You should see something like this (it might be a *little* fancier): 40 | 41 |

42 | 43 |

44 | 45 | Happy hacking! 46 | -------------------------------------------------------------------------------- /demos/base/shader.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform float u_time; 7 | 8 | void main() { 9 | color = vec4(coords.xy, pow(sin(u_time * 3.14), 2.), 1.); 10 | } 11 | -------------------------------------------------------------------------------- /demos/base/shader.graph: -------------------------------------------------------------------------------- 1 | (let rainbow (shader "shader" 512 512)) 2 | (output rainbow) 3 | -------------------------------------------------------------------------------- /demos/curl_noise/curl_noise.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | // Reference Paper: 4 | // https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph2007-curlnoise.pdf 5 | 6 | in vec2 coords; // Between 0-1 7 | out vec4 color; 8 | 9 | uniform sampler2D u_previous; 10 | uniform vec2 u_resolution; // Size of the screen in pixels 11 | uniform float u_time; 12 | 13 | #define PIXEL (1.0 / u_resolution) 14 | 15 | vec3 permute(vec3 x) { 16 | return mod(((x * 34.0) + 1.0) * x, 289.0); 17 | } 18 | 19 | vec3 taylorInvSqrt(vec3 r) { 20 | return 1.79284291400159 - 0.85373472095314 * r; 21 | } 22 | 23 | // Given 2D coordinates, returns a float noise value 24 | // in the [-1.0, 1.0] range. 25 | float simplex_noise(vec2 p) { 26 | const vec2 C = vec2(0.211324865405187134, 0.366025403784438597); 27 | 28 | // First corner 29 | vec2 i = floor(p + dot(p, C.yy)); 30 | vec2 x0 = p - i + dot(i, C.xx); 31 | 32 | // Other corners 33 | vec2 i1; 34 | i1.x = step(x0.y, x0.x); 35 | i1.y = 1.0 - i1.x; 36 | 37 | vec4 x12 = x0.xyxy + vec4(C.xx, C.xx * 2.0 - 1.0); 38 | x12.xy -= i1; 39 | 40 | // Permutations 41 | i = mod(i, 289.0); // Avoid truncation in polynomial evaluation. 42 | vec3 permuted = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); 43 | 44 | // Circularly symmetric blending kernel. 45 | vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0); 46 | 47 | m = m * m; 48 | m = m * m; 49 | 50 | // Gradients from 41 points on a line, mapped onto a diamond. 51 | vec3 x = fract(permuted * (1.0 / 41.0)) * 2.0 - 1.0; 52 | vec3 gy = abs(x) - 0.5; 53 | vec3 ox = floor(x + 0.5); // Could use round here? 54 | vec3 gx = x - ox; 55 | 56 | // Normalize gradients implicitly by scaling m. 57 | m *= taylorInvSqrt(gx * gx + gy * gy); 58 | 59 | // Compute final noise value at p. 60 | vec3 g; 61 | g.x = gx.x * x0.x + gy.x * x0.y; 62 | g.yz = gx.yz * x12.xz + gy.yz * x12.yw; 63 | 64 | // Scale output to span range [-1, 1]. 65 | // (Scaling factor determined by experiments) 66 | return 130.0 * dot(m, g); 67 | } 68 | 69 | // P is a point from 0-1 70 | // Output is in the range X: [-1.0, 1.0] 71 | // Y: [-1.0, 1.0] 72 | vec2 curl_noise(vec2 p) { 73 | vec2 dx = vec2(PIXEL.x, 0.0); 74 | vec2 dy = vec2(0.0, PIXEL.y); 75 | 76 | // How the field changes with respect to X 77 | float dfield_dx = simplex_noise(p + dx) - simplex_noise(p - dx); 78 | 79 | // How the field changes with respect to Y 80 | float dfield_dy = simplex_noise(p + dy) - simplex_noise(p - dy); 81 | 82 | vec2 vel = vec2(dfield_dy, -dfield_dx); 83 | 84 | return normalize(vel); 85 | } 86 | 87 | void main() { 88 | // float noise = perlin(coords * 100.0); 89 | vec2 curl = curl_noise(coords * 3.0) + curl_noise(coords * 10.0) + curl_noise(coords * 100.0); 90 | 91 | curl = (curl + 1.0) / 2.0; 92 | 93 | color = vec4(curl.x, curl.y, 0.5, 1.0); 94 | } 95 | -------------------------------------------------------------------------------- /demos/curl_noise/shader.graph: -------------------------------------------------------------------------------- 1 | (let curl_noise (shader-rec "curl_noise" 512 512)) 2 | ;(let move (shader-rec "move" 512 512 curl_noise)) 3 | ;(let simplex_noise (shader-rec "simplex_noise" 512 512)) 4 | (output curl_noise) 5 | -------------------------------------------------------------------------------- /demos/curl_noise/simplex_noise.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | // Reference implementation for 2D Simplex Noise: 4 | // https://weber.itn.liu.se/~stegu/jgt2012/article.pdf 5 | 6 | in vec2 coords; // Between 0-1 7 | out vec4 color; 8 | 9 | vec3 permute(vec3 x) { 10 | return mod(((x * 34.0) + 1.0) * x, 289.0); 11 | } 12 | 13 | vec3 taylorInvSqrt(vec3 r) { 14 | return 1.79284291400159 - 0.85373472095314 * r; 15 | } 16 | 17 | // Given 2D coordinates, returns a float noise value 18 | // in the [-1.0, 1.0] range. 19 | float simplex_noise(vec2 p) { 20 | const vec2 C = vec2(0.211324865405187134, 0.366025403784438597); 21 | 22 | // First corner 23 | vec2 i = floor(p + dot(p, C.yy)); 24 | vec2 x0 = p - i + dot(i, C.xx); 25 | 26 | // Other corners 27 | vec2 i1; 28 | i1.x = step(x0.y, x0.x); 29 | i1.y = 1.0 - i1.x; 30 | 31 | vec4 x12 = x0.xyxy + vec4(C.xx, C.xx * 2.0 - 1.0); 32 | x12.xy -= i1; 33 | 34 | // Permutations 35 | i = mod(i, 289.0); // Avoid truncation in polynomial evaluation. 36 | vec3 permuted = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); 37 | 38 | // Circularly symmetric blending kernel. 39 | vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0); 40 | 41 | m = m * m; 42 | m = m * m; 43 | 44 | // Gradients from 41 points on a line, mapped onto a diamond. 45 | vec3 x = fract(permuted * (1.0 / 41.0)) * 2.0 - 1.0; 46 | vec3 gy = abs(x) - 0.5; 47 | vec3 ox = floor(x + 0.5); // Could use round here? 48 | vec3 gx = x - ox; 49 | 50 | // Normalize gradients implicitly by scaling m. 51 | m *= taylorInvSqrt(gx * gx + gy * gy); 52 | 53 | // Compute final noise value at p. 54 | vec3 g; 55 | g.x = gx.x * x0.x + gy.x * x0.y; 56 | g.yz = gx.yz * x12.xz + gy.yz * x12.yw; 57 | 58 | // Scale output to span range [-1, 1]. 59 | // (Scaling factor determined by experiments) 60 | return 130.0 * dot(m, g); 61 | } 62 | 63 | void main() { 64 | float noise = simplex_noise(coords * 10.0); 65 | 66 | // Bring noise back to the [0.0, 1.0] range. 67 | noise = (noise + 1.0) / 2.0; 68 | 69 | color = vec4(vec3(noise), 1.0); 70 | } 71 | -------------------------------------------------------------------------------- /demos/gravity/add.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform sampler2D u_texture_0; 7 | uniform sampler2D u_texture_1; 8 | 9 | void main() { 10 | color = texture(u_texture_0, coords); 11 | color += texture(u_texture_1, coords).r * 0.3; 12 | } 13 | -------------------------------------------------------------------------------- /demos/gravity/channel.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform sampler2D u_texture_0; 7 | 8 | float sim(vec2 a, vec2 b) { 9 | return dot(normalize(a), normalize(b))*.5+.5; 10 | } 11 | 12 | void main() { 13 | vec4 p = texture(u_texture_0, coords).rgba; 14 | p = p*2.-1.; 15 | float a = sim(vec2(p.r, p.g), vec2(p.b, p.a)); 16 | vec2 d = vec2(p.r, p.g) + vec2(p.b, p.a); 17 | d = normalize(d); 18 | d = d * 0.5 + 0.5; 19 | // color = vec4(vec3(a), 1.0) * 2.0; 20 | color = vec4(d, 0.5, 1.0); 21 | // color = sqrt(color); 22 | } 23 | -------------------------------------------------------------------------------- /demos/gravity/grid.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | // use this fragment shader as a starting point for new ones 7 | uniform sampler2D u_previous; 8 | uniform sampler2D u_texture_0; 9 | uniform vec2 u_resolution; 10 | uniform float u_time; 11 | 12 | #define PIXEL (1.0 / u_resolution) 13 | 14 | vec4 lookup(vec2 p, float dx, float dy) { 15 | vec2 uv = p + vec2(dx, dy) * PIXEL; 16 | vec4 c = texture(u_previous, uv, 0.).rgba; 17 | c = c*2.-1.; 18 | 19 | float mass = texture(u_texture_0, uv, 0.).r * 0.99; 20 | 21 | // if (texture(u_texture_0, uv, 0.).r > 0.5) { 22 | return (1.0 - mass) * c + (mass * vec4( 23 | vec2(dx, dy), 24 | -vec2(dx, dy) 25 | )); 26 | // } 27 | 28 | // return c; 29 | } 30 | 31 | float sim(vec2 a, vec2 b) { 32 | return dot(normalize(a), normalize(b)); 33 | } 34 | 35 | vec4 update(vec2 p) { 36 | vec2 force = vec2(0.0); 37 | vec2 force_back = vec2(0.0); 38 | float tot_s = 0.0; 39 | float tot_sb = 0.0; 40 | 41 | vec4 c = lookup(p, 0, 0); 42 | vec2 baseline = vec2(c.r, c.g); // + vec2(c.b, c.a); 43 | 44 | for (float i = -1; i <= 1; i++) { 45 | for (float j = -1; j <= 1; j++) { 46 | vec4 n = lookup(p, i, j); 47 | 48 | { 49 | float s = sim(n.xy, baseline); 50 | vec2 new = vec2(i, j) + n.xy; 51 | float strength = sqrt(length(new)); 52 | if (s >= 0.0) { 53 | force += n.xy * strength * s; 54 | tot_s += strength * s; 55 | } else { 56 | force_back += n.xy * strength * -s; 57 | tot_sb += strength * -s; 58 | } 59 | } 60 | 61 | { 62 | float s = sim(n.ba, baseline); 63 | vec2 new = vec2(i, j) + n.ba; 64 | float strength = sqrt(length(new)); 65 | if (s >= 0.0) { 66 | force += n.ba * strength * s; 67 | tot_s += strength * s; 68 | } else { 69 | force_back += n.ba * strength * -s; 70 | tot_sb += strength * -s; 71 | } 72 | } 73 | } 74 | } 75 | 76 | return vec4( 77 | force / (tot_s + 0.008), 78 | force_back / (tot_sb + 0.008)); 79 | } 80 | 81 | float rand(vec2 co){ 82 | return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); 83 | } 84 | 85 | void main() { 86 | if (u_time < 1.0) { 87 | color = vec4( 88 | rand(coords), 89 | rand(coords + 1.0), 90 | rand(coords + 2.0), 91 | rand(coords + 3.0) 92 | ); 93 | // color = vec4(0.5); 94 | return; 95 | } 96 | 97 | vec4 new = update(coords); 98 | 99 | color = new*.5+.5; 100 | } 101 | -------------------------------------------------------------------------------- /demos/gravity/mass.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform sampler2D u_texture_0; 7 | uniform float u_time; 8 | uniform vec2 u_resolution; 9 | 10 | #define a_time 0.5 * u_time 11 | #define PIXEL (1.0 / u_resolution) 12 | 13 | void main() { 14 | color = vec4(vec3(0.), 1.); 15 | 16 | vec2 c = mod(1.0 * coords, 1.0); 17 | 18 | float l = length(c - 0.5); 19 | // color = vec4(vec3(1.0 - smoothstep(0.1, 0.1 + PIXEL.x, l)), 1.); 20 | // color += vec4(vec3(smoothstep(0.4, 0.4 + PIXEL.x, l)), 1.); 21 | 22 | float l2 = length(c - (vec2(sin(a_time), cos(a_time)) * 0.15 + 0.5)); 23 | color += vec4(vec3(1.0 - smoothstep(0.1, 0.1 + PIXEL.x, l2)), 1.); 24 | 25 | float l3 = length(c - (vec2(sin(a_time+3.14), cos(a_time+3.14)) * 0.15 + 0.5)); 26 | color += vec4(vec3(1.0 - smoothstep(0.1, 0.1 + PIXEL.x, l3)), 1.); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /demos/gravity/shader.graph: -------------------------------------------------------------------------------- 1 | (let s 512) 2 | 3 | (let mass (shader "mass" s s)) 4 | (let force (shader-rec "grid" s s mass)) 5 | ; (let planet (shader-rec "wander" s s force)) 6 | 7 | (let colored (shader "channel" s s force)) 8 | ; (let out (shader "add" s s width_x planet)) 9 | 10 | (output colored) 11 | -------------------------------------------------------------------------------- /demos/gravity/wander.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform sampler2D u_previous; 7 | uniform sampler2D u_texture_0; 8 | uniform vec2 u_resolution; 9 | uniform float u_time; 10 | 11 | #define PIXEL (1.0 / u_resolution) 12 | 13 | vec2 lookup(vec2 p, float dx, float dy) { 14 | vec2 uv = p + vec2(dx, dy) * PIXEL; 15 | vec4 c = texture(u_texture_0, uv, 0.).rgba; 16 | c = c*2.-1.; 17 | return (c.xy + c.ba); 18 | } 19 | 20 | 21 | vec3 move(vec2 p) { 22 | float color = 0.0; 23 | vec3 ret = vec3(0.0, 0.0, 0.0); 24 | 25 | for (float i = -1; i <= 1; i++) { 26 | for (float j = -1; j <= 1; j++) { 27 | vec2 uv = p + vec2(i, j) * PIXEL; 28 | vec3 particle = texture(u_previous, uv, 0.).rgb; 29 | if (particle.r > 0.5) { 30 | vec2 velocity = particle.gb*2.-1.; 31 | vec2 force = lookup(coords, i, j); 32 | vec2 new_v = velocity + force * 0.01; 33 | vec2 new_pos = (vec2(i, j) + new_v.xy) + sin(u_time * 37289.321789 + p) * 0.5; 34 | 35 | if (abs(new_pos.x) < 0.5 && abs(new_pos.y) < 0.5) { 36 | ret += vec3(particle.r, new_v); 37 | } 38 | } 39 | } 40 | } 41 | 42 | return vec3(ret.r, ret.gb*.5+.5); 43 | } 44 | 45 | void main() { 46 | // if (sin(u_time * 10.0) < 0.0) { 47 | // color = vec4(0.); 48 | 49 | vec2 tile = mod(coords * u_resolution, 128.0); 50 | if (tile.x < 1.0 || tile.y < 1.0) { 51 | color = vec4(1.0, 0.5, 0.5, 1.0); 52 | return; 53 | } 54 | // } 55 | 56 | color = vec4(move(coords), 1.0); 57 | } 58 | -------------------------------------------------------------------------------- /demos/life/color.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform sampler2D u_texture; 7 | uniform sampler2D u_previous; 8 | uniform vec2 u_resolution; 9 | uniform float u_time; 10 | 11 | #define PIXEL (1.0 / u_resolution) 12 | 13 | void main() { 14 | vec3 c = texture(u_texture, coords, 0.).rgb; 15 | vec3 p = texture(u_previous, coords, 0.).rgb; 16 | p *= vec3(0.97, 0.99, 0.98); 17 | color = vec4(max(c, p), 1.); 18 | } 19 | -------------------------------------------------------------------------------- /demos/life/life.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform sampler2D u_previous; 7 | uniform vec2 u_resolution; 8 | uniform float u_time; 9 | 10 | #define PIXEL (1.0 / u_resolution) 11 | 12 | int is_alive(vec2 st) { 13 | st = mod(st, 1.0); 14 | return int(texture(u_previous, st, 0.).r); 15 | } 16 | 17 | int count_neighbors(vec2 st) { 18 | int alive = 0 - is_alive(st); 19 | 20 | for (int i = -1; i <= 1; i++) { 21 | for (int j = -1; j <= 1; j++) { 22 | vec2 stn = vec2(st.x + PIXEL.x * i, st.y + PIXEL.y * j); 23 | alive += is_alive(stn); 24 | } 25 | } 26 | 27 | return alive; 28 | } 29 | 30 | int step_gol(vec2 st) { 31 | int neighbors = count_neighbors(st); 32 | 33 | if ((neighbors == 3) || (is_alive(st) == 1 && neighbors == 2)) { 34 | return 1; 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | float random (vec2 st) { 41 | return fract(sin(dot(st.xy, 42 | vec2(12.9898,78.233)))*43758.5453123); 43 | } 44 | 45 | void main() { 46 | if (u_time < 1.0) { 47 | color = vec4(vec3(0.), 1.); 48 | if (coords.x < 0.5 && coords.y < 0.5) { 49 | color = vec4(1.); 50 | } 51 | return; 52 | } 53 | 54 | color = vec4(vec3(step_gol(coords)), 1.); 55 | } 56 | -------------------------------------------------------------------------------- /demos/life/life.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonarino/shadergarden/c23432225a09eab3a55a777f7c727507c379707c/demos/life/life.gif -------------------------------------------------------------------------------- /demos/life/shader.graph: -------------------------------------------------------------------------------- 1 | (let life (shader-rec "life" 512 512)) 2 | (let color (shader-rec "color" 512 512 life)) 3 | ; for classic game of life 4 | ; change `color` to `life` below: 5 | (output color) 6 | -------------------------------------------------------------------------------- /demos/raymarcher/avg.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform sampler2D u_previous; 7 | uniform sampler2D u_texture_0; 8 | uniform vec2 u_resolution; 9 | uniform float u_time; 10 | 11 | float random (vec2 st) { 12 | return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); 13 | } 14 | 15 | vec4 lerp(vec4 a, vec4 b, float t) { 16 | return t * a + (1.0 - t) * b; 17 | } 18 | 19 | void main() { 20 | color = lerp( 21 | texture(u_previous, coords, 0.), 22 | texture(u_texture_0, coords, 0.), 23 | 0.995); 24 | } 25 | -------------------------------------------------------------------------------- /demos/raymarcher/denoise.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | // Snaps a disparity map to some edges, 4 | // Using an automata and some fancy curves. 5 | 6 | uniform sampler2D u_texture_0; // edge map 7 | uniform sampler2D u_texture_1; // image 8 | uniform vec2 u_resolution; 9 | uniform float u_time; 10 | 11 | in vec2 coords; 12 | out vec4 color; 13 | 14 | #define PIXEL (1.0 / u_resolution) 15 | 16 | float phi(float n) { 17 | return (1. - sqrt(4 * n + 1)) / 2.0; 18 | } 19 | 20 | float warp_space(float x, float w) { 21 | float pos = phi(w); 22 | return -(w/(x-pos)+pos+1.); 23 | } 24 | 25 | float left_warped_curve(float x, float w) { 26 | return .5-.5*cos(3.14*warp_space(x, w)); 27 | } 28 | 29 | vec3 average_out(vec2 uv) { 30 | vec3 total = vec3(0.0); 31 | float out_of = 0.0; 32 | 33 | for (float i = -1.0; i <= 1.0; i++) { 34 | for (float j = -1.0; j <= 1.0; j++) { 35 | vec2 pos = vec2(uv.x + i * PIXEL.x, uv.y + j * PIXEL.y); 36 | float edge = texture(u_texture_0, pos, 0.).r; 37 | float wall = 1.0 - left_warped_curve(edge, 0.0001); 38 | vec3 color = texture(u_texture_1, pos, 0.).rgb; 39 | total += wall * color; 40 | out_of += wall; 41 | } 42 | } 43 | 44 | if (out_of == 0.0) { 45 | return texture(u_texture_1, coords, 0.).rgb; 46 | } 47 | 48 | vec3 average = total / out_of; 49 | return average; 50 | } 51 | 52 | void main() { 53 | color = vec4(average_out(coords), 1.0); 54 | } 55 | -------------------------------------------------------------------------------- /demos/raymarcher/render.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | out vec4 out_color; 4 | 5 | // METADATA ---------- 6 | 7 | uniform vec2 u_resolution; 8 | uniform float u_time; 9 | 10 | // CONSTANTS ---------- 11 | 12 | #define START 0.0 13 | #define END 1000.0 14 | #define STEPS 100 15 | #define EPSILON 0.001 16 | #define FOV 60.0 17 | #define PIXEL (1.0 / u_resolution) 18 | 19 | // CAMERA ---------- 20 | 21 | vec3 makeRay(float fov, float ratio, vec2 st) { 22 | vec2 xy = st - vec2(ratio, 1.0) * 0.5; 23 | float z = 1.0 / tan(radians(fov) / 2.0); 24 | return normalize(vec3(xy, -z)); 25 | } 26 | 27 | mat3 look(vec3 camera, vec3 target, vec3 up) { 28 | // Based on gluLookAt man page 29 | vec3 f = normalize(target - camera); 30 | vec3 s = normalize(cross(f, up)); 31 | vec3 u = cross(s, f); 32 | return mat3(s, u, -f); 33 | } 34 | 35 | float random(vec2 st) { 36 | return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); 37 | } 38 | 39 | // SIGNED DISTANCE FUNCTIONS ---------- 40 | 41 | float box(vec3 point, vec3 size) { 42 | vec3 d = abs(point) - (size / 2.0); 43 | float insideDistance = min(max(d.x, max(d.y, d.z)), 0.0); 44 | float outsideDistance = length(max(d, 0.0)); 45 | return insideDistance + outsideDistance; 46 | } 47 | 48 | float sphere(vec3 point, float radius) { 49 | return length(point) - radius; 50 | } 51 | 52 | float scene(vec3 point) { 53 | float c1 = box(point - vec3(-10.0, 0.0, 0.0), vec3(10.0)); 54 | float c2 = box(point - vec3(0.0, 10.0, 0.0), vec3(10.0)); 55 | float c3 = box(point - vec3(0.0,-10.0, 0.0), vec3(10.0)); 56 | float c4 = box(point - vec3(0.0, 0.0, 10.0), vec3(10.0)); 57 | float c5 = box(point - vec3(0.0, 0.0,-10.0), vec3(10.0)); 58 | float scene_box = min(c5, min(min(c1, c2), min(c3, c4))); 59 | 60 | mat3 t = look(vec3(1.0, 0., 0.5), vec3(0., 0., 0.), vec3(0.0, 1.0, 0.0)); 61 | float box1 = box((point + vec3(2., 2., 2.)) * t, vec3(3.0, 6.0, 3.0)); 62 | mat3 t2 = look(vec3(0.5, 0., 1.0), vec3(0., 0., 0.), vec3(0.0, 1.0, 0.0)); 63 | float box2 = box((point + vec3(-1., 3.5, -1.3)) * t2, vec3(3.0, 3.0, 5.0)); 64 | float box_u = min(box1, box2); 65 | 66 | float circle = sphere(point + vec3(-1., 0.0, -1.3), 2.0); 67 | return min(min(scene_box, circle), box_u); 68 | } 69 | 70 | vec4 emission(vec3 point) { 71 | if (length(point) > 10.0) { 72 | float intense = dot(normalize(point), vec3(0., 1., 0.))*.5+.5; 73 | return vec4(vec3(0.05, 0.05, 0.15), 0.) + intense * 0.2; 74 | } 75 | 76 | if (sphere(point + vec3(-1., 0.0, -1.3), 2.0) < 0.01) { 77 | return vec4(vec3(0.), 1.); 78 | } 79 | 80 | if (point.z > 4.99) { 81 | return vec4(1., 0., 0., 1.); 82 | } 83 | if (point.z < -4.99) { 84 | return vec4(0., 1., 0., 1.); 85 | } 86 | if (point.y > 4.99) { 87 | return vec4(vec3(0.9), 0.); 88 | } 89 | 90 | return vec4(0.); 91 | } 92 | 93 | // RAY MARCHER ---------- 94 | 95 | // depth, minimum distance, number of steps 96 | vec3 march(vec3 camera, vec3 ray) { 97 | float depth = START; 98 | float minDistance = END; 99 | float steps = 0.0; 100 | 101 | for (steps = 0.0; steps < float(STEPS); steps++) { 102 | float dist = scene(camera + depth * ray); 103 | minDistance = min(minDistance, dist); 104 | depth += dist; 105 | 106 | if (dist < EPSILON) { 107 | return vec3(depth, minDistance, steps - 1.0 + (dist / EPSILON)); 108 | } 109 | 110 | if (depth > END) { 111 | return vec3(END, minDistance, steps); 112 | } 113 | } 114 | 115 | return vec3(depth, minDistance, STEPS); 116 | } 117 | 118 | // SHADING AND COLORS ---------- 119 | 120 | vec3 normal(vec3 p) { 121 | return normalize(vec3( 122 | scene(vec3(p.x + EPSILON, p.y, p.z)) - scene(vec3(p.x - EPSILON, p.y, p.z)), 123 | scene(vec3(p.x, p.y + EPSILON, p.z)) - scene(vec3(p.x, p.y - EPSILON, p.z)), 124 | scene(vec3(p.x, p.y, p.z + EPSILON)) - scene(vec3(p.x, p.y, p.z - EPSILON)) 125 | )); 126 | } 127 | 128 | vec3 sample_sphere(in vec2 seed) { 129 | vec3 point = vec3(0.0); 130 | 131 | // sample point in unit cube, check if in unit sphere 132 | for (int i = 0; i < 3; i++) { 133 | point = vec3( 134 | random(seed), 135 | random(seed * 2.0), 136 | random(seed * 3.0)) * 2. - 1.; 137 | if (length(point) <= 1.0) { 138 | break; 139 | } 140 | } 141 | 142 | return point; 143 | } 144 | 145 | vec3 sample_sphere_surface(in vec2 seed) { 146 | return normalize(sample_sphere(seed)); 147 | } 148 | 149 | vec3 diffuse_dir(in vec3 normal, in vec2 seed) { 150 | return normalize(normal + sample_sphere_surface(seed)); 151 | } 152 | 153 | vec3 reflect_dir(in vec3 dir, in vec3 normal, in float roughness, in vec2 seed) { 154 | return reflect(dir, normal + sample_sphere(seed) * roughness); 155 | } 156 | 157 | // MAIN LOOP ---------- 158 | 159 | void main() { 160 | vec2 st = gl_FragCoord.xy/u_resolution.xy; 161 | st.x *= u_resolution.x/u_resolution.y; 162 | 163 | vec2 jitter = vec2( 164 | random(st + u_time), 165 | random(st + u_time * 2.0)); 166 | st += jitter * PIXEL; 167 | 168 | vec3 ray = makeRay(FOV, u_resolution.x/u_resolution.y, st); 169 | vec3 point = vec3(17.9, -1.2, 0.0); 170 | mat3 view = look(point, vec3(0., -1.2, 0.), vec3(0.0, 1.0, 0.0)); 171 | vec3 dir = view * ray; 172 | vec3 color = vec3(0.0); 173 | 174 | for (int i = 0; i < 5; i++) { 175 | vec3 marchResults = march(point, dir); 176 | float depth = marchResults.x; 177 | float minDistance = marchResults.y; 178 | float steps = marchResults.z; 179 | 180 | point = point + (depth - EPSILON * 2.0) * dir; 181 | vec4 e = emission(point); 182 | vec3 normals = normal(point); 183 | if (random(st + u_time * 3.0) < e.a) { 184 | dir = reflect_dir(dir, normals, 0.05, st + u_time * 4.0); 185 | } else { 186 | dir = diffuse_dir(normals, st + u_time * 5.0); 187 | } 188 | float occ = 1.0 - ((steps + (random(st) * 2.0 - 1.0)) / float(STEPS)); 189 | color += e.rgb; 190 | color *= occ; 191 | } 192 | 193 | color /= 3.0; 194 | color -= 0.2; 195 | color *= 2.0; 196 | color += 0.2; 197 | 198 | // float keyLight = float(smoothstep((normals.x + normals.y) / 2.0 + 0.5, 0.4, 0.6) * 0.5 + 0.5); 199 | // color = vec3(pow(occ, 2.0) * keyLight); 200 | 201 | out_color = vec4(color, 1.0); 202 | 203 | // gl_FragColor = vec4(color * 1.0 - (steps / float(STEPS)), 1.0); 204 | } 205 | -------------------------------------------------------------------------------- /demos/raymarcher/scale.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform sampler2D u_texture_0; 7 | uniform vec2 u_resolution; 8 | uniform float u_time; 9 | 10 | void main() { 11 | vec4 s = texture(u_texture_0, coords, 0.).rgba; 12 | color = vec4(s.rgb * (1.0 / s.a), 1.); 13 | } 14 | -------------------------------------------------------------------------------- /demos/raymarcher/shader.graph: -------------------------------------------------------------------------------- 1 | (let w (* 640 2)) 2 | (let h (* 480 2)) 3 | (let frame (shader "render" w h)) 4 | (let avg (shader-rec "avg" w h frame)) 5 | (let scaled (shader "scale" w h avg)) 6 | (let edges (shader "sobel" w h scaled)) 7 | (let crisp scaled) 8 | (repeat 100 9 | (let crisp (shader "denoise" w h edges crisp))) 10 | (output crisp) 11 | -------------------------------------------------------------------------------- /demos/raymarcher/sobel.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform sampler2D u_texture_0; 7 | uniform vec2 u_resolution; 8 | uniform float u_time; 9 | 10 | float lookup(vec2 p, float dx, float dy) { 11 | vec2 uv = (p.xy + vec2(dx, dy)) / u_resolution.xy; 12 | vec4 c = texture(u_texture_0, uv.xy); 13 | return 0.2126*c.r + 0.7152*c.g + 0.0722*c.b; 14 | } 15 | 16 | void main() { 17 | vec2 p = coords * u_resolution; 18 | 19 | float gx = 0.0; 20 | gx += -1.0 * lookup(p, -1.0, -1.0); 21 | gx += -2.0 * lookup(p, -1.0, 0.0); 22 | gx += -1.0 * lookup(p, -1.0, 1.0); 23 | gx += 1.0 * lookup(p, 1.0, -1.0); 24 | gx += 2.0 * lookup(p, 1.0, 0.0); 25 | gx += 1.0 * lookup(p, 1.0, 1.0); 26 | gx = gx / 8.0; 27 | 28 | float gy = 0.0; 29 | gy += -1.0 * lookup(p, -1.0, -1.0); 30 | gy += -2.0 * lookup(p, 0.0, -1.0); 31 | gy += -1.0 * lookup(p, 1.0, -1.0); 32 | gy += 1.0 * lookup(p, -1.0, 1.0); 33 | gy += 2.0 * lookup(p, 0.0, 1.0); 34 | gy += 1.0 * lookup(p, 1.0, 1.0); 35 | gy = gy / 8.0; 36 | 37 | vec4 col = vec4(vec3(abs((gx*gx + gy*gy))), 1.0); 38 | color = col; 39 | } 40 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | combine_control_expr = false 2 | wrap_comments = true 3 | comment_width = 60 4 | enum_discrim_align_threshold = 20 5 | fn_single_line = true 6 | imports_layout = "Vertical" 7 | match_block_trailing_comma = true 8 | max_width = 80 9 | imports_granularity = "Crate" 10 | newline_style = "Unix" 11 | normalize_comments = true 12 | overflow_delimited_expr = true 13 | group_imports = "StdExternalCrate" 14 | reorder_modules = false 15 | struct_field_align_threshold = 20 16 | trailing_semicolon = true 17 | use_field_init_shorthand = true 18 | use_try_shorthand = true 19 | -------------------------------------------------------------------------------- /src/graph/compute_node.rs: -------------------------------------------------------------------------------- 1 | use glium::{ 2 | uniforms::{ 3 | AsUniformValue, 4 | UniformValue, 5 | }, 6 | Texture2d, 7 | }; 8 | 9 | use crate::{ 10 | graph::{ 11 | node::Node, 12 | NodeId, 13 | UniformMap, 14 | }, 15 | util::RectStrip, 16 | }; 17 | 18 | pub type ComputeNodeFn = Box T>; 19 | 20 | /// Represents a compute node, i.e. a node that runs on the 21 | /// GPU. Still operates on uniforms, takes a single input. 22 | pub struct ComputeNode { 23 | pub func: ComputeNodeFn, 24 | pub input: NodeId, 25 | pub output: T, 26 | } 27 | 28 | impl std::fmt::Debug for ComputeNode { 29 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 30 | f.debug_struct("ComputeNode") 31 | .field("input", &self.input) 32 | .finish() 33 | } 34 | } 35 | 36 | impl Node for ComputeNode { 37 | fn inputs(&self) -> Vec { vec![self.input] } 38 | 39 | fn outputs(&self) -> (&str, UniformValue) { 40 | ("compute", self.output.as_uniform_value()) 41 | } 42 | 43 | fn texture(&self) -> Option<&Texture2d> { None } 44 | 45 | fn forward(&mut self, _rect_strip: &RectStrip, uniforms: UniformMap<'_>) { 46 | self.output = (self.func)(uniforms); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/graph/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | rc::Rc, 4 | time::Instant, 5 | }; 6 | 7 | use glium::{ 8 | backend::Context, 9 | uniforms::AsUniformValue, 10 | Texture2d, 11 | }; 12 | 13 | use crate::util::{ 14 | compile_shader, 15 | default_buffer, 16 | RectStrip, 17 | }; 18 | 19 | mod compute_node; 20 | mod node; 21 | mod shader_node; 22 | mod uniform; 23 | 24 | pub use compute_node::{ 25 | ComputeNode, 26 | ComputeNodeFn, 27 | }; 28 | pub use node::Node; 29 | pub use shader_node::{ 30 | Buffer, 31 | ShaderNode, 32 | }; 33 | use uniform::UniformMap; 34 | 35 | // TODO: remove the distinction between uniforms and 36 | // textures as inputs 37 | 38 | /// Handle that represents a particular node, 39 | /// in the context of a shader graph. 40 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 41 | pub struct NodeId(usize); 42 | 43 | /// Represents a Directed Acyclic Graph of shaders. 44 | /// Each shader is run sequentially, and can be the input to 45 | /// shaders later down the line. 46 | pub struct ShaderGraph { 47 | context: Rc, 48 | rect_strip: RectStrip, 49 | pub created: std::time::Instant, 50 | 51 | /// None is an input node. 52 | nodes: Vec>>, 53 | 54 | // TODO: use sets? 55 | inputs: Vec, 56 | outputs: Vec, 57 | } 58 | 59 | impl std::fmt::Debug for ShaderGraph { 60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 | f.debug_struct("ShaderGraph") 62 | .field("nodes", &self.nodes) 63 | .field("inputs", &self.inputs) 64 | .field("outputs", &self.outputs) 65 | .finish() 66 | } 67 | } 68 | 69 | impl ShaderGraph { 70 | /// Creates a new shader graph within a specific glium 71 | /// context. 72 | pub fn new(context: &Rc) -> ShaderGraph { 73 | ShaderGraph { 74 | context: context.clone(), 75 | rect_strip: RectStrip::new(context), 76 | nodes: vec![], 77 | inputs: vec![], 78 | outputs: vec![], 79 | created: Instant::now(), 80 | } 81 | } 82 | 83 | pub fn get_inputs(&self) -> &Vec { &self.inputs } 84 | 85 | pub fn get_outputs(&self) -> &Vec { &self.outputs } 86 | 87 | /// Adds anything that implements the `Node` trait to 88 | /// the graph Will panic if the `Node` does not 89 | /// preserve DAG structure. 90 | pub fn add_node(&mut self, node: Option>) -> NodeId { 91 | if let Some(ref node) = node { 92 | self.assert_dag(&node.inputs()) 93 | } 94 | 95 | self.nodes.push(node); 96 | NodeId(self.nodes.len() - 1) 97 | } 98 | 99 | fn assert_dag(&self, nodes: &[NodeId]) { 100 | // to preserve acyclic structure, can only ref backwards 101 | for NodeId(input) in nodes { 102 | assert!(input < &self.nodes.len()); 103 | } 104 | } 105 | 106 | /// Adds an input to the shader graph. 107 | /// Use the returned `NodeId` to mark it as a 108 | /// texture input to other shaders. 109 | pub fn add_input(&mut self) -> NodeId { 110 | let id = self.add_node(None); 111 | self.inputs.push(id); 112 | id 113 | } 114 | 115 | // TODO: custom uniforms! 116 | /// Adds a shader to a shader graph. 117 | /// Each shader has its own underlying `Texture2d` of a 118 | /// particular size. Shader is bound to the graph's 119 | /// context. 120 | pub fn add_shader( 121 | &mut self, 122 | source: &str, 123 | inputs: Vec, 124 | width: u32, 125 | height: u32, 126 | ) -> Result { 127 | self._add_shader( 128 | source, 129 | inputs, 130 | Buffer::Single(default_buffer(&self.context, width, height)), 131 | ) 132 | } 133 | 134 | /// Add a recurrent shader to the graph. 135 | pub fn add_rec_shader( 136 | &mut self, 137 | source: &str, 138 | inputs: Vec, 139 | width: u32, 140 | height: u32, 141 | ) -> Result { 142 | // set up the shader and its buffers 143 | self._add_shader( 144 | source, 145 | inputs, 146 | Buffer::new_double(|| default_buffer(&self.context, width, height)), 147 | ) 148 | } 149 | 150 | fn _add_shader( 151 | &mut self, 152 | source: &str, 153 | inputs: Vec, 154 | buffer: Buffer, 155 | ) -> Result { 156 | let shader = compile_shader(&self.context, source)?; 157 | 158 | let shader_node = ShaderNode { 159 | shader, 160 | inputs, 161 | buffer, 162 | }; 163 | Ok(self.add_node(Some(Box::new(shader_node)))) 164 | } 165 | 166 | /// Adds a compute node, which produces 167 | /// a set of uniforms for use in the next shader. 168 | pub fn add_compute( 169 | &mut self, 170 | compute_node: ComputeNode, 171 | ) -> Result { 172 | Ok(self.add_node(Some(Box::new(compute_node)))) 173 | } 174 | 175 | /// Mark a node in the graph as an output. 176 | /// When calling `forward`, this node's texture will be 177 | /// included in the output map. To access it, index 178 | /// the map with the `NodeId` this method returns. 179 | /// Returns `None` if the requested `Node` does not 180 | /// support being an output. 181 | pub fn mark_output(&mut self, id: NodeId) -> Option { 182 | if let Some(node) = &self.nodes[id.0] { 183 | node.texture()?; 184 | } 185 | 186 | if !self.outputs.contains(&id) { 187 | self.outputs.push(id) 188 | } 189 | 190 | Some(id) 191 | } 192 | 193 | fn time(created: Instant) -> f32 { 194 | ((Instant::now() - created).as_millis() as f64 / 1000.0) as f32 195 | } 196 | 197 | fn build_inputs<'a>( 198 | mut uniforms: UniformMap<'a>, 199 | previous: &'a [Option>], 200 | inputs: &'a [NodeId], 201 | input_map: &'a BTreeMap, 202 | ) -> UniformMap<'a> { 203 | // TODO: decide what other default uniforms to use 204 | // let mut uniforms = UniformMap::new(); 205 | 206 | for input in inputs.iter() { 207 | match &previous[input.0] { 208 | Some(node) => { 209 | let (kind, uniform_value) = node.outputs(); 210 | uniforms.add(kind, uniform_value); 211 | }, 212 | None => { 213 | uniforms 214 | .add("texture", input_map[input].as_uniform_value()); 215 | }, 216 | }; 217 | } 218 | 219 | uniforms 220 | } 221 | 222 | fn pull_outputs<'a>( 223 | &'a self, 224 | input_map: BTreeMap, 225 | ) -> BTreeMap { 226 | // pull all output textures 227 | // note that an input is fair game to be pulled as an output 228 | // texture 229 | let mut output_map = BTreeMap::new(); 230 | for id in self.outputs.iter() { 231 | let texture = match &self.nodes[id.0] { 232 | // unwrap: checked before insertion 233 | Some(node) => node.texture().unwrap(), 234 | None => input_map[id], 235 | }; 236 | output_map.insert(*id, texture); 237 | } 238 | 239 | output_map 240 | } 241 | 242 | // TODO: represent inputs as actual nodes, not Nones 243 | 244 | /// Does a forward pass of the entire shader graph. 245 | /// Takes a set of `N` inputs, and produces a set of `M` 246 | /// outputs. Each shader in the DAG is run, from 247 | /// front to back, previous input textures are bound 248 | /// as uniforms of the form: `u_texture_0, .., 249 | /// u_texture_n`. Use the `map!` macro to quickly 250 | /// build a `BTreeMap` to pass to this function. All 251 | /// marked outputs will be included in the output map. 252 | pub fn forward<'a>( 253 | &'a mut self, 254 | input_map: BTreeMap, 255 | ) -> BTreeMap { 256 | // ensure all input textures have been passed in 257 | // use btreemap because `inputs` is small 258 | // and node ids are cheap to compare 259 | for input in self.inputs.iter() { 260 | assert!(input_map.contains_key(input)); 261 | } 262 | 263 | for split_index in 0..self.nodes.len() { 264 | // this is a DAG, so we can only ever reference 265 | // previous nodes from the current one 266 | // we split here so we can have multiple mutible borrows. 267 | let (previous, current) = self.nodes.split_at_mut(split_index); 268 | 269 | if let Some(ref mut node) = current[0] { 270 | let mut uniforms = UniformMap::new(); 271 | let time = Self::time(self.created); 272 | uniforms.add("time", time.as_uniform_value()); 273 | 274 | let inputs = node.inputs(); 275 | let uniforms = Self::build_inputs( 276 | uniforms, &*previous, &inputs, &input_map, 277 | ); 278 | 279 | node.forward(&self.rect_strip, uniforms); 280 | } 281 | } 282 | 283 | // pulls and returns all the output textures 284 | self.pull_outputs(input_map) 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/graph/node.rs: -------------------------------------------------------------------------------- 1 | use glium::{ 2 | uniforms::UniformValue, 3 | Texture2d, 4 | }; 5 | 6 | use crate::{ 7 | graph::{ 8 | NodeId, 9 | UniformMap, 10 | }, 11 | util::RectStrip, 12 | }; 13 | 14 | /// Represents a generalized shader in a shader graph. 15 | /// Implement this trait to add arbitrary nodes to the 16 | /// shader graph. 17 | pub trait Node: std::fmt::Debug { 18 | /// Not a fan, requires allocation. 19 | fn inputs(&self) -> Vec; 20 | 21 | /// Returns (kind, uniforms) tuple. 22 | fn outputs(&self) -> (&str, UniformValue); 23 | 24 | // TODO: remove texture in favor of `outputs`? 25 | /// Denotes whether the node produces an output texture. 26 | /// This function should be consistent, 27 | /// e.g. if it returns `Some` it should always return 28 | /// `Some`. 29 | fn texture(&self) -> Option<&Texture2d>; 30 | 31 | // TODO: should I pass a rect strip or a context? 32 | // I can build a rect strip from a context, but that takes 33 | // time. Is the performance hit worth the generalized 34 | // interface? Is there a performance hit? 35 | // Should rect strip have a reference to a Rc? 36 | fn forward(&mut self, rect_strip: &RectStrip, uniforms: UniformMap); 37 | } 38 | -------------------------------------------------------------------------------- /src/graph/shader_node.rs: -------------------------------------------------------------------------------- 1 | use glium::{ 2 | uniforms::{ 3 | AsUniformValue, 4 | UniformValue, 5 | }, 6 | Program, 7 | Surface, 8 | Texture2d, 9 | }; 10 | 11 | use crate::{ 12 | graph::{ 13 | node::Node, 14 | NodeId, 15 | UniformMap, 16 | }, 17 | util::RectStrip, 18 | }; 19 | 20 | pub enum Buffer { 21 | Single(Texture2d), 22 | Double(Texture2d, Texture2d), 23 | } 24 | 25 | impl Buffer { 26 | pub fn new_double Texture2d>(f: F) -> Buffer { 27 | Buffer::Double(f(), f()) 28 | } 29 | 30 | pub fn front(&self) -> &Texture2d { 31 | match self { 32 | Buffer::Single(ref texture) => texture, 33 | Buffer::Double(ref front, _back) => front, 34 | } 35 | } 36 | pub fn back(&self) -> Option<&Texture2d> { 37 | match self { 38 | Buffer::Single(_) => None, 39 | Buffer::Double(_front, back) => Some(back), 40 | } 41 | } 42 | 43 | pub fn swap(&mut self) { 44 | // Using `Box`, the swap is cheap. 45 | if let Buffer::Double(ref mut front, ref mut back) = self { 46 | std::mem::swap(front, back); 47 | } 48 | } 49 | } 50 | 51 | // TODO: Remove `pub` on struct fields 52 | 53 | /// Represents a single shader, the inputs it expects, 54 | /// and the texture it owns that is updated in each forward 55 | /// pass. 56 | pub struct ShaderNode { 57 | pub shader: Program, 58 | pub inputs: Vec, 59 | pub buffer: Buffer, 60 | } 61 | 62 | impl std::fmt::Debug for ShaderNode { 63 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 64 | f.debug_struct("ShaderGraph") 65 | .field("inputs", &self.inputs) 66 | .finish() 67 | } 68 | } 69 | 70 | impl Node for ShaderNode { 71 | fn inputs(&self) -> Vec { self.inputs.to_owned() } 72 | 73 | fn outputs(&self) -> (&str, UniformValue) { 74 | match self.buffer { 75 | Buffer::Single(ref texture) => { 76 | ("texture", texture.as_uniform_value()) 77 | }, 78 | Buffer::Double(ref texture, _) => { 79 | ("texture", texture.as_uniform_value()) 80 | }, 81 | } 82 | } 83 | 84 | fn texture(&self) -> Option<&Texture2d> { Some(self.buffer.front()) } 85 | 86 | fn forward(&mut self, rect_strip: &RectStrip, uniforms: UniformMap) { 87 | self.buffer.swap(); 88 | 89 | let front = self.buffer.front(); 90 | let resolution = 91 | [front.get_width() as f32, front.get_height().unwrap() as f32]; 92 | 93 | let mut uniforms = uniforms; 94 | uniforms.add("resolution", UniformValue::Vec2(resolution)); 95 | if let Some(back) = self.buffer.back() { 96 | uniforms.add("previous", back.as_uniform_value()); 97 | } 98 | 99 | // taking the previous inputs, 100 | // render out the next texture using a shader, 101 | // overwriting its previous contents. 102 | front 103 | .as_surface() 104 | .draw( 105 | &rect_strip.buffer, 106 | &rect_strip.indices, 107 | &self.shader, 108 | &uniforms, 109 | &Default::default(), 110 | ) 111 | .unwrap(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/graph/uniform.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use glium::uniforms::{ 4 | UniformValue, 5 | Uniforms, 6 | }; 7 | 8 | #[derive(Default)] 9 | pub struct UniformMap<'a>(BTreeMap>>); 10 | 11 | impl<'a> UniformMap<'a> { 12 | /// Create a new empty `UniformMap`. 13 | pub fn new() -> UniformMap<'a> { UniformMap(BTreeMap::new()) } 14 | 15 | /// Inserts a kind of uniform into the uniform map. 16 | /// Will automatically number the item `u_{kind}_N`, 17 | /// starting at `N = 0`. 18 | /// Returns the index of the item, i.e. `N`. 19 | pub fn add(&mut self, kind: &str, uniform: UniformValue<'a>) -> usize { 20 | if !self.0.contains_key(kind) { 21 | self.0.insert(kind.to_string(), vec![]); 22 | } 23 | 24 | let uniforms = self.0.get_mut(kind).unwrap(); 25 | uniforms.push(uniform); 26 | uniforms.len() - 1 27 | } 28 | 29 | /// Get a specific kind of uniform at a given index. 30 | pub fn get(&self, kind: &str, index: usize) -> Option<&UniformValue<'a>> { 31 | self.0.get(kind)?.get(index) 32 | } 33 | 34 | /// Get all uniforms of a given kind. 35 | pub fn get_kind_all(&self, kind: &str) -> Option<&Vec>> { 36 | self.0.get(kind) 37 | } 38 | 39 | /// Join two maps by appending one to the other. 40 | /// This works by appending all lists of the same kind 41 | /// together, With `self` being first, and `other` 42 | /// being second. 43 | pub fn append(&mut self, other: Self) { 44 | for (key, values) in other.0.into_iter() { 45 | for value in values { 46 | self.add(&key, value); 47 | } 48 | } 49 | } 50 | } 51 | 52 | impl<'a> Uniforms for UniformMap<'a> { 53 | fn visit_values<'b, F: FnMut(&str, UniformValue<'b>)>( 54 | &'b self, 55 | mut output: F, 56 | ) { 57 | for (kind, uniforms) in self.0.iter() { 58 | // if there is only one uniform of a kind, 59 | // no subscript is required. 60 | // this lets us have `u_time` instead of `u_time_0`, 61 | // but if an input adds another `time` uniform, 62 | // the user *must* disambiguiate which one is intended. 63 | if uniforms.len() == 1 { 64 | output(&format!("u_{}", kind), uniforms[0]); 65 | } 66 | 67 | for (index, uniform) in uniforms.iter().enumerate() { 68 | output(&format!("u_{}_{}", kind, index), *uniform) 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use ffmpeg_next::{ 4 | format::{ 5 | input, 6 | Pixel, 7 | }, 8 | media::Type, 9 | software::scaling::{ 10 | context::Context, 11 | flag::Flags, 12 | }, 13 | util::frame::video::Video, 14 | }; 15 | use glium::{ 16 | backend::Facade, 17 | texture::RawImage2d, 18 | Texture2d, 19 | }; 20 | 21 | pub struct FrameStream { 22 | frames: Vec, 23 | index: usize, 24 | } 25 | 26 | impl FrameStream { 27 | pub fn new( 28 | filename: &Path, 29 | width: u32, 30 | height: u32, 31 | facade: &F, 32 | ) -> Result> { 33 | ffmpeg_next::init().unwrap(); 34 | let mut ictx = input(&filename)?; 35 | 36 | let input = ictx.streams().best(Type::Video).expect("no video stream"); 37 | let video_stream_index = input.index(); 38 | let mut decoder = input.codec().decoder().video()?; 39 | 40 | let mut scaler = Context::get( 41 | decoder.format(), 42 | decoder.width(), 43 | decoder.height(), 44 | Pixel::RGB24, 45 | width, 46 | height, 47 | Flags::BILINEAR, 48 | )?; 49 | 50 | let mut frames = vec![]; 51 | for (stream, packet) in ictx.packets() { 52 | if stream.index() == video_stream_index { 53 | decoder.send_packet(&packet)?; 54 | let mut decoded = Video::empty(); 55 | while decoder.receive_frame(&mut decoded).is_ok() { 56 | let mut rgb_frame = Video::empty(); 57 | scaler 58 | .run(&decoded, &mut rgb_frame) 59 | .expect("scaler failed"); 60 | let data = rgb_frame.data(0); 61 | let raw_image = RawImage2d::from_raw_rgb_reversed( 62 | data, 63 | (width, height), 64 | ); 65 | frames.push(Texture2d::new(facade, raw_image).unwrap()); 66 | } 67 | } 68 | } 69 | decoder.send_eof()?; 70 | Ok(Self { frames, index: 0 }) 71 | } 72 | 73 | pub fn next_frame(&mut self) -> &Texture2d { 74 | let frame = &self.frames[self.index % self.frames.len()]; 75 | self.index += 1; 76 | frame 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use glium; 2 | pub use include_dir; 3 | pub use notify; 4 | 5 | pub mod graph; 6 | #[cfg(feature = "ffmpeg")] 7 | pub mod input; 8 | pub mod lisp; 9 | pub mod map; 10 | pub mod reload; 11 | pub mod util; 12 | pub mod png; 13 | -------------------------------------------------------------------------------- /src/lisp/env.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use lexpr::Value; 4 | 5 | use crate::{ 6 | graph::{ 7 | NodeId, 8 | ShaderGraph, 9 | }, 10 | lisp::Val, 11 | }; 12 | 13 | pub type FnDef = (Vec, Vec); 14 | 15 | #[derive(Debug)] 16 | pub struct Scope { 17 | items: Vec>, 18 | } 19 | 20 | impl Scope { 21 | pub fn new() -> Self { 22 | Scope { 23 | items: vec![BTreeMap::new()], 24 | } 25 | } 26 | 27 | pub fn get(&self, name: &str) -> Result<&T, String> { 28 | for item in self.items.iter().rev() { 29 | if let Some(item) = item.get(name) { 30 | return Ok(item); 31 | } 32 | } 33 | 34 | Err(format!("Item `{}` is not defined", name)) 35 | } 36 | 37 | pub fn set(&mut self, name: String, item: T) { 38 | self.items.last_mut().unwrap().insert(name, item); 39 | } 40 | 41 | pub fn enter_scope(&mut self) { self.items.push(BTreeMap::new()) } 42 | 43 | pub fn exit_scope(&mut self) -> BTreeMap { 44 | self.items.pop().unwrap() 45 | } 46 | } 47 | 48 | pub type ExternalFn = 49 | Box Result>; 50 | pub type External = BTreeMap; 51 | 52 | // Note that functions are not first class. 53 | // We're trying to describe a graph, 54 | // not make a turing complete language ;) 55 | pub struct Env { 56 | /// Maps name to value, separate from function scope. 57 | vars: Scope, 58 | /// Maps name to args, body. 59 | functions: Scope, 60 | /// Maps shader name to shader source. 61 | shaders: BTreeMap, 62 | /// Maps names to rust functions that construct 63 | /// subgraphs. 64 | external: External, 65 | } 66 | 67 | impl std::fmt::Debug for Env { 68 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 69 | f.debug_struct("Env") 70 | .field("vars", &self.vars) 71 | .field("functions", &self.functions) 72 | .field("shaders", &self.shaders.keys().collect::>()) 73 | .field("external", &self.external.keys().collect::>()) 74 | .finish() 75 | } 76 | } 77 | 78 | impl Env { 79 | pub fn new(shaders: BTreeMap, external: External) -> Env { 80 | Env { 81 | vars: Scope::new(), 82 | functions: Scope::new(), 83 | shaders, 84 | external, 85 | } 86 | } 87 | 88 | pub fn get(&self, name: &str) -> Result<&Val, String> { 89 | self.vars.get(name) 90 | } 91 | 92 | pub fn set(&mut self, name: String, item: Val) { self.vars.set(name, item) } 93 | 94 | pub fn get_fn(&self, name: &str) -> Result<&FnDef, String> { 95 | self.functions.get(name) 96 | } 97 | 98 | pub fn set_fn(&mut self, name: String, item: FnDef) { 99 | self.functions.set(name, item) 100 | } 101 | 102 | pub fn enter_scope(&mut self) { 103 | self.vars.enter_scope(); 104 | self.functions.enter_scope(); 105 | } 106 | 107 | pub fn exit_scope(&mut self) { 108 | self.vars.exit_scope(); 109 | self.functions.exit_scope(); 110 | } 111 | 112 | pub fn shader(&self, name: &str) -> Result<&String, String> { 113 | self.shaders.get(name).ok_or(format!( 114 | "Could not load shader `{}`, it is not defined", 115 | name 116 | )) 117 | } 118 | 119 | pub fn external(&self, name: &str) -> Result<&ExternalFn, String> { 120 | self.external.get(name).ok_or(format!( 121 | "Could not load external function `{}`, it is not defined", 122 | name 123 | )) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/lisp/load.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | fs, 4 | path::Path, 5 | }; 6 | 7 | /// Loads a directory of shaders into a big ol' `BTreeMap`. 8 | pub fn load_shaders(path: T) -> Result, String> 9 | where 10 | T: AsRef, 11 | { 12 | let mut map = BTreeMap::new(); 13 | let files = fs::read_dir(path) 14 | .map_err(|_| "Could not read shader directory".to_string())?; 15 | 16 | for p in files { 17 | // some type stuff, you know the deal 18 | let path = p.map_err(|_| "Got a bad file path".to_string())?.path(); 19 | 20 | // only include `.frag` files 21 | if path.is_dir() { 22 | continue; 23 | } 24 | match path.extension() { 25 | Some(x) if x.to_str() == Some("frag") => (), 26 | _ => { 27 | continue; 28 | }, 29 | } 30 | 31 | // get the key and value, insertomundo! 32 | let name = path 33 | .file_stem() 34 | .ok_or("Could not infer shader name from file name")? 35 | .to_str() 36 | .ok_or("Could not convert file name to UTF8 string")? 37 | .to_string(); 38 | let contents = fs::read_to_string(path) 39 | .map_err(|_| "Could not get shader contents")?; 40 | map.insert(name, contents); 41 | } 42 | 43 | Ok(map) 44 | } 45 | -------------------------------------------------------------------------------- /src/lisp/mod.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use glium::backend::Context; 4 | use lexpr::Value; 5 | 6 | use crate::{ 7 | graph::{ 8 | NodeId, 9 | ShaderGraph, 10 | }, 11 | reload::ShaderDir, 12 | }; 13 | 14 | mod env; 15 | mod load; 16 | mod val; 17 | 18 | pub use env::{ 19 | Env, 20 | External, 21 | }; 22 | pub use load::load_shaders; 23 | pub use val::Val; 24 | 25 | /// Takes a source string of lisp that represents the shader 26 | /// graph to be constructed, And constructs that shader 27 | /// graph within a certain context, Provided a map of all 28 | /// shaders avaliable for the construction of the graph. You 29 | /// can use [`load_shaders`] to load a directory into a map. 30 | pub fn graph_from_sexp( 31 | context: &Rc, 32 | shader_dir: ShaderDir, 33 | external: External, 34 | ) -> Result { 35 | let mut graph = ShaderGraph::new(context); 36 | let mut env = Env::new(shader_dir.shaders, external); 37 | 38 | // little hack to get a list of expressions 39 | let sexp = lexpr::from_str(&format!("({})", shader_dir.lisp)) 40 | .map_err(|e| format!("{}", e))?; 41 | 42 | begin(&mut graph, &mut env, &sexp)?; 43 | 44 | Ok(graph) 45 | } 46 | 47 | fn into_iter(sexp: &Value) -> Result, String> { 48 | sexp.list_iter() 49 | .ok_or_else(|| "Expected a form".to_string()) 50 | } 51 | 52 | fn next_item<'a>( 53 | iter: &mut lexpr::cons::ListIter<'a>, 54 | ) -> Result<&'a Value, String> { 55 | iter.next() 56 | .ok_or_else(|| "Expected a non-empty form".to_string()) 57 | } 58 | 59 | fn next_symbol<'a>( 60 | iter: &mut lexpr::cons::ListIter<'a>, 61 | ) -> Result<&'a str, String> { 62 | return next_item(iter)? 63 | .as_symbol() 64 | .ok_or_else(|| "Expected a symbol".to_string()); 65 | } 66 | 67 | fn iter_finish(iter: lexpr::cons::ListIter<'_>) -> Result<(), String> { 68 | if !iter.is_empty() { 69 | Err("Unexpected extra args while parsing form".to_string()) 70 | } else { 71 | Ok(()) 72 | } 73 | } 74 | 75 | fn begin( 76 | mut graph: &mut ShaderGraph, 77 | env: &mut Env, 78 | sexp: &Value, 79 | ) -> Result<(), String> { 80 | for declaration in into_iter(sexp)? { 81 | declare(&mut graph, env, declaration)?; 82 | } 83 | 84 | Ok(()) 85 | } 86 | 87 | fn declare( 88 | graph: &mut ShaderGraph, 89 | env: &mut Env, 90 | sexp: &Value, 91 | ) -> Result<(), String> { 92 | let mut iter = into_iter(sexp)?; 93 | let keyword = next_symbol(&mut iter)?; 94 | 95 | match keyword { 96 | "input" => { 97 | let var = next_symbol(&mut iter)?; 98 | let input = graph.add_input(); 99 | env.set(var.to_string(), Val::Node(input)); 100 | }, 101 | "output" => { 102 | let id = next_symbol(&mut iter)?; 103 | graph.mark_output(env.get(id)?.to_node()?); 104 | }, 105 | "define" => { 106 | // get the form defining the signature 107 | let list = next_item(&mut iter)?; 108 | let mut signature = into_iter(list)?; 109 | let name = next_symbol(&mut signature)?; 110 | 111 | // extract all the arguments 112 | let mut args = vec![]; 113 | for arg in signature { 114 | args.push( 115 | arg.as_symbol() 116 | .ok_or_else(|| { 117 | "Expected symbol in signature".to_string() 118 | })? 119 | .to_string(), 120 | ); 121 | } 122 | 123 | let forms: Vec = iter.map(|f| f.to_owned()).collect(); 124 | if forms.is_empty() { 125 | return Err(format!( 126 | "Definition `{}` must have at least one expression in body", 127 | name 128 | )); 129 | } 130 | env.set_fn(name.to_string(), (args, forms)); 131 | return Ok(()); 132 | }, 133 | "let" => { 134 | let var = next_symbol(&mut iter)?; 135 | let val = expr(graph, env, next_item(&mut iter)?)?; 136 | env.set(var.to_string(), val); 137 | }, 138 | "repeat" => { 139 | let times = expr(graph, env, next_item(&mut iter)?)?.to_nat()?; 140 | let forms: Vec = iter.map(|f| f.to_owned()).collect(); 141 | for _ in 0..times { 142 | for form in forms.iter() { 143 | declare(graph, env, form)?; 144 | } 145 | } 146 | return Ok(()); 147 | }, 148 | other => { 149 | return Err(format!( 150 | "Expected a statement keyword, found `{}`", 151 | other 152 | )) 153 | }, 154 | } 155 | 156 | iter_finish(iter) 157 | } 158 | 159 | fn expr( 160 | graph: &mut ShaderGraph, 161 | env: &mut Env, 162 | value: &Value, 163 | ) -> Result { 164 | let val = match value { 165 | Value::Number(n) => { 166 | if let Some(float) = n.as_f64() { 167 | Val::Number(float) 168 | } else { 169 | return Err("unexpected number type".to_string()); 170 | } 171 | }, 172 | 173 | Value::Bool(b) => Val::Bool(*b), 174 | Value::String(_) => Val::String(value.as_str().unwrap().to_string()), 175 | Value::Symbol(_) => env.get(value.as_symbol().unwrap())?.clone(), 176 | 177 | x if x.is_list() => node(graph, env, x)?, 178 | 179 | other => return Err(format!("unexpected value `{}`", other)), 180 | }; 181 | 182 | Ok(val) 183 | } 184 | 185 | fn shader( 186 | graph: &mut ShaderGraph, 187 | env: &mut Env, 188 | mut iter: lexpr::cons::ListIter<'_>, 189 | ) -> Result<(String, u32, u32, Vec), String> { 190 | let name = expr(graph, env, next_item(&mut iter)?)?.to_string()?; 191 | let width = expr(graph, env, next_item(&mut iter)?)?.to_nat()?; 192 | let height = expr(graph, env, next_item(&mut iter)?)?.to_nat()?; 193 | let mut inputs = vec![]; 194 | for remaining in iter { 195 | let node_id = expr(graph, env, remaining)?.to_node()?; 196 | inputs.push(node_id); 197 | } 198 | Ok((name, width as u32, height as u32, inputs)) 199 | } 200 | 201 | fn external( 202 | graph: &mut ShaderGraph, 203 | env: &mut Env, 204 | mut iter: lexpr::cons::ListIter<'_>, 205 | ) -> Result<(String, Vec), String> { 206 | let name = expr(graph, env, next_item(&mut iter)?)?.to_string()?; 207 | let mut inputs = vec![]; 208 | for remaining in iter { 209 | let node_id = expr(graph, env, remaining)?.to_node()?; 210 | inputs.push(node_id); 211 | } 212 | Ok((name, inputs)) 213 | } 214 | 215 | fn node( 216 | graph: &mut ShaderGraph, 217 | env: &mut Env, 218 | sexp: &Value, 219 | ) -> Result { 220 | let mut iter = into_iter(sexp)?; 221 | let function = next_symbol(&mut iter)?; 222 | 223 | match function { 224 | "shader" => { 225 | let (name, width, height, inputs) = shader(graph, env, iter)?; 226 | let node_id = graph.add_shader( 227 | env.shader(&name)?, 228 | inputs, 229 | width as u32, 230 | height as u32, 231 | )?; 232 | Ok(Val::Node(node_id)) 233 | }, 234 | "shader-param" => { 235 | // get the shader we'll be running the transformations 236 | // against 237 | let decl = into_iter(next_item(&mut iter)?)?; 238 | let (name, width, height, inputs) = shader(graph, env, decl)?; 239 | let mut source = env.shader(&name)?.to_string(); 240 | 241 | // parse the substitutions to be applied 242 | // let mut subst = vec![]; 243 | for form in iter { 244 | source = subst(graph, env, form, source)?; 245 | } 246 | 247 | let node_id = graph.add_shader( 248 | &source, 249 | inputs, 250 | width as u32, 251 | height as u32, 252 | )?; 253 | Ok(Val::Node(node_id)) 254 | }, 255 | "shader-rec" => { 256 | let (name, width, height, inputs) = shader(graph, env, iter)?; 257 | let node_id = graph.add_rec_shader( 258 | env.shader(&name)?, 259 | inputs, 260 | width as u32, 261 | height as u32, 262 | )?; 263 | Ok(Val::Node(node_id)) 264 | }, 265 | "extern" => { 266 | let (name, inputs) = external(graph, env, iter)?; 267 | let adder = env.external(&name)?; 268 | let node_id = adder(graph, &inputs).map_err(|e| { 269 | format!("While adding external function `{}`: {}", name, e) 270 | })?; 271 | Ok(Val::Node(node_id)) 272 | }, 273 | user_defined => { 274 | // evaluate the arguments (pass by value) 275 | let mut args = vec![]; 276 | for arg in iter { 277 | args.push(expr(graph, env, arg)?); 278 | } 279 | 280 | if let Some(val) = builtin(user_defined, &args) { 281 | return val; 282 | } 283 | 284 | // get the function 285 | let (params, body) = env.get_fn(user_defined)?.clone(); 286 | 287 | // check things match up before calling 288 | if params.len() != args.len() { 289 | return Err(format!( 290 | "function `{}` expected {} args, but found {}", 291 | user_defined, 292 | params.len(), 293 | args.len(), 294 | )); 295 | } 296 | 297 | // evaluate in new scope, declare arguments 298 | // recursive definitions will bap the stack, so please don't 299 | // write them 300 | env.enter_scope(); 301 | for (name, val) in params.iter().zip(args.iter()) { 302 | env.set(name.to_string(), val.to_owned()) 303 | } 304 | 305 | // unwrap is ok because length is checked when adding 306 | // definiton 307 | let last = body.last().unwrap(); 308 | let declarations = &body[..body.len() - 1]; 309 | for declaration in declarations { 310 | declare(graph, env, declaration)?; 311 | } 312 | 313 | // TODO: multiple returns how? 314 | // last value must be an expression, return it 315 | let ret = expr(graph, env, last)?.to_node()?; 316 | env.exit_scope(); 317 | Ok(Val::Node(ret)) 318 | }, 319 | } 320 | } 321 | 322 | fn builtin(name: &str, args: &[Val]) -> Option> { 323 | let result: fn(Vec) -> Val = match name { 324 | "+" => |n| Val::Number(n.into_iter().sum()), 325 | "-" => |n| { 326 | let iter = n.into_iter(); 327 | Val::Number(iter.reduce(|a, b| a - b).unwrap_or(0.0)) 328 | }, 329 | "*" => |n| Val::Number(n.into_iter().product()), 330 | "/" => |n| { 331 | let iter = n.into_iter(); 332 | Val::Number(iter.reduce(|a, b| a / b).unwrap_or(1.0)) 333 | }, 334 | _ => { 335 | return None; 336 | }, 337 | }; 338 | 339 | let numbers = args 340 | .iter() 341 | .map(|v| v.to_float()) 342 | .collect::, _>>(); 343 | 344 | let numbers = match numbers { 345 | Err(e) => { 346 | return Some(Err(e)); 347 | }, 348 | Ok(ok) => ok, 349 | }; 350 | 351 | Some(Ok(result(numbers))) 352 | } 353 | 354 | fn subst( 355 | graph: &mut ShaderGraph, 356 | env: &mut Env, 357 | form: &Value, 358 | source: String, 359 | ) -> Result { 360 | let mut subst_iter = into_iter(form)?; 361 | let op = next_symbol(&mut subst_iter)?; 362 | 363 | let (name, subst) = match op { 364 | "define" => { 365 | let name = 366 | expr(graph, env, next_item(&mut subst_iter)?)?.to_string()?; 367 | let val = 368 | expr(graph, env, next_item(&mut subst_iter)?)?.to_string()?; 369 | let subst = format!("#define {} {}", name, val); 370 | (name, subst) 371 | }, 372 | "ifdef" => { 373 | let name = 374 | expr(graph, env, next_item(&mut subst_iter)?)?.to_string()?; 375 | let should_define = 376 | expr(graph, env, next_item(&mut subst_iter)?)?.to_bool()?; 377 | if should_define { 378 | (name.clone(), format!("#define {} 1", name)) 379 | } else { 380 | (name, String::new()) 381 | } 382 | }, 383 | other => { 384 | return Err(format!("Invalid param substitution type `{}`", other)); 385 | }, 386 | }; 387 | 388 | iter_finish(subst_iter)?; 389 | return Ok(source.replace(&format!("<{}>", name), &subst)); 390 | } 391 | -------------------------------------------------------------------------------- /src/lisp/val.rs: -------------------------------------------------------------------------------- 1 | use crate::graph::NodeId; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum Val { 5 | Node(NodeId), 6 | Number(f64), 7 | Bool(bool), 8 | String(String), 9 | } 10 | 11 | impl Val { 12 | pub fn to_node(&self) -> Result { 13 | match self { 14 | Val::Node(n) => Ok(*n), 15 | other => Err(format!( 16 | "Type mismatch: expected a Node, found `{:?}`", 17 | other 18 | )), 19 | } 20 | } 21 | 22 | pub fn to_nat(&self) -> Result { 23 | match self { 24 | Val::Number(u) => Ok(*u as usize), 25 | other => Err(format!( 26 | "Type mismatch: expected a Number, found `{:?}`", 27 | other 28 | )), 29 | } 30 | } 31 | 32 | pub fn to_float(&self) -> Result { 33 | match self { 34 | Val::Number(u) => Ok(*u), 35 | other => Err(format!( 36 | "Type mismatch: expected a Number, found `{:?}`", 37 | other 38 | )), 39 | } 40 | } 41 | 42 | pub fn to_string(&self) -> Result { 43 | match self { 44 | Val::String(s) => Ok(s.to_string()), 45 | Val::Number(f) => Ok(format!("{}", f)), 46 | other => Err(format!( 47 | "Type mismatch: expected a String, found `{:?}`", 48 | other 49 | )), 50 | } 51 | } 52 | 53 | pub fn to_bool(&self) -> Result { 54 | match self { 55 | Val::Bool(b) => Ok(*b), 56 | other => Err(format!( 57 | "Type mismatch: expected a Boolean, found `{:?}`", 58 | other 59 | )), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | ffi::OsStr, 4 | fs, 5 | path::PathBuf, 6 | time::{ 7 | Duration, 8 | Instant, 9 | }, 10 | }; 11 | 12 | use glium::{ 13 | backend::Facade, 14 | glutin::{ 15 | event::{ 16 | Event, 17 | WindowEvent, 18 | }, 19 | event_loop::ControlFlow, 20 | }, 21 | Surface, 22 | }; 23 | use shadergarden::{ 24 | lisp, 25 | map, 26 | png, 27 | reload, 28 | util, 29 | }; 30 | use structopt::{ 31 | clap::AppSettings, 32 | StructOpt, 33 | }; 34 | 35 | /// Waits a specific number of nanoseconds before rendering 36 | /// the next frame. Returns a control_flow that should be 37 | /// set in the event loop. This is based on the current 38 | /// time, so must be recalculated every frame. 39 | fn wait_nanos(nanos: u64) -> ControlFlow { 40 | let wait = Instant::now() + Duration::from_nanos(nanos); 41 | ControlFlow::WaitUntil(wait) 42 | } 43 | 44 | /// Simple event handler that quits when the window is 45 | /// closed. Must be called from within the event loop. 46 | pub fn handle_event(event: Event<()>, control_flow: &mut ControlFlow) { 47 | if let Event::WindowEvent { 48 | event: WindowEvent::CloseRequested, 49 | .. 50 | } = event 51 | { 52 | *control_flow = ControlFlow::Exit; 53 | } 54 | } 55 | 56 | pub fn dir(path: &OsStr) -> PathBuf { 57 | if path == "." { 58 | std::env::current_dir().expect("Can not determine package directory") 59 | } else { 60 | PathBuf::from(path) 61 | } 62 | } 63 | 64 | pub fn package_dir(path: &OsStr) -> PathBuf { 65 | if path == "." { 66 | std::env::current_dir().expect("Can not determine package directory") 67 | } else { 68 | PathBuf::from(path) 69 | } 70 | } 71 | 72 | #[derive(StructOpt, Debug)] 73 | struct New { 74 | #[structopt(default_value = ".", parse(from_os_str = package_dir))] 75 | project: PathBuf, 76 | } 77 | 78 | #[derive(StructOpt, Debug)] 79 | struct Run { 80 | #[structopt(default_value = ".", parse(from_os_str = package_dir))] 81 | project: PathBuf, 82 | #[structopt(short, long)] 83 | graph: Option, 84 | #[structopt(short, long, default_value = "512")] 85 | width: u32, 86 | #[structopt(short, long, default_value = "512")] 87 | height: u32, 88 | #[structopt(short, long)] 89 | inputs: Vec, 90 | } 91 | 92 | #[derive(StructOpt, Debug)] 93 | struct Render { 94 | #[structopt(flatten)] 95 | run: Run, 96 | #[structopt(short, long)] 97 | output: PathBuf, 98 | /// Starting frame 99 | #[structopt(short, long, default_value = "0")] 100 | start: u64, 101 | /// Ending frame 102 | #[structopt(short, long, default_value = "150")] 103 | end: u64, 104 | #[structopt(long, default_value = "30")] 105 | fps: f64, 106 | } 107 | 108 | #[derive(StructOpt, Debug)] 109 | #[structopt(name = "Shader Garden", bin_name = "shadergarden", about, global_settings(&[AppSettings::ColoredHelp, AppSettings::DeriveDisplayOrder]))] 110 | enum Cli { 111 | New(New), 112 | Run(Run), 113 | Render(Render), 114 | } 115 | 116 | /// Main function 117 | /// Runs the shader graph, and displays the output in the 118 | /// window. If changes are detected, we rebuild the shader 119 | /// graph and swap it out. 120 | fn main() { 121 | // parse arguments and extract 122 | let args = Cli::from_args(); 123 | 124 | match args { 125 | Cli::Run(r) => run(r), 126 | Cli::Render(r) => render(r), 127 | Cli::New(n) => new(n.project), 128 | } 129 | } 130 | 131 | fn new(path: PathBuf) { 132 | fs::create_dir_all(&path).unwrap(); 133 | if let Ok(mut dir) = fs::read_dir(&path) { 134 | if dir.next().is_some() { 135 | eprintln!("[fatal] Specified project path is not empty"); 136 | panic!(); 137 | } 138 | } 139 | 140 | if reload::BASE_PROJECT.extract(&path).is_err() { 141 | eprintln!("[fatal] Could not create base project"); 142 | panic!(); 143 | } 144 | 145 | eprintln!( 146 | "[info] successfully created new project in `{}`", 147 | path.display() 148 | ); 149 | } 150 | 151 | // TODO: factor out common parts of render and run 152 | 153 | fn render(render: Render) { 154 | let args = render.run; 155 | let lisp_config = args 156 | .graph 157 | .to_owned() 158 | .unwrap_or_else(|| args.project.join("shader.graph")); 159 | let inputs = args.inputs; 160 | 161 | // set up the main event loop 162 | let (event_loop, display) = util::create( 163 | "Shader Garden Renderer".to_string(), 164 | args.width as f64, 165 | args.height as f64, 166 | ); 167 | 168 | // set up hot code reloading 169 | let shader_dir = reload::ShaderDir::new_from_dir(args.project, lisp_config) 170 | .expect("Could not load initial shader directory"); 171 | let mut graph = 172 | lisp::graph_from_sexp(display.get_context(), shader_dir, map! {}) 173 | .map_err(|e| { 174 | eprintln!("[fatal] Could not build initial graph:"); 175 | eprintln!("{}", e); 176 | panic!(); 177 | }) 178 | .unwrap(); 179 | 180 | eprintln!("[info] Built initial graph"); 181 | 182 | // build a table of textures 183 | #[cfg(feature = "ffmpeg")] 184 | let mut input_textures = 185 | util::input_textures(&display, &inputs, args.width, args.height); 186 | 187 | #[cfg(not(feature = "ffmpeg"))] 188 | assert!(inputs.is_empty(), "Inputs are not supported when running without ffmpeg"); 189 | 190 | eprintln!("[info] Starting Render..."); 191 | 192 | let mut frame_number = 0; 193 | let frames_output = render.output; 194 | let frame_start = render.start; 195 | let frame_end = render.end; 196 | let frame_nanos = (1000000000.0 / render.fps) as u64; 197 | 198 | event_loop.run(move |event, _, mut control_flow| { 199 | // waits until next frame, keep at top 200 | *control_flow = wait_nanos(0); 201 | handle_event(event, &mut control_flow); 202 | 203 | // get the input and output handles 204 | let input_nodes = graph.get_inputs(); 205 | let output = if let [output] = graph.get_outputs().as_slice() { 206 | *output 207 | } else { 208 | eprintln!("[fatal] Graph has invalid output signature."); 209 | panic!(); 210 | }; 211 | 212 | #[cfg(not(feature = "ffmpeg"))] 213 | assert!(input_nodes.is_empty()); 214 | 215 | #[cfg(feature = "ffmpeg")] 216 | assert_eq!( 217 | input_nodes.len(), 218 | input_textures.len(), 219 | "The number of graph inputs and provided textures does not match up", 220 | ); 221 | 222 | // render the shader graph, display the primary output 223 | #[allow(unused_mut)] 224 | let mut input_map = BTreeMap::new(); 225 | 226 | #[cfg(feature = "ffmpeg")] 227 | for (node_id, texture) in input_nodes.iter().zip(input_textures.iter_mut()) { 228 | input_map.insert(*node_id, texture.next_frame()); 229 | } 230 | 231 | // dumb hack to make the playback smooth(er) 232 | graph.created = std::time::Instant::now() 233 | - std::time::Duration::from_nanos(frame_nanos * frame_number); 234 | let output_map = graph.forward(input_map); 235 | 236 | // set up the draw target and draw 237 | let mut target = display.draw(); 238 | target.clear_color(0.0, 0.0, 0.0, 1.0); 239 | let texture = output_map[&output]; 240 | util::texture(&display, &mut target, texture); 241 | target.finish().unwrap(); 242 | 243 | if frame_number >= frame_start { 244 | png::write_png(texture, &frames_output.join( 245 | format!("frame-{:0>4}.png", frame_number - frame_start) 246 | )); 247 | } 248 | if frame_number > frame_end { 249 | panic!("Finished render, bailing pathetically"); 250 | } 251 | frame_number += 1 252 | }); 253 | } 254 | 255 | fn run(args: Run) { 256 | let lisp_config = args 257 | .graph 258 | .to_owned() 259 | .unwrap_or_else(|| args.project.join("shader.graph")); 260 | let inputs = args.inputs; 261 | 262 | // set up the main event loop 263 | let (event_loop, display) = util::create( 264 | "Shader Garden Playground".to_string(), 265 | args.width as f64, 266 | args.height as f64, 267 | ); 268 | 269 | // set up hot code reloading 270 | let mut watcher = reload::ShaderGraphWatcher::new_watch_dir( 271 | display.get_context(), 272 | args.project, 273 | lisp_config, 274 | ) 275 | .map_err(|e| { 276 | eprintln!("[fatal] Could not build initial graph:"); 277 | eprintln!("{}", e); 278 | panic!(); 279 | }) 280 | .unwrap(); 281 | eprintln!("[info] Built initial graph"); 282 | 283 | // build a table of textures 284 | #[cfg(feature = "ffmpeg")] 285 | let mut input_textures = 286 | util::input_textures(&display, &inputs, args.width, args.height); 287 | 288 | #[cfg(not(feature = "ffmpeg"))] 289 | assert!(inputs.is_empty(), "Inputs are not supported when running without ffmpeg"); 290 | 291 | eprintln!("[info] Starting..."); 292 | 293 | event_loop.run(move |event, _, mut control_flow| { 294 | // waits until next frame, keep at top 295 | *control_flow = wait_nanos(16_666_667); 296 | handle_event(event, &mut control_flow); 297 | 298 | // get the graph, notify if updated 299 | let (graph, watch_result) = watcher.graph(); 300 | match watch_result { 301 | reload::WatchResult::NoChange => (), 302 | reload::WatchResult::Rebuilt => eprintln!("[info] Graph rebuilt"), 303 | reload::WatchResult::Err(e) => { 304 | eprintln!("[warn] Could not rebuild graph:"); 305 | eprintln!("{}", e); 306 | } 307 | } 308 | 309 | // get the input and output handles 310 | let input_nodes = graph.get_inputs(); 311 | let output = if let [output] = graph.get_outputs().as_slice() { 312 | *output 313 | } else { 314 | eprintln!("[fatal] Graph has invalid output signature."); 315 | panic!(); 316 | }; 317 | 318 | #[cfg(not(feature = "ffmpeg"))] 319 | assert!(input_nodes.is_empty()); 320 | 321 | #[cfg(feature = "ffmpeg")] 322 | assert_eq!( 323 | input_nodes.len(), 324 | input_textures.len(), 325 | "The number of graph inputs and provided textures does not match up", 326 | ); 327 | 328 | // render the shader graph, display the primary output 329 | #[allow(unused_mut)] 330 | let mut input_map = BTreeMap::new(); 331 | 332 | #[cfg(feature = "ffmpeg")] 333 | for (node_id, texture) in input_nodes.iter().zip(input_textures.iter_mut()) { 334 | input_map.insert(*node_id, texture.next_frame()); 335 | } 336 | 337 | let output_map = graph.forward(input_map); 338 | 339 | // set up the draw target and draw 340 | let mut target = display.draw(); 341 | target.clear_color(0.0, 0.0, 0.0, 1.0); 342 | util::texture(&display, &mut target, output_map[&output]); 343 | target.finish().unwrap(); 344 | }); 345 | } 346 | -------------------------------------------------------------------------------- /src/map.rs: -------------------------------------------------------------------------------- 1 | /// Build a b-tree map using a nice syntax like in other 2 | /// languages. 3 | #[macro_export] 4 | macro_rules! map { 5 | // handle the case w/ a trailing comma by removing the comma 6 | ($($key:expr => $val:expr),*,) => ( 7 | $crate::map!($($key => $val),*) 8 | ); 9 | // construct a btree map 10 | ($($key:expr => $val:expr),*) => ({ 11 | #[allow(unused_mut)] 12 | let mut b_map = ::std::collections::BTreeMap::new(); 13 | $(b_map.insert($key, $val);)* 14 | b_map 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/png.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::Cursor, 3 | path::Path, 4 | }; 5 | 6 | use glium::texture::{ 7 | RawImage2d, 8 | Texture2d, 9 | }; 10 | use image::{ 11 | ImageBuffer, 12 | ImageFormat, 13 | Rgba, 14 | }; 15 | 16 | /// Pretty neat macro right here. Takes an image path, loads 17 | /// it, and converts it to a raw_image. 18 | #[macro_export] 19 | macro_rules! include_png { 20 | ($path:literal) => {{ 21 | let bytes = include_bytes!($path); 22 | crate::png::image_from_bytes(bytes) 23 | }}; 24 | } 25 | 26 | pub fn image_from_bytes<'a>(bytes: Vec) -> RawImage2d<'a, u8> { 27 | let cursor = Cursor::new(&bytes); 28 | let image = image::load(cursor, ImageFormat::Png).unwrap().to_rgba8(); 29 | let image_dimensions = image.dimensions(); 30 | 31 | RawImage2d::from_raw_rgba_reversed(&image.into_raw(), image_dimensions) 32 | } 33 | 34 | pub fn load_png(path: &Path) -> RawImage2d { 35 | let bytes = std::fs::read(path).expect("Could not read input image"); 36 | image_from_bytes(bytes) 37 | } 38 | 39 | pub fn write_png(texture: &Texture2d, path: &Path) { 40 | let mut buffer = 41 | ImageBuffer::new(texture.width() as u32, texture.height() as u32); 42 | 43 | let sink: Vec> = texture.read(); 44 | 45 | for (y, row) in sink.iter().rev().enumerate() { 46 | for (x, pixel) in row.iter().enumerate() { 47 | buffer.put_pixel( 48 | x as u32, 49 | y as u32, 50 | Rgba([pixel.0, pixel.1, pixel.2, pixel.3]), 51 | ); 52 | } 53 | } 54 | 55 | buffer.save(&path).expect("Could not write frame"); 56 | println!("Saved frame {}", path.display()); 57 | } 58 | -------------------------------------------------------------------------------- /src/reload/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod shader_dir; 2 | pub mod watcher; 3 | 4 | pub use shader_dir::{ 5 | ShaderDir, 6 | BASE_PROJECT, 7 | }; 8 | pub use watcher::{ 9 | ShaderGraphWatcher, 10 | WatchResult, 11 | }; 12 | -------------------------------------------------------------------------------- /src/reload/shader_dir.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | ffi::OsStr, 4 | fs, 5 | path::Path, 6 | }; 7 | 8 | use include_dir::{ 9 | include_dir, 10 | Dir, 11 | }; 12 | 13 | pub const BASE_PROJECT: Dir = include_dir!("./demos/base"); 14 | 15 | /// Represents a directory of shaders, and a shader graph 16 | /// lisp configuration file. 17 | pub struct ShaderDir { 18 | pub lisp: String, 19 | pub shaders: BTreeMap, 20 | } 21 | 22 | impl ShaderDir { 23 | /// Creates a new `ShaderDir` from component parts. 24 | pub fn new( 25 | lisp_graph: String, 26 | shaders: BTreeMap, 27 | ) -> ShaderDir { 28 | ShaderDir { 29 | lisp: lisp_graph, 30 | shaders, 31 | } 32 | } 33 | 34 | // TODO: abstract and combine following new methods 35 | 36 | /// Creates a `ShaderDir` from a directory included at 37 | /// compile time. Note that `lisp_graph` must be a 38 | /// parsable lisp expression, not a path. 39 | pub fn new_from_included( 40 | dir: Dir, 41 | lisp_graph: String, 42 | ) -> Result { 43 | let mut shaders = BTreeMap::new(); 44 | for file in dir.files() { 45 | if file.path().is_dir() 46 | || file.path().extension() != Some(OsStr::new("frag")) 47 | { 48 | continue; 49 | } 50 | 51 | // get the key and value, insertomundo! 52 | let name = file 53 | .path() 54 | .file_stem() 55 | .ok_or("Could not infer shader name from file name")? 56 | .to_str() 57 | .ok_or("Could not convert file name to UTF8 string")? 58 | .to_string(); 59 | let contents = String::from_utf8(file.contents().to_vec()) 60 | .map_err(|_| "Could not get shader contents")?; 61 | shaders.insert(name, contents); 62 | } 63 | 64 | Ok(ShaderDir { 65 | lisp: lisp_graph, 66 | shaders, 67 | }) 68 | } 69 | 70 | /// Creates a new `ShaderDir` from a directory. 71 | pub fn new_from_dir(path: T, config: T) -> Result 72 | where 73 | T: AsRef, 74 | { 75 | let lisp = fs::read_to_string(&config).map_err(|_| { 76 | format!( 77 | "Could not read `{}` in shader directory", 78 | config.as_ref().to_str().unwrap() 79 | ) 80 | })?; 81 | 82 | let mut shaders = BTreeMap::new(); 83 | let files = fs::read_dir(path) 84 | .map_err(|_| "Could not read shader directory".to_string())?; 85 | 86 | for p in files { 87 | // some type stuff, you know the deal 88 | let path = p.map_err(|_| "Got a bad file path".to_string())?.path(); 89 | 90 | // only include `.frag` files 91 | if path.is_dir() || path.extension() != Some(OsStr::new("frag")) { 92 | continue; 93 | } 94 | 95 | // get the key and value, insertomundo! 96 | let name = path 97 | .file_stem() 98 | .ok_or("Could not infer shader name from file name")? 99 | .to_str() 100 | .ok_or("Could not convert file name to UTF8 string")? 101 | .to_string(); 102 | let contents = fs::read_to_string(path) 103 | .map_err(|_| "Could not get shader contents")?; 104 | shaders.insert(name, contents); 105 | } 106 | 107 | Ok(ShaderDir { lisp, shaders }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/reload/watcher.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | path::{ 3 | Path, 4 | PathBuf, 5 | }, 6 | rc::Rc, 7 | sync::{ 8 | atomic::{ 9 | AtomicBool, 10 | Ordering, 11 | }, 12 | Arc, 13 | }, 14 | time::{ 15 | Duration, 16 | Instant, 17 | }, 18 | }; 19 | 20 | use glium::backend::Context; 21 | use notify::{ 22 | RecommendedWatcher, 23 | RecursiveMode, 24 | Watcher, 25 | }; 26 | 27 | use crate::{ 28 | graph::ShaderGraph, 29 | lisp::graph_from_sexp, 30 | map, 31 | reload::ShaderDir, 32 | }; 33 | 34 | /// A struct that watches a directory for changes, 35 | /// and hot-reloads a shader graph if changes have been 36 | /// made. 37 | pub struct ShaderGraphWatcher { 38 | context: Rc, 39 | last_reload: Instant, 40 | path: PathBuf, 41 | config: PathBuf, 42 | changed: Arc, 43 | _watcher: RecommendedWatcher, 44 | shader_graph: ShaderGraph, 45 | } 46 | 47 | pub enum WatchResult { 48 | /// No changes were made. 49 | NoChange, 50 | /// Changes were made and the graph was rebuilt. 51 | Rebuilt, 52 | /// Changes were made but the graph could not be 53 | /// rebuilt. 54 | Err(String), 55 | } 56 | 57 | impl ShaderGraphWatcher { 58 | /// Creates a new watcher over a certain dir. 59 | /// Returns an error if the directory could not be 60 | /// loaded, Or the graph could not be built. 61 | pub fn new_watch_dir( 62 | context: &Rc, 63 | path: T, 64 | config: T, 65 | ) -> Result 66 | where 67 | T: AsRef, 68 | { 69 | let path = path.as_ref().to_path_buf(); 70 | let config = config.as_ref().to_path_buf(); 71 | 72 | let changed = Arc::new(AtomicBool::new(false)); 73 | // build the watcher 74 | let mut watcher = RecommendedWatcher::new({ 75 | let changed = changed.clone(); 76 | move |res| match res { 77 | Ok(_) => changed.store(true, Ordering::SeqCst), 78 | Err(e) => println!("[warn] Watch error: `{:?}`.", e), 79 | } 80 | }) 81 | .unwrap(); 82 | watcher.watch(&path, RecursiveMode::Recursive).unwrap(); 83 | 84 | let shader_graph = ShaderGraphWatcher::build(context, &path, &config)?; 85 | let last_reload = Instant::now(); 86 | 87 | Ok(ShaderGraphWatcher { 88 | context: context.clone(), 89 | last_reload, 90 | path, 91 | config, 92 | changed, 93 | _watcher: watcher, 94 | shader_graph, 95 | }) 96 | } 97 | 98 | fn build( 99 | context: &Rc, 100 | path: &Path, 101 | config: &Path, 102 | ) -> Result { 103 | let shader_dir = ShaderDir::new_from_dir(path, config)?; 104 | let shader_graph = graph_from_sexp(context, shader_dir, map! {})?; 105 | Ok(shader_graph) 106 | } 107 | 108 | /// Gets the shader graph without trying to reload 109 | /// Note that `graph` will only reload when needed, 110 | /// And tries to de-duplicate redundant reloads, 111 | /// So only use this for fine-grained control over 112 | /// reloads. 113 | pub fn graph_no_reload(&mut self) -> &mut ShaderGraph { 114 | &mut self.shader_graph 115 | } 116 | 117 | /// Forces a rebuild of the graph. Do not call this in a 118 | /// loop! As with `graph_no_reload`, only use this 119 | /// for fine-grained control over reloads. 120 | pub fn graph_force_reload(&mut self) -> (&mut ShaderGraph, WatchResult) { 121 | let watch_result = match ShaderGraphWatcher::build( 122 | &self.context, 123 | &self.path, 124 | &self.config, 125 | ) { 126 | Ok(graph) => { 127 | self.shader_graph = graph; 128 | WatchResult::Rebuilt 129 | }, 130 | Err(error) => WatchResult::Err(error), 131 | }; 132 | 133 | self.last_reload = Instant::now(); 134 | (&mut self.shader_graph, watch_result) 135 | } 136 | 137 | /// Reloads a shader graph if there have been changes, 138 | /// And the graph hasn't been rebuilt recently. 139 | /// Note that if compilation fails, the old graph will 140 | /// remain in use. Returns a borrowed `ShaderGraph`, 141 | /// and whether the graph was rebuilt. 142 | pub fn graph(&mut self) -> (&mut ShaderGraph, WatchResult) { 143 | if self.last_reload.elapsed() > Duration::from_millis(300) 144 | && self.changed.swap(false, Ordering::SeqCst) 145 | { 146 | self.graph_force_reload() 147 | } else { 148 | (self.graph_no_reload(), WatchResult::NoChange) 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "ffmpeg")] 2 | use std::path::PathBuf; 3 | use std::rc::Rc; 4 | 5 | use glium::{ 6 | backend::{ 7 | Context, 8 | Facade, 9 | }, 10 | glutin::{ 11 | dpi::LogicalSize, 12 | event_loop::EventLoop, 13 | window::WindowBuilder, 14 | ContextBuilder, 15 | }, 16 | implement_vertex, 17 | index::NoIndices, 18 | texture::{ 19 | MipmapsOption, 20 | UncompressedFloatFormat, 21 | }, 22 | uniform, 23 | uniforms::{ 24 | MagnifySamplerFilter, 25 | Sampler, 26 | }, 27 | Display, 28 | Frame, 29 | Program, 30 | Surface, 31 | Texture2d, 32 | VertexBuffer, 33 | }; 34 | 35 | #[cfg(feature = "ffmpeg")] 36 | use crate::input::FrameStream; 37 | 38 | #[derive(Copy, Clone)] 39 | pub struct Vertex { 40 | position: [f32; 2], 41 | tex_coords: [f32; 2], 42 | } 43 | 44 | implement_vertex!(Vertex, position, tex_coords); 45 | 46 | pub struct RectStrip { 47 | pub buffer: VertexBuffer, 48 | pub indices: NoIndices, 49 | } 50 | 51 | impl RectStrip { 52 | /// Builds a triangle strip and a vertex buffer 53 | /// used to render basically everything. 54 | pub fn new(facade: &F) -> RectStrip { 55 | let mut shape = vec![]; 56 | for a in &[0.0, 1.0] { 57 | for b in &[0.0, 1.0] { 58 | shape.push(Vertex { 59 | position: [a * 2. - 1., b * 2. - 1.], 60 | tex_coords: [*a, *b], 61 | }); 62 | } 63 | } 64 | 65 | let buffer = VertexBuffer::new(facade, &shape).unwrap(); 66 | let indices = NoIndices(glium::index::PrimitiveType::TriangleStrip); 67 | 68 | RectStrip { buffer, indices } 69 | } 70 | } 71 | 72 | /// Makes a window on which to display something. 73 | /// Note that width and height are specified according to 74 | /// [`LogicalSize`]. Fore more fine-grained control, use the 75 | /// individual components. 76 | pub fn create_window(title: String, width: f64, height: f64) -> WindowBuilder { 77 | WindowBuilder::new() 78 | .with_inner_size(LogicalSize::new(width, height)) 79 | .with_title(title) 80 | } 81 | 82 | /// Sets up a window on which to draw. 83 | /// Note that this returns a simple [`EventLoop`] and 84 | /// [`Display`]. 85 | pub fn create( 86 | title: String, 87 | width: f64, 88 | height: f64, 89 | ) -> (EventLoop<()>, Display) { 90 | let event_loop = EventLoop::new(); 91 | let wb = create_window(title, width, height); 92 | let cb = ContextBuilder::new(); 93 | let display = 94 | Display::new(wb, cb, &event_loop).expect("Unable to create display"); 95 | (event_loop, display) 96 | } 97 | 98 | /// Writes an output texture to the entire window. 99 | pub fn texture(display: &Display, target: &mut Frame, texture: &Texture2d) { 100 | let rect_strip = RectStrip::new(display); 101 | let vertex_shader_src = include_str!("./texture.vert"); 102 | let fragment_shader_src = include_str!("./texture.frag"); 103 | 104 | let program = glium::Program::from_source( 105 | display, 106 | vertex_shader_src, 107 | fragment_shader_src, 108 | None, 109 | ) 110 | .unwrap(); 111 | 112 | target 113 | .draw( 114 | &rect_strip.buffer, 115 | &rect_strip.indices, 116 | &program, 117 | &uniform! { 118 | tex: Sampler::new(texture) 119 | .magnify_filter(MagnifySamplerFilter::Nearest), 120 | }, 121 | &Default::default(), 122 | ) 123 | .unwrap(); 124 | } 125 | 126 | pub fn default_buffer( 127 | context: &Rc, 128 | width: u32, 129 | height: u32, 130 | ) -> Texture2d { 131 | // set up the shader and its buffer 132 | Texture2d::empty_with_format( 133 | context, 134 | UncompressedFloatFormat::U16U16U16U16, // 4 16-bit color channels 135 | MipmapsOption::NoMipmap, 136 | width, 137 | height, 138 | ) 139 | .unwrap() 140 | } 141 | 142 | pub fn compile_shader( 143 | context: &Rc, 144 | source: &str, 145 | ) -> Result { 146 | Program::from_source(context, include_str!("./texture.vert"), source, None) 147 | .map_err(|e| format!("{}", e)) 148 | } 149 | 150 | #[cfg(feature = "ffmpeg")] 151 | pub fn input_textures( 152 | display: &Display, 153 | inputs: &[PathBuf], 154 | width: u32, 155 | height: u32, 156 | ) -> Vec { 157 | // build a table of textures 158 | let mut input_textures = vec![]; 159 | for texture_path in inputs { 160 | eprintln!( 161 | "[info] Building frames for {}", 162 | texture_path.to_string_lossy() 163 | ); 164 | input_textures.push( 165 | FrameStream::new(&texture_path, width, height, display) 166 | .expect("Couldn't open frame source"), 167 | ); 168 | } 169 | return input_textures; 170 | } 171 | -------------------------------------------------------------------------------- /src/util/texture.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 coords; 4 | out vec4 color; 5 | 6 | uniform sampler2D tex; 7 | 8 | void main() { 9 | color = texture(tex, coords); 10 | } 11 | -------------------------------------------------------------------------------- /src/util/texture.vert: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec2 position; 4 | in vec2 tex_coords; 5 | out vec2 coords; 6 | 7 | void main() { 8 | coords = tex_coords; 9 | gl_Position = vec4(position, 0.0, 1.0); 10 | } 11 | --------------------------------------------------------------------------------