diff --git a/Cargo.lock b/Cargo.lock index 4940067882..21e269c4af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,7 +96,7 @@ name = "backtrace-sys" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -177,10 +177,10 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.15" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -225,7 +225,7 @@ name = "cmake" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -247,7 +247,7 @@ name = "compositor" version = "0.1.0" dependencies = [ "compositor-windows 0.1.0", - "gleam 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "webrender 0.61.0", ] @@ -255,7 +255,7 @@ dependencies = [ name = "compositor-windows" version = "0.1.0" dependencies = [ - "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -420,7 +420,7 @@ name = "direct-composition" version = "0.1.0" dependencies = [ "euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gleam 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "mozangle 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "webrender 0.61.0", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -617,12 +617,27 @@ dependencies = [ [[package]] name = "gleam" -version = "0.9.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "glsl" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glsl-to-cxx" +version = "0.1.0" +dependencies = [ + "glsl 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "glutin" version = "0.21.0" @@ -747,6 +762,14 @@ name = "itoa" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -761,11 +784,6 @@ name = "khronos_api" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "lazy_static" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "lazy_static" version = "1.3.0" @@ -786,7 +804,7 @@ name = "libloading" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -862,6 +880,11 @@ name = "memoffset" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "minidl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "mio" version = "0.6.16" @@ -907,7 +930,7 @@ name = "mozangle" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", "gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -929,7 +952,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -940,6 +963,15 @@ name = "nodrop" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "nom" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-integer" version = "0.1.38" @@ -1503,6 +1535,15 @@ name = "svg_fmt" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "swgl" +version = "0.1.0" +dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glsl-to-cxx 0.1.0", +] + [[package]] name = "syn" version = "0.15.30" @@ -1568,16 +1609,6 @@ dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "thread_profiler" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "tileview" version = "0.1.0" @@ -1599,6 +1630,14 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tracy-rs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "minidl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "typenum" version = "1.10.0" @@ -1674,6 +1713,11 @@ name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "void" version = "1.0.2" @@ -1760,7 +1804,7 @@ dependencies = [ "euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", "freetype 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gleam 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1777,8 +1821,8 @@ dependencies = [ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "svg_fmt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_profiler 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "tracy-rs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "webrender_api 0.61.0", "webrender_build 0.0.1", "wr_malloc_size_of 0.0.1", @@ -1793,7 +1837,7 @@ dependencies = [ "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gleam 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "glutin 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "webrender 0.61.0", @@ -1919,7 +1963,7 @@ dependencies = [ "env_logger 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", "font-loader 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gleam 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "glutin 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1930,6 +1974,7 @@ dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "swgl 0.1.0", "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", "webrender 0.61.0", "webrender_api 0.61.0", @@ -2020,7 +2065,7 @@ dependencies = [ "checksum bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431" "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" "checksum bytes 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "2f1d50c876fb7545f5f289cd8b2aee3f359d073ae819eed5d6373638e2c61e59" -"checksum cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0ebb87d1116151416c0cf66a0e3fb6430cccd120fd6300794b4dfaa050ac40ba" +"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" "checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" "checksum cgl 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "55e7ec0b74fe5897894cbc207092c577e87c52f8a59e8ca8d97ef37551f60a49" "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" @@ -2066,8 +2111,9 @@ dependencies = [ "checksum gl_generator 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39a23d5e872a275135d66895d954269cf5e8661d234eb1c2480f4ce0d586acbd" "checksum gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca98bbde17256e02d17336a6bdb5a50f7d0ccacee502e191d3e3d0ec2f96f84a" "checksum gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +"checksum gleam 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77b1fd76468fff720bad31016688b805c6442ebf2d7c29123e10e4984aa61986" "checksum gleam 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "cae10d7c99d0e77b4766e850a60898a17c1abaf01075531f1066f03dc7dc5fc5" -"checksum gleam 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332d1f4e6c6181ed07178f84a552b2387d43ecf6821a86c22cfb3883ea3fb1b9" +"checksum glsl 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "766443890761b3c4edcce86cafaac97971b200662fbdd0446eb7c6b99b4401ea" "checksum glutin 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb26027a84c3b9e1949ef0df0b6a3db8d0c124243a5c161ea25c7def90cb1474" "checksum glutin_egl_sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "23f48987ab6cb2b61ad903b59e54a2fd0c380a7baff68cffd6826b69a73dd326" "checksum glutin_emscripten_sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "245b3fdb08df6ffed7585365851f8404af9c7e2dd4b59f15262e968b6a95a0c7" @@ -2081,9 +2127,9 @@ dependencies = [ "checksum inflate 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4ec18d981200fd14e65ee8e35fb60ed1ce55227a02407303f3a72517c6146dcc" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682" +"checksum jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" -"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" @@ -2098,6 +2144,7 @@ dependencies = [ "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" +"checksum minidl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "647da2489d438eb707e9bcffe0e47fb6f3f355ed288120740fc1e614cfadb9e9" "checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" "checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" @@ -2105,6 +2152,7 @@ dependencies = [ "checksum net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0" "checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" +"checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" "checksum num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac0ea58d64a89d9d6b7688031b3be9358d6c919badcf7fbb0527ccfd891ee45" "checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" "checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" @@ -2178,8 +2226,8 @@ dependencies = [ "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -"checksum thread_profiler 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5920e77802b177479ab5795767fa48e68f61b2f516c2ac0041e2978dd8efe483" "checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" +"checksum tracy-rs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3aa118469b61af5fead107a4882dc4661a05591b6653bbc546c1c8bbc181047" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" @@ -2192,6 +2240,7 @@ dependencies = [ "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c7904a7e2bb3cdf0cf5e783f44204a85a37a93151738fa349f06680f59a98b45" "checksum wayland-client 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)" = "49963e5f9eeaf637bfcd1b9f0701c99fd5cd05225eb51035550d4272806f2713" diff --git a/ci-scripts/linux-debug-tests.sh b/ci-scripts/linux-debug-tests.sh index 46916aeb52..2c2fc756b6 100755 --- a/ci-scripts/linux-debug-tests.sh +++ b/ci-scripts/linux-debug-tests.sh @@ -32,4 +32,5 @@ cargo build ${CARGOFLAGS} popd cargo test ${CARGOFLAGS} \ - --all --exclude compositor-windows --exclude compositor + --all --exclude compositor-windows --exclude compositor \ + --exclude glsl-to-cxx --exclude swgl diff --git a/ci-scripts/macos-debug-tests.sh b/ci-scripts/macos-debug-tests.sh index 609576aec7..2325475d90 100755 --- a/ci-scripts/macos-debug-tests.sh +++ b/ci-scripts/macos-debug-tests.sh @@ -38,4 +38,5 @@ cargo check ${CARGOFLAGS} popd cargo test ${CARGOFLAGS} ${CARGOTESTFLAGS} \ - --all --exclude compositor-windows --exclude compositor + --all --exclude compositor-windows --exclude compositor \ + --exclude glsl-to-cxx --exclude swgl diff --git a/direct-composition/Cargo.toml b/direct-composition/Cargo.toml index 916a22349c..6d33a9fc10 100644 --- a/direct-composition/Cargo.toml +++ b/direct-composition/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [target.'cfg(windows)'.dependencies] euclid = "0.20" -gleam = "0.9.2" +gleam = "0.10.0" mozangle = {version = "0.3.1", features = ["egl"]} webrender = {path = "../webrender"} winapi = {version = "0.3", features = ["winerror", "d3d11", "dcomp"]} diff --git a/example-compositor/compositor/Cargo.toml b/example-compositor/compositor/Cargo.toml index 31c9763435..f009be0e8a 100644 --- a/example-compositor/compositor/Cargo.toml +++ b/example-compositor/compositor/Cargo.toml @@ -7,7 +7,7 @@ license = "MPL-2.0" [dependencies] webrender = { path = "../../webrender" } -gleam = "0.9.2" +gleam = "0.10.0" [target.'cfg(windows)'.dependencies] compositor-windows = { path = "../compositor-windows" } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 3052d6b7cf..f82c14fc37 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -61,7 +61,7 @@ debug = ["webrender/capture", "webrender/debugger", "webrender/profiler"] app_units = "0.7" env_logger = "0.5" euclid = "0.20" -gleam = "0.9.2" +gleam = "0.10.0" glutin = "0.21" rayon = "1" webrender = { path = "../webrender" } diff --git a/glsl-to-cxx/Cargo.toml b/glsl-to-cxx/Cargo.toml new file mode 100644 index 0000000000..1ff4298b54 --- /dev/null +++ b/glsl-to-cxx/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "glsl-to-cxx" +version = "0.1.0" +license = "MPL-2.0" +authors = ["The Mozilla Project Developers"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +glsl = "4.0" diff --git a/glsl-to-cxx/README.md b/glsl-to-cxx/README.md new file mode 100644 index 0000000000..1b5b4845d2 --- /dev/null +++ b/glsl-to-cxx/README.md @@ -0,0 +1,3 @@ +A GLSL to C++ translator. + +Translates GLSL to vectorized C++. Intended for use with WebRender software backend. diff --git a/glsl-to-cxx/src/hir.rs b/glsl-to-cxx/src/hir.rs new file mode 100644 index 0000000000..f668c585bb --- /dev/null +++ b/glsl-to-cxx/src/hir.rs @@ -0,0 +1,3762 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use glsl::syntax; +use glsl::syntax::{ArrayedIdentifier, ArraySpecifier, AssignmentOp, BinaryOp, Identifier}; +use glsl::syntax::{NonEmpty, PrecisionQualifier, StructFieldSpecifier, StructSpecifier}; +use glsl::syntax::{TypeSpecifier, TypeSpecifierNonArray, UnaryOp}; +use std::cell::{Cell, Ref, RefCell}; +use std::collections::HashMap; +use std::iter::FromIterator; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::rc::Rc; + +trait LiftFrom { + fn lift(state: &mut State, s: S) -> Self; +} + +fn lift>(state: &mut State, s: S) -> T { + LiftFrom::lift(state, s) +} + +#[derive(Debug)] +pub struct Symbol { + pub name: String, + pub decl: SymDecl, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct FunctionSignature { + ret: Type, + params: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct FunctionType { + signatures: NonEmpty, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum SamplerFormat { + Unknown, + RGBA8, + RGBA32F, + RGBA32I, + R8, +} + +impl SamplerFormat { + pub fn type_suffix(self) -> Option<&'static str> { + match self { + SamplerFormat::Unknown => None, + SamplerFormat::RGBA8 => Some("RGBA8"), + SamplerFormat::RGBA32F => Some("RGBA32F"), + SamplerFormat::RGBA32I => Some("RGBA32I"), + SamplerFormat::R8 => Some("R8"), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum StorageClass { + None, + Const, + In, + Out, + Uniform, + Sampler(SamplerFormat), + FragColor(i32), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ArraySizes { + pub sizes: Vec, +} + +impl LiftFrom<&ArraySpecifier> for ArraySizes { + fn lift(state: &mut State, a: &ArraySpecifier) -> Self { + ArraySizes { + sizes: vec![match a { + ArraySpecifier::Unsized => panic!(), + ArraySpecifier::ExplicitlySized(expr) => translate_expression(state, expr), + }], + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum TypeKind { + Void, + Bool, + Int, + UInt, + Float, + Double, + Vec2, + Vec3, + Vec4, + DVec2, + DVec3, + DVec4, + BVec2, + BVec3, + BVec4, + IVec2, + IVec3, + IVec4, + UVec2, + UVec3, + UVec4, + Mat2, + Mat3, + Mat4, + Mat23, + Mat24, + Mat32, + Mat34, + Mat42, + Mat43, + DMat2, + DMat3, + DMat4, + DMat23, + DMat24, + DMat32, + DMat34, + DMat42, + DMat43, + // floating point opaque types + Sampler1D, + Image1D, + Sampler2D, + Image2D, + Sampler3D, + Image3D, + SamplerCube, + ImageCube, + Sampler2DRect, + Image2DRect, + Sampler1DArray, + Image1DArray, + Sampler2DArray, + Image2DArray, + SamplerBuffer, + ImageBuffer, + Sampler2DMS, + Image2DMS, + Sampler2DMSArray, + Image2DMSArray, + SamplerCubeArray, + ImageCubeArray, + Sampler1DShadow, + Sampler2DShadow, + Sampler2DRectShadow, + Sampler1DArrayShadow, + Sampler2DArrayShadow, + SamplerCubeShadow, + SamplerCubeArrayShadow, + // signed integer opaque types + ISampler1D, + IImage1D, + ISampler2D, + IImage2D, + ISampler3D, + IImage3D, + ISamplerCube, + IImageCube, + ISampler2DRect, + IImage2DRect, + ISampler1DArray, + IImage1DArray, + ISampler2DArray, + IImage2DArray, + ISamplerBuffer, + IImageBuffer, + ISampler2DMS, + IImage2DMS, + ISampler2DMSArray, + IImage2DMSArray, + ISamplerCubeArray, + IImageCubeArray, + // unsigned integer opaque types + AtomicUInt, + USampler1D, + UImage1D, + USampler2D, + UImage2D, + USampler3D, + UImage3D, + USamplerCube, + UImageCube, + USampler2DRect, + UImage2DRect, + USampler1DArray, + UImage1DArray, + USampler2DArray, + UImage2DArray, + USamplerBuffer, + UImageBuffer, + USampler2DMS, + UImage2DMS, + USampler2DMSArray, + UImage2DMSArray, + USamplerCubeArray, + UImageCubeArray, + Struct(SymRef), +} + +impl TypeKind { + pub fn is_sampler(&self) -> bool { + use TypeKind::*; + match self { + Sampler1D + | Image1D + | Sampler2D + | Image2D + | Sampler3D + | Image3D + | SamplerCube + | ImageCube + | Sampler2DRect + | Image2DRect + | Sampler1DArray + | Image1DArray + | Sampler2DArray + | Image2DArray + | SamplerBuffer + | ImageBuffer + | Sampler2DMS + | Image2DMS + | Sampler2DMSArray + | Image2DMSArray + | SamplerCubeArray + | ImageCubeArray + | Sampler1DShadow + | Sampler2DShadow + | Sampler2DRectShadow + | Sampler1DArrayShadow + | Sampler2DArrayShadow + | SamplerCubeShadow + | SamplerCubeArrayShadow + | ISampler1D + | IImage1D + | ISampler2D + | IImage2D + | ISampler3D + | IImage3D + | ISamplerCube + | IImageCube + | ISampler2DRect + | IImage2DRect + | ISampler1DArray + | IImage1DArray + | ISampler2DArray + | IImage2DArray + | ISamplerBuffer + | IImageBuffer + | ISampler2DMS + | IImage2DMS + | ISampler2DMSArray + | IImage2DMSArray + | ISamplerCubeArray + | IImageCubeArray + | USampler1D + | UImage1D + | USampler2D + | UImage2D + | USampler3D + | UImage3D + | USamplerCube + | UImageCube + | USampler2DRect + | UImage2DRect + | USampler1DArray + | UImage1DArray + | USampler2DArray + | UImage2DArray + | USamplerBuffer + | UImageBuffer + | USampler2DMS + | UImage2DMS + | USampler2DMSArray + | UImage2DMSArray + | USamplerCubeArray + | UImageCubeArray => true, + _ => false, + } + } + + pub fn glsl_primitive_type_name(&self) -> Option<&'static str> { + use TypeKind::*; + Some(match self { + Void => "void", + Bool => "bool", + Int => "int", + UInt => "uint", + Float => "float", + Double => "double", + Vec2 => "vec2", + Vec3 => "vec3", + Vec4 => "vec4", + DVec2 => "dvec2", + DVec3 => "dvec3", + DVec4 => "dvec4", + BVec2 => "bvec2", + BVec3 => "bvec3", + BVec4 => "bvec4", + IVec2 => "ivec2", + IVec3 => "ivec3", + IVec4 => "ivec4", + UVec2 => "uvec2", + UVec3 => "uvec3", + UVec4 => "uvec4", + Mat2 => "mat2", + Mat3 => "mat3", + Mat4 => "mat4", + Mat23 => "mat23", + Mat24 => "mat24", + Mat32 => "mat32", + Mat34 => "mat34", + Mat42 => "mat42", + Mat43 => "mat43", + DMat2 => "dmat2", + DMat3 => "dmat3", + DMat4 => "dmat4", + DMat23 => "dmat23", + DMat24 => "dmat24", + DMat32 => "dmat32", + DMat34 => "dmat34", + DMat42 => "dmat42", + DMat43 => "dmat43", + Sampler1D => "sampler1D", + Image1D => "image1D", + Sampler2D => "sampler2D", + Image2D => "image2D", + Sampler3D => "sampler3D", + Image3D => "image3D", + SamplerCube => "samplerCube", + ImageCube => "imageCube", + Sampler2DRect => "sampler2DRect", + Image2DRect => "image2DRect", + Sampler1DArray => "sampler1DArray", + Image1DArray => "image1DArray", + Sampler2DArray => "sampler2DArray", + Image2DArray => "image2DArray", + SamplerBuffer => "samplerBuffer", + ImageBuffer => "imageBuffer", + Sampler2DMS => "sampler2DMS", + Image2DMS => "image2DMS", + Sampler2DMSArray => "sampler2DMSArray", + Image2DMSArray => "image2DMSArray", + SamplerCubeArray => "samplerCubeArray", + ImageCubeArray => "imageCubeArray", + Sampler1DShadow => "sampler1DShadow", + Sampler2DShadow => "sampler2DShadow", + Sampler2DRectShadow => "sampler2DRectShadow", + Sampler1DArrayShadow => "sampler1DArrayShadow", + Sampler2DArrayShadow => "sampler2DArrayShadow", + SamplerCubeShadow => "samplerCubeShadow", + SamplerCubeArrayShadow => "samplerCubeArrayShadow", + ISampler1D => "isampler1D", + IImage1D => "iimage1D", + ISampler2D => "isampler2D", + IImage2D => "iimage2D", + ISampler3D => "isampler3D", + IImage3D => "iimage3D", + ISamplerCube => "isamplerCube", + IImageCube => "iimageCube", + ISampler2DRect => "isampler2DRect", + IImage2DRect => "iimage2DRect", + ISampler1DArray => "isampler1DArray", + IImage1DArray => "iimage1DArray", + ISampler2DArray => "isampler2DArray", + IImage2DArray => "iimage2DArray", + ISamplerBuffer => "isamplerBuffer", + IImageBuffer => "iimageBuffer", + ISampler2DMS => "isampler2MS", + IImage2DMS => "iimage2DMS", + ISampler2DMSArray => "isampler2DMSArray", + IImage2DMSArray => "iimage2DMSArray", + ISamplerCubeArray => "isamplerCubeArray", + IImageCubeArray => "iimageCubeArray", + AtomicUInt => "atomic_uint", + USampler1D => "usampler1D", + UImage1D => "uimage1D", + USampler2D => "usampler2D", + UImage2D => "uimage2D", + USampler3D => "usampler3D", + UImage3D => "uimage3D", + USamplerCube => "usamplerCube", + UImageCube => "uimageCube", + USampler2DRect => "usampler2DRect", + UImage2DRect => "uimage2DRect", + USampler1DArray => "usampler1DArray", + UImage1DArray => "uimage1DArray", + USampler2DArray => "usampler2DArray", + UImage2DArray => "uimage2DArray", + USamplerBuffer => "usamplerBuffer", + UImageBuffer => "uimageBuffer", + USampler2DMS => "usampler2DMS", + UImage2DMS => "uimage2DMS", + USampler2DMSArray => "usamplerDMSArray", + UImage2DMSArray => "uimage2DMSArray", + USamplerCubeArray => "usamplerCubeArray", + UImageCubeArray => "uimageCubeArray", + Struct(..) => return None, + }) + } + + pub fn cxx_primitive_type_name(&self) -> Option<&'static str> { + use TypeKind::*; + match self { + Bool => Some("Bool"), + Int => Some("I32"), + UInt => Some("U32"), + Float => Some("Float"), + Double => Some("Double"), + _ => self.glsl_primitive_type_name(), + } + } + + pub fn cxx_primitive_scalar_type_name(&self) -> Option<&'static str> { + use TypeKind::*; + match self { + Void => Some("void"), + Bool => Some("bool"), + Int => Some("int32_t"), + UInt => Some("uint32_t"), + Float => Some("float"), + Double => Some("double"), + _ => { + if self.is_sampler() { + self.cxx_primitive_type_name() + } else { + None + } + } + } + } + + pub fn from_glsl_primitive_type_name(name: &str) -> Option { + use TypeKind::*; + Some(match name { + "void" => Void, + "bool" => Bool, + "int" => Int, + "uint" => UInt, + "float" => Float, + "double" => Double, + "vec2" => Vec2, + "vec3" => Vec3, + "vec4" => Vec4, + "dvec2" => DVec2, + "dvec3" => DVec3, + "dvec4" => DVec4, + "bvec2" => BVec2, + "bvec3" => BVec3, + "bvec4" => BVec4, + "ivec2" => IVec2, + "ivec3" => IVec3, + "ivec4" => IVec4, + "uvec2" => UVec2, + "uvec3" => UVec3, + "uvec4" => UVec4, + "mat2" => Mat2, + "mat3" => Mat3, + "mat4" => Mat4, + "mat23" => Mat23, + "mat24" => Mat24, + "mat32" => Mat32, + "mat34" => Mat34, + "mat42" => Mat42, + "mat43" => Mat43, + "dmat2" => DMat2, + "dmat3" => DMat3, + "dmat4" => DMat4, + "dmat23" => DMat23, + "dmat24" => DMat24, + "dmat32" => DMat32, + "dmat34" => DMat34, + "dmat42" => DMat42, + "dmat43" => DMat43, + "sampler1D" => Sampler1D, + "image1D" => Image1D, + "sampler2D" => Sampler2D, + "image2D" => Image2D, + "sampler3D" => Sampler3D, + "image3D" => Image3D, + "samplerCube" => SamplerCube, + "imageCube" => ImageCube, + "sampler2DRect" => Sampler2DRect, + "image2DRect" => Image2DRect, + "sampler1DArray" => Sampler1DArray, + "image1DArray" => Image1DArray, + "sampler2DArray" => Sampler2DArray, + "image2DArray" => Image2DArray, + "samplerBuffer" => SamplerBuffer, + "imageBuffer" => ImageBuffer, + "sampler2DMS" => Sampler2DMS, + "image2DMS" => Image2DMS, + "sampler2DMSArray" => Sampler2DMSArray, + "image2DMSArray" => Image2DMSArray, + "samplerCubeArray" => SamplerCubeArray, + "imageCubeArray" => ImageCubeArray, + "sampler1DShadow" => Sampler1DShadow, + "sampler2DShadow" => Sampler2DShadow, + "sampler2DRectShadow" => Sampler2DRectShadow, + "sampler1DArrayShadow" => Sampler1DArrayShadow, + "sampler2DArrayShadow" => Sampler2DArrayShadow, + "samplerCubeShadow" => SamplerCubeShadow, + "samplerCubeArrayShadow" => SamplerCubeArrayShadow, + "isampler1D" => ISampler1D, + "iimage1D" => IImage1D, + "isampler2D" => ISampler2D, + "iimage2D" => IImage2D, + "isampler3D" => ISampler3D, + "iimage3D" => IImage3D, + "isamplerCube" => ISamplerCube, + "iimageCube" => IImageCube, + "isampler2DRect" => ISampler2DRect, + "iimage2DRect" => IImage2DRect, + "isampler1DArray" => ISampler1DArray, + "iimage1DArray" => IImage1DArray, + "isampler2DArray" => ISampler2DArray, + "iimage2DArray" => IImage2DArray, + "isamplerBuffer" => ISamplerBuffer, + "iimageBuffer" => IImageBuffer, + "isampler2MS" => ISampler2DMS, + "iimage2DMS" => IImage2DMS, + "isampler2DMSArray" => ISampler2DMSArray, + "iimage2DMSArray" => IImage2DMSArray, + "isamplerCubeArray" => ISamplerCubeArray, + "iimageCubeArray" => IImageCubeArray, + "atomic_uint" => AtomicUInt, + "usampler1D" => USampler1D, + "uimage1D" => UImage1D, + "usampler2D" => USampler2D, + "uimage2D" => UImage2D, + "usampler3D" => USampler3D, + "uimage3D" => UImage3D, + "usamplerCube" => USamplerCube, + "uimageCube" => UImageCube, + "usampler2DRect" => USampler2DRect, + "uimage2DRect" => UImage2DRect, + "usampler1DArray" => USampler1DArray, + "uimage1DArray" => UImage1DArray, + "usampler2DArray" => USampler2DArray, + "uimage2DArray" => UImage2DArray, + "usamplerBuffer" => USamplerBuffer, + "uimageBuffer" => UImageBuffer, + "usampler2DMS" => USampler2DMS, + "uimage2DMS" => UImage2DMS, + "usamplerDMSArray" => USampler2DMSArray, + "uimage2DMSArray" => UImage2DMSArray, + "usamplerCubeArray" => USamplerCubeArray, + "uimageCubeArray" => UImageCubeArray, + _ => return None, + }) + } + + pub fn from_primitive_type_specifier(spec: &syntax::TypeSpecifierNonArray) -> Option { + use TypeKind::*; + Some(match spec { + TypeSpecifierNonArray::Void => Void, + TypeSpecifierNonArray::Bool => Bool, + TypeSpecifierNonArray::Int => Int, + TypeSpecifierNonArray::UInt => UInt, + TypeSpecifierNonArray::Float => Float, + TypeSpecifierNonArray::Double => Double, + TypeSpecifierNonArray::Vec2 => Vec2, + TypeSpecifierNonArray::Vec3 => Vec3, + TypeSpecifierNonArray::Vec4 => Vec4, + TypeSpecifierNonArray::DVec2 => DVec2, + TypeSpecifierNonArray::DVec3 => DVec3, + TypeSpecifierNonArray::DVec4 => DVec4, + TypeSpecifierNonArray::BVec2 => BVec2, + TypeSpecifierNonArray::BVec3 => BVec3, + TypeSpecifierNonArray::BVec4 => BVec4, + TypeSpecifierNonArray::IVec2 => IVec2, + TypeSpecifierNonArray::IVec3 => IVec3, + TypeSpecifierNonArray::IVec4 => IVec4, + TypeSpecifierNonArray::UVec2 => UVec2, + TypeSpecifierNonArray::UVec3 => UVec3, + TypeSpecifierNonArray::UVec4 => UVec4, + TypeSpecifierNonArray::Mat2 => Mat2, + TypeSpecifierNonArray::Mat3 => Mat3, + TypeSpecifierNonArray::Mat4 => Mat4, + TypeSpecifierNonArray::Mat23 => Mat23, + TypeSpecifierNonArray::Mat24 => Mat24, + TypeSpecifierNonArray::Mat32 => Mat32, + TypeSpecifierNonArray::Mat34 => Mat34, + TypeSpecifierNonArray::Mat42 => Mat42, + TypeSpecifierNonArray::Mat43 => Mat43, + TypeSpecifierNonArray::DMat2 => DMat2, + TypeSpecifierNonArray::DMat3 => DMat3, + TypeSpecifierNonArray::DMat4 => DMat4, + TypeSpecifierNonArray::DMat23 => DMat23, + TypeSpecifierNonArray::DMat24 => DMat24, + TypeSpecifierNonArray::DMat32 => DMat32, + TypeSpecifierNonArray::DMat34 => DMat34, + TypeSpecifierNonArray::DMat42 => DMat42, + TypeSpecifierNonArray::DMat43 => DMat43, + TypeSpecifierNonArray::Sampler1D => Sampler1D, + TypeSpecifierNonArray::Image1D => Image1D, + TypeSpecifierNonArray::Sampler2D => Sampler2D, + TypeSpecifierNonArray::Image2D => Image2D, + TypeSpecifierNonArray::Sampler3D => Sampler3D, + TypeSpecifierNonArray::Image3D => Image3D, + TypeSpecifierNonArray::SamplerCube => SamplerCube, + TypeSpecifierNonArray::ImageCube => ImageCube, + TypeSpecifierNonArray::Sampler2DRect => Sampler2DRect, + TypeSpecifierNonArray::Image2DRect => Image2DRect, + TypeSpecifierNonArray::Sampler1DArray => Sampler1DArray, + TypeSpecifierNonArray::Image1DArray => Image1DArray, + TypeSpecifierNonArray::Sampler2DArray => Sampler2DArray, + TypeSpecifierNonArray::Image2DArray => Image2DArray, + TypeSpecifierNonArray::SamplerBuffer => SamplerBuffer, + TypeSpecifierNonArray::ImageBuffer => ImageBuffer, + TypeSpecifierNonArray::Sampler2DMS => Sampler2DMS, + TypeSpecifierNonArray::Image2DMS => Image2DMS, + TypeSpecifierNonArray::Sampler2DMSArray => Sampler2DMSArray, + TypeSpecifierNonArray::Image2DMSArray => Image2DMSArray, + TypeSpecifierNonArray::SamplerCubeArray => SamplerCubeArray, + TypeSpecifierNonArray::ImageCubeArray => ImageCubeArray, + TypeSpecifierNonArray::Sampler1DShadow => Sampler1DShadow, + TypeSpecifierNonArray::Sampler2DShadow => Sampler2DShadow, + TypeSpecifierNonArray::Sampler2DRectShadow => Sampler2DRectShadow, + TypeSpecifierNonArray::Sampler1DArrayShadow => Sampler1DArrayShadow, + TypeSpecifierNonArray::Sampler2DArrayShadow => Sampler2DArrayShadow, + TypeSpecifierNonArray::SamplerCubeShadow => SamplerCubeShadow, + TypeSpecifierNonArray::SamplerCubeArrayShadow => SamplerCubeArrayShadow, + TypeSpecifierNonArray::ISampler1D => ISampler1D, + TypeSpecifierNonArray::IImage1D => IImage1D, + TypeSpecifierNonArray::ISampler2D => ISampler2D, + TypeSpecifierNonArray::IImage2D => IImage2D, + TypeSpecifierNonArray::ISampler3D => ISampler3D, + TypeSpecifierNonArray::IImage3D => IImage3D, + TypeSpecifierNonArray::ISamplerCube => ISamplerCube, + TypeSpecifierNonArray::IImageCube => IImageCube, + TypeSpecifierNonArray::ISampler2DRect => ISampler2DRect, + TypeSpecifierNonArray::IImage2DRect => IImage2DRect, + TypeSpecifierNonArray::ISampler1DArray => ISampler1DArray, + TypeSpecifierNonArray::IImage1DArray => IImage1DArray, + TypeSpecifierNonArray::ISampler2DArray => ISampler2DArray, + TypeSpecifierNonArray::IImage2DArray => IImage2DArray, + TypeSpecifierNonArray::ISamplerBuffer => ISamplerBuffer, + TypeSpecifierNonArray::IImageBuffer => IImageBuffer, + TypeSpecifierNonArray::ISampler2DMS => ISampler2DMS, + TypeSpecifierNonArray::IImage2DMS => IImage2DMS, + TypeSpecifierNonArray::ISampler2DMSArray => ISampler2DMSArray, + TypeSpecifierNonArray::IImage2DMSArray => IImage2DMSArray, + TypeSpecifierNonArray::ISamplerCubeArray => ISamplerCubeArray, + TypeSpecifierNonArray::IImageCubeArray => IImageCubeArray, + TypeSpecifierNonArray::AtomicUInt => AtomicUInt, + TypeSpecifierNonArray::USampler1D => USampler1D, + TypeSpecifierNonArray::UImage1D => UImage1D, + TypeSpecifierNonArray::USampler2D => USampler2D, + TypeSpecifierNonArray::UImage2D => UImage2D, + TypeSpecifierNonArray::USampler3D => USampler3D, + TypeSpecifierNonArray::UImage3D => UImage3D, + TypeSpecifierNonArray::USamplerCube => USamplerCube, + TypeSpecifierNonArray::UImageCube => UImageCube, + TypeSpecifierNonArray::USampler2DRect => USampler2DRect, + TypeSpecifierNonArray::UImage2DRect => UImage2DRect, + TypeSpecifierNonArray::USampler1DArray => USampler1DArray, + TypeSpecifierNonArray::UImage1DArray => UImage1DArray, + TypeSpecifierNonArray::USampler2DArray => USampler2DArray, + TypeSpecifierNonArray::UImage2DArray => UImage2DArray, + TypeSpecifierNonArray::USamplerBuffer => USamplerBuffer, + TypeSpecifierNonArray::UImageBuffer => UImageBuffer, + TypeSpecifierNonArray::USampler2DMS => USampler2DMS, + TypeSpecifierNonArray::UImage2DMS => UImage2DMS, + TypeSpecifierNonArray::USampler2DMSArray => USampler2DMSArray, + TypeSpecifierNonArray::UImage2DMSArray => UImage2DMSArray, + TypeSpecifierNonArray::USamplerCubeArray => USamplerCubeArray, + TypeSpecifierNonArray::UImageCubeArray => UImageCubeArray, + TypeSpecifierNonArray::Struct(..) | TypeSpecifierNonArray::TypeName(..) => return None, + }) + } +} + +impl LiftFrom<&syntax::TypeSpecifierNonArray> for TypeKind { + fn lift(state: &mut State, spec: &syntax::TypeSpecifierNonArray) -> Self { + use TypeKind::*; + if let Some(kind) = TypeKind::from_primitive_type_specifier(spec) { + kind + } else { + match spec { + TypeSpecifierNonArray::Struct(s) => { + Struct(state.lookup(s.name.as_ref().unwrap().as_str()).unwrap()) + } + TypeSpecifierNonArray::TypeName(s) => Struct(state.lookup(&s.0).unwrap()), + _ => unreachable!(), + } + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Type { + pub kind: TypeKind, + pub precision: Option, + pub array_sizes: Option>, +} + +impl Type { + pub fn new(kind: TypeKind) -> Self { + Type { + kind, + precision: None, + array_sizes: None, + } + } +} + +impl LiftFrom<&syntax::FullySpecifiedType> for Type { + fn lift(state: &mut State, ty: &syntax::FullySpecifiedType) -> Self { + let kind = lift(state, &ty.ty.ty); + let array_sizes = match ty.ty.array_specifier.as_ref() { + Some(x) => Some(Box::new(lift(state, x))), + None => None, + }; + let precision = get_precision(&ty.qualifier); + Type { + kind, + precision, + array_sizes, + } + } +} + +impl LiftFrom<&syntax::TypeSpecifier> for Type { + fn lift(state: &mut State, ty: &syntax::TypeSpecifier) -> Self { + let kind = lift(state, &ty.ty); + let array_sizes = ty + .array_specifier + .as_ref() + .map(|x| Box::new(lift(state, x))); + Type { + kind, + precision: None, + array_sizes, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct StructField { + pub ty: Type, + pub name: syntax::Identifier, +} + +fn get_precision(qualifiers: &Option) -> Option { + let mut precision = None; + for qual in qualifiers.iter().flat_map(|x| x.qualifiers.0.iter()) { + match qual { + syntax::TypeQualifierSpec::Precision(p) => { + if precision.is_some() { + panic!("Multiple precisions"); + } + precision = Some(p.clone()); + } + _ => {} + } + } + precision +} + +impl LiftFrom<&StructFieldSpecifier> for StructField { + fn lift(state: &mut State, f: &StructFieldSpecifier) -> Self { + let mut ty: Type = lift(state, &f.ty); + match &f.identifiers.0[..] { + [ident] => { + if let Some(a) = &ident.array_spec { + ty.array_sizes = Some(Box::new(lift(state, a))); + } + StructField { + ty, + name: ident.ident.clone(), + } + } + _ => panic!("bad number of identifiers"), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct StructFields { + pub fields: Vec, +} + +impl LiftFrom<&StructSpecifier> for StructFields { + fn lift(state: &mut State, s: &StructSpecifier) -> Self { + let fields = s.fields.0.iter().map(|field| lift(state, field)).collect(); + Self { fields } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum RunClass { + Unknown, + Scalar, + Vector, + Dependent(u32), +} + +impl RunClass { + pub fn merge(self, run_class: RunClass) -> RunClass { + match (self, run_class) { + (RunClass::Vector, _) | (_, RunClass::Vector) => RunClass::Vector, + (RunClass::Dependent(x), RunClass::Dependent(y)) => RunClass::Dependent(x | y), + (RunClass::Unknown, _) | (_, RunClass::Dependent(..)) => run_class, + _ => self, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SymDecl { + NativeFunction(FunctionType, Option<&'static str>), + UserFunction(Rc, RunClass), + Local(StorageClass, Type, RunClass), + Global( + StorageClass, + Option, + Type, + RunClass, + ), + Struct(StructFields), +} + +#[derive(Clone, Debug, PartialEq, Copy, Eq, Hash)] +pub struct SymRef(u32); + +#[derive(Debug)] +struct Scope { + name: String, + names: HashMap, +} +impl Scope { + fn new(name: String) -> Self { + Scope { + name, + names: HashMap::new(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TexelFetchOffsets { + pub min_x: i32, + pub max_x: i32, + pub min_y: i32, + pub max_y: i32, +} + +impl TexelFetchOffsets { + fn new(x: i32, y: i32) -> Self { + TexelFetchOffsets { + min_x: x, + max_x: x, + min_y: y, + max_y: y, + } + } + + fn add_offset(&mut self, x: i32, y: i32) { + self.min_x = self.min_x.min(x); + self.max_x = self.max_x.max(x); + self.min_y = self.min_y.min(y); + self.max_y = self.max_y.max(y); + } +} + +#[derive(Debug)] +pub struct State { + scopes: Vec, + syms: Vec>, + in_function: Option, + run_class_changed: Cell, + last_declaration: SymRef, + branch_run_class: RunClass, + branch_declaration: SymRef, + modified_globals: RefCell>, + pub used_fragcoord: i32, + pub used_globals: RefCell>, + pub texel_fetches: HashMap<(SymRef, SymRef), TexelFetchOffsets>, +} + +impl State { + pub fn new() -> Self { + State { + scopes: Vec::new(), + syms: Vec::new(), + in_function: None, + run_class_changed: Cell::new(false), + last_declaration: SymRef(0), + branch_run_class: RunClass::Unknown, + branch_declaration: SymRef(0), + modified_globals: RefCell::new(Vec::new()), + used_fragcoord: 0, + used_globals: RefCell::new(Vec::new()), + texel_fetches: HashMap::new(), + } + } + + pub fn lookup(&self, name: &str) -> Option { + for s in self.scopes.iter().rev() { + if let Some(sym) = s.names.get(name) { + return Some(*sym); + } + } + return None; + } + + fn declare(&mut self, name: &str, decl: SymDecl) -> SymRef { + let s = SymRef(self.syms.len() as u32); + self.syms.push(RefCell::new(Symbol { + name: name.into(), + decl, + })); + self.scopes.last_mut().unwrap().names.insert(name.into(), s); + s + } + + pub fn sym(&self, sym: SymRef) -> Ref { + self.syms[sym.0 as usize].borrow() + } + + pub fn sym_mut(&mut self, sym: SymRef) -> &mut Symbol { + self.syms[sym.0 as usize].get_mut() + } + + pub fn lookup_sym_mut(&mut self, name: &str) -> Option<&mut Symbol> { + self.lookup(name) + .map(move |x| self.syms[x.0 as usize].get_mut()) + } + + fn push_scope(&mut self, name: String) { + self.scopes.push(Scope::new(name)); + } + fn pop_scope(&mut self) { + self.scopes.pop(); + } + + fn return_run_class(&self, mut new_run_class: RunClass) { + new_run_class = self.branch_run_class.merge(new_run_class); + if let Some(sym) = self.in_function { + let mut b = self.syms[sym.0 as usize].borrow_mut(); + if let SymDecl::UserFunction(_, ref mut run_class) = b.decl { + *run_class = run_class.merge(new_run_class); + } + } + } + + pub fn function_definition(&self, name: SymRef) -> Option<(Rc, RunClass)> { + if let SymDecl::UserFunction(ref fd, ref run_class) = &self.sym(name).decl { + Some((fd.clone(), *run_class)) + } else { + None + } + } + + fn merge_run_class(&self, sym: SymRef, mut new_run_class: RunClass) -> RunClass { + if sym.0 <= self.branch_declaration.0 { + new_run_class = self.branch_run_class.merge(new_run_class); + } + let mut b = self.syms[sym.0 as usize].borrow_mut(); + let mut old_run_class = new_run_class; + if let SymDecl::Local(_, _, ref mut run_class) = b.decl { + old_run_class = *run_class; + new_run_class = old_run_class.merge(new_run_class); + *run_class = new_run_class; + } + if old_run_class != RunClass::Unknown && old_run_class != new_run_class { + self.run_class_changed.set(true); + } + new_run_class + } +} + +/// A declaration. +#[derive(Clone, Debug, PartialEq)] +pub enum Declaration { + FunctionPrototype(FunctionPrototype), + StructDefinition(SymRef), + InitDeclaratorList(InitDeclaratorList), + Precision(PrecisionQualifier, TypeSpecifier), + Block(Block), + Global(TypeQualifier, Vec), +} + +/// A general purpose block, containing fields and possibly a list of declared identifiers. Semantic +/// is given with the storage qualifier. +#[derive(Clone, Debug, PartialEq)] +pub struct Block { + pub qualifier: TypeQualifier, + pub name: Identifier, + pub fields: Vec, + pub identifier: Option, +} + +/// Function identifier. +#[derive(Clone, Debug, PartialEq)] +pub enum FunIdentifier { + Identifier(SymRef), + Constructor(Type), +} + +/// Function prototype. +#[derive(Clone, Debug, PartialEq)] +pub struct FunctionPrototype { + pub ty: Type, + pub name: Identifier, + pub parameters: Vec, +} + +impl FunctionPrototype { + pub fn has_parameter(&self, sym: SymRef) -> bool { + for param in &self.parameters { + match param { + FunctionParameterDeclaration::Named(_, ref d) => { + if d.sym == sym { + return true; + } + } + _ => {} + } + } + false + } +} + +/// Function parameter declaration. +#[derive(Clone, Debug, PartialEq)] +pub enum FunctionParameterDeclaration { + Named(Option, FunctionParameterDeclarator), + Unnamed(Option, TypeSpecifier), +} + +/// Function parameter declarator. +#[derive(Clone, Debug, PartialEq)] +pub struct FunctionParameterDeclarator { + pub ty: Type, + pub name: Identifier, + pub sym: SymRef, +} + +/// Init declarator list. +#[derive(Clone, Debug, PartialEq)] +pub struct InitDeclaratorList { + // XXX it feels like separating out the type and the names is better than + // head and tail + // Also, it might be nice to separate out type definitions from name definitions + pub head: SingleDeclaration, + pub tail: Vec, +} + +/// Type qualifier. +#[derive(Clone, Debug, PartialEq)] +pub struct TypeQualifier { + pub qualifiers: NonEmpty, +} + +fn lift_type_qualifier_for_declaration( + _state: &mut State, + q: &Option, +) -> Option { + q.as_ref().and_then(|x| { + NonEmpty::from_non_empty_iter(x.qualifiers.0.iter().flat_map(|x| match x { + syntax::TypeQualifierSpec::Precision(_) => None, + syntax::TypeQualifierSpec::Interpolation(_) => None, + syntax::TypeQualifierSpec::Invariant => Some(TypeQualifierSpec::Invariant), + syntax::TypeQualifierSpec::Layout(l) => Some(TypeQualifierSpec::Layout(l.clone())), + syntax::TypeQualifierSpec::Precise => Some(TypeQualifierSpec::Precise), + syntax::TypeQualifierSpec::Storage(_) => None, + })) + .map(|x| TypeQualifier { qualifiers: x }) + }) +} + +fn lift_type_qualifier_for_parameter( + _state: &mut State, + q: &Option, +) -> Option { + let mut qp: Option = None; + if let Some(q) = q { + for x in &q.qualifiers.0 { + match (&qp, x) { + (None, syntax::TypeQualifierSpec::Storage(s)) => match s { + syntax::StorageQualifier::Const => qp = Some(ParameterQualifier::Const), + syntax::StorageQualifier::In => qp = Some(ParameterQualifier::In), + syntax::StorageQualifier::Out => qp = Some(ParameterQualifier::Out), + syntax::StorageQualifier::InOut => qp = Some(ParameterQualifier::InOut), + _ => panic!("Bad storage qualifier for parameter"), + }, + (_, syntax::TypeQualifierSpec::Precision(_)) => {} + _ => panic!("Bad parameter qualifier {:?}", x), + } + } + } + qp +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ParameterQualifier { + Const, + In, + InOut, + Out, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum MemoryQualifier { + Coherent, + Volatile, + Restrict, + ReadOnly, + WriteOnly, +} + +/// Type qualifier spec. +#[derive(Clone, Debug, PartialEq)] +pub enum TypeQualifierSpec { + Layout(syntax::LayoutQualifier), + Invariant, + Parameter(ParameterQualifier), + Memory(MemoryQualifier), + Precise, +} + +/// Single declaration. +#[derive(Clone, Debug, PartialEq)] +pub struct SingleDeclaration { + pub ty: Type, + pub ty_def: Option, + pub qualifier: Option, + pub name: SymRef, + pub initializer: Option, +} + +/// A single declaration with implicit, already-defined type. +#[derive(Clone, Debug, PartialEq)] +pub struct SingleDeclarationNoType { + pub ident: ArrayedIdentifier, + pub initializer: Option, +} + +/// Initializer. +#[derive(Clone, Debug, PartialEq)] +pub enum Initializer { + Simple(Box), + List(NonEmpty), +} + +impl From for Initializer { + fn from(e: Expr) -> Self { + Initializer::Simple(Box::new(e)) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Expr { + pub kind: ExprKind, + pub ty: Type, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum FieldSet { + Rgba, + Xyzw, + Stpq, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SwizzleSelector { + pub field_set: FieldSet, + pub components: Vec, +} + +impl SwizzleSelector { + fn parse(s: &str) -> Self { + let mut components = Vec::new(); + let mut field_set = Vec::new(); + + for c in s.chars() { + match c { + 'r' => { + components.push(0); + field_set.push(FieldSet::Rgba); + } + 'x' => { + components.push(0); + field_set.push(FieldSet::Xyzw); + } + 's' => { + components.push(0); + field_set.push(FieldSet::Stpq); + } + + 'g' => { + components.push(1); + field_set.push(FieldSet::Rgba); + } + 'y' => { + components.push(1); + field_set.push(FieldSet::Xyzw); + } + 't' => { + components.push(1); + field_set.push(FieldSet::Stpq); + } + + 'b' => { + components.push(2); + field_set.push(FieldSet::Rgba); + } + 'z' => { + components.push(2); + field_set.push(FieldSet::Xyzw); + } + 'p' => { + components.push(2); + field_set.push(FieldSet::Stpq); + } + + 'a' => { + components.push(3); + field_set.push(FieldSet::Rgba); + } + 'w' => { + components.push(3); + field_set.push(FieldSet::Xyzw); + } + 'q' => { + components.push(3); + field_set.push(FieldSet::Stpq); + } + _ => panic!("bad selector"), + } + } + + let first = &field_set[0]; + assert!(field_set.iter().all(|item| item == first)); + assert!(components.len() <= 4); + SwizzleSelector { + field_set: first.clone(), + components, + } + } + + pub fn to_string(&self) -> String { + let mut s = String::new(); + let fs = match self.field_set { + FieldSet::Rgba => ['r', 'g', 'b', 'a'], + FieldSet::Xyzw => ['x', 'y', 'z', 'w'], + FieldSet::Stpq => ['s', 't', 'p', 'q'], + }; + for i in &self.components { + s.push(fs[*i as usize]) + } + s + } +} + +/// The most general form of an expression. As you can see if you read the variant list, in GLSL, an +/// assignment is an expression. This is a bit silly but think of an assignment as a statement first +/// then an expression which evaluates to what the statement “returns”. +/// +/// An expression is either an assignment or a list (comma) of assignments. +#[derive(Clone, Debug, PartialEq)] +pub enum ExprKind { + /// A variable expression, using an identifier. + Variable(SymRef), + /// Integral constant expression. + IntConst(i32), + /// Unsigned integral constant expression. + UIntConst(u32), + /// Boolean constant expression. + BoolConst(bool), + /// Single precision floating expression. + FloatConst(f32), + /// Double precision floating expression. + DoubleConst(f64), + /// A unary expression, gathering a single expression and a unary operator. + Unary(UnaryOp, Box), + /// A binary expression, gathering two expressions and a binary operator. + Binary(BinaryOp, Box, Box), + /// A ternary conditional expression, gathering three expressions. + Ternary(Box, Box, Box), + /// An assignment is also an expression. Gathers an expression that defines what to assign to, an + /// assignment operator and the value to associate with. + Assignment(Box, AssignmentOp, Box), + /// Add an array specifier to an expression. + Bracket(Box, Box), + /// A functional call. It has a function identifier and a list of expressions (arguments). + FunCall(FunIdentifier, Vec), + /// An expression associated with a field selection (struct). + Dot(Box, Identifier), + /// An expression associated with a component selection + SwizzleSelector(Box, SwizzleSelector), + /// Post-incrementation of an expression. + PostInc(Box), + /// Post-decrementation of an expression. + PostDec(Box), + /// An expression that contains several, separated with comma. + Comma(Box, Box), + /// A temporary condition variable + Cond(usize, Box), + CondMask, +} + +/* +impl From for Expr { + fn from(x: i32) -> Expr { + ExprKind::IntConst(x) + } +} + +impl From for Expr { + fn from(x: u32) -> Expr { + Expr::UIntConst(x) + } +} + +impl From for Expr { + fn from(x: bool) -> Expr { + Expr::BoolConst(x) + } +} + +impl From for Expr { + fn from(x: f32) -> Expr { + Expr::FloatConst(x) + } +} + +impl From for Expr { + fn from(x: f64) -> Expr { + Expr::DoubleConst(x) + } +} +*/ +/// Starting rule. +#[derive(Clone, Debug, PartialEq)] +pub struct TranslationUnit(pub NonEmpty); + +impl TranslationUnit { + /// Construct a translation unit from an iterator. + /// + /// # Errors + /// + /// `None` if the iterator yields no value. + pub fn from_iter(iter: I) -> Option + where + I: IntoIterator, + { + NonEmpty::from_non_empty_iter(iter).map(TranslationUnit) + } +} + +impl Deref for TranslationUnit { + type Target = NonEmpty; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for TranslationUnit { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl IntoIterator for TranslationUnit { + type IntoIter = as IntoIterator>::IntoIter; + type Item = ExternalDeclaration; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a TranslationUnit { + type IntoIter = <&'a NonEmpty as IntoIterator>::IntoIter; + type Item = &'a ExternalDeclaration; + + fn into_iter(self) -> Self::IntoIter { + (&self.0).into_iter() + } +} + +impl<'a> IntoIterator for &'a mut TranslationUnit { + type IntoIter = <&'a mut NonEmpty as IntoIterator>::IntoIter; + type Item = &'a mut ExternalDeclaration; + + fn into_iter(self) -> Self::IntoIter { + (&mut self.0).into_iter() + } +} + +/// External declaration. +#[derive(Clone, Debug, PartialEq)] +pub enum ExternalDeclaration { + Preprocessor(syntax::Preprocessor), + FunctionDefinition(Rc), + Declaration(Declaration), +} + +/// Function definition. +#[derive(Clone, Debug, PartialEq)] +pub struct FunctionDefinition { + pub prototype: FunctionPrototype, + pub body: CompoundStatement, + pub globals: Vec, + pub texel_fetches: HashMap<(SymRef, SymRef), TexelFetchOffsets>, +} + +/// Compound statement (with no new scope). +#[derive(Clone, Debug, PartialEq)] +pub struct CompoundStatement { + pub statement_list: Vec, +} + +impl CompoundStatement { + pub fn new() -> Self { + CompoundStatement { + statement_list: Vec::new(), + } + } +} + +impl FromIterator for CompoundStatement { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + CompoundStatement { + statement_list: iter.into_iter().collect(), + } + } +} + +/// Statement. +#[derive(Clone, Debug, PartialEq)] +pub enum Statement { + Compound(Box), + Simple(Box), +} + +/// Simple statement. +#[derive(Clone, Debug, PartialEq)] +pub enum SimpleStatement { + Declaration(Declaration), + Expression(ExprStatement), + Selection(SelectionStatement), + Switch(SwitchStatement), + Iteration(IterationStatement), + Jump(JumpStatement), +} + +impl SimpleStatement { + /// Create a new expression statement. + pub fn new_expr(expr: E) -> Self + where + E: Into, + { + SimpleStatement::Expression(Some(expr.into())) + } + + /// Create a new selection statement (if / else). + pub fn new_if_else(ife: If, truee: True, falsee: False) -> Self + where + If: Into, + True: Into, + False: Into, + { + SimpleStatement::Selection(SelectionStatement { + cond: Box::new(ife.into()), + body: Box::new(truee.into()), + else_stmt: Some(Box::new(falsee.into())), + }) + } + + /// Create a new while statement. + pub fn new_while(cond: C, body: S) -> Self + where + C: Into, + S: Into, + { + SimpleStatement::Iteration(IterationStatement::While( + cond.into(), + Box::new(body.into()), + )) + } + + /// Create a new do-while statement. + pub fn new_do_while(body: S, cond: C) -> Self + where + S: Into, + C: Into, + { + SimpleStatement::Iteration(IterationStatement::DoWhile( + Box::new(body.into()), + Box::new(cond.into()), + )) + } +} + +/// Expression statement. +pub type ExprStatement = Option; + +/// Selection statement. +#[derive(Clone, Debug, PartialEq)] +pub struct SelectionStatement { + pub cond: Box, + pub body: Box, + // the else branch + pub else_stmt: Option>, +} + +/// Condition. +#[derive(Clone, Debug, PartialEq)] +pub enum Condition { + Expr(Box), +} + +impl From for Condition { + fn from(expr: Expr) -> Self { + Condition::Expr(Box::new(expr)) + } +} + +/// Switch statement. +#[derive(Clone, Debug, PartialEq)] +pub struct SwitchStatement { + pub head: Box, + pub cases: Vec, +} + +/// Case label statement. +#[derive(Clone, Debug, PartialEq)] +pub enum CaseLabel { + Case(Box), + Def, +} + +/// An individual case +#[derive(Clone, Debug, PartialEq)] +pub struct Case { + pub label: CaseLabel, + pub stmts: Vec, +} + +/// Iteration statement. +#[derive(Clone, Debug, PartialEq)] +pub enum IterationStatement { + While(Condition, Box), + DoWhile(Box, Box), + For(ForInitStatement, ForRestStatement, Box), +} + +/// For init statement. +#[derive(Clone, Debug, PartialEq)] +pub enum ForInitStatement { + Expression(Option), + Declaration(Box), +} + +/// For init statement. +#[derive(Clone, Debug, PartialEq)] +pub struct ForRestStatement { + pub condition: Option, + pub post_expr: Option>, +} + +/// Jump statement. +#[derive(Clone, Debug, PartialEq)] +pub enum JumpStatement { + Continue, + Break, + Return(Option>), + Discard, +} + +trait NonEmptyExt { + fn map U>(&self, s: &mut State, f: F) -> NonEmpty; + fn new(x: T) -> NonEmpty; +} + +impl NonEmptyExt for NonEmpty { + fn map U>(&self, s: &mut State, mut f: F) -> NonEmpty { + NonEmpty::from_non_empty_iter(self.into_iter().map(|x| f(s, &x))).unwrap() + } + fn new(x: T) -> NonEmpty { + NonEmpty::from_non_empty_iter(vec![x].into_iter()).unwrap() + } +} + +fn translate_initializater(state: &mut State, i: &syntax::Initializer) -> Initializer { + match i { + syntax::Initializer::Simple(i) => { + Initializer::Simple(Box::new(translate_expression(state, i))) + } + _ => panic!(), + } +} + +fn translate_struct_declaration(state: &mut State, d: &syntax::SingleDeclaration) -> Declaration { + let ty = d.ty.clone(); + let ty_def = match &ty.ty.ty { + TypeSpecifierNonArray::Struct(s) => { + let decl = SymDecl::Struct(lift(state, s)); + Some(state.declare(s.name.as_ref().unwrap().as_str(), decl)) + } + _ => None, + }; + + let ty_def = ty_def.expect("Must be type definition"); + + Declaration::StructDefinition(ty_def) +} + +fn get_expr_index(e: &syntax::Expr) -> i32 { + match e { + syntax::Expr::IntConst(i) => *i, + syntax::Expr::UIntConst(u) => *u as i32, + syntax::Expr::FloatConst(f) => *f as i32, + syntax::Expr::DoubleConst(f) => *f as i32, + _ => panic!(), + } +} + +fn translate_variable_declaration( + state: &mut State, + d: &syntax::InitDeclaratorList, + default_run_class: RunClass, +) -> Declaration { + let mut ty = d.head.ty.clone(); + ty.ty.array_specifier = d.head.array_specifier.clone(); + let ty_def = match &ty.ty.ty { + TypeSpecifierNonArray::Struct(s) => { + let decl = SymDecl::Struct(lift(state, s)); + Some(state.declare(s.name.as_ref().unwrap().as_str(), decl)) + } + _ => None, + }; + + let mut ty: Type = lift(state, &d.head.ty); + if let Some(array) = &d.head.array_specifier { + ty.array_sizes = Some(Box::new(lift(state, array))) + } + + let (sym, decl) = match d.head.name.as_ref() { + Some(name) => { + let mut storage = StorageClass::None; + let mut interpolation = None; + for qual in d + .head + .ty + .qualifier + .iter() + .flat_map(|x| x.qualifiers.0.iter()) + { + match qual { + syntax::TypeQualifierSpec::Storage(s) => match (&storage, s) { + (StorageClass::FragColor(..), syntax::StorageQualifier::Out) => {} + (StorageClass::Sampler(..), syntax::StorageQualifier::Uniform) => {} + (StorageClass::None, syntax::StorageQualifier::Out) => { + storage = StorageClass::Out; + } + (StorageClass::None, syntax::StorageQualifier::In) => { + storage = StorageClass::In; + } + (StorageClass::None, syntax::StorageQualifier::Uniform) => { + if ty.kind.is_sampler() { + storage = StorageClass::Sampler(SamplerFormat::Unknown); + } else { + storage = StorageClass::Uniform; + } + } + (StorageClass::None, syntax::StorageQualifier::Const) => { + storage = StorageClass::Const; + } + _ => panic!("bad storage {:?}", (storage, s)), + }, + syntax::TypeQualifierSpec::Interpolation(i) => match (&interpolation, i) { + (None, i) => interpolation = Some(i.clone()), + _ => panic!("multiple interpolation"), + }, + syntax::TypeQualifierSpec::Layout(l) => { + let mut loc = -1; + let mut index = -1; + for id in &l.ids { + match id { + syntax::LayoutQualifierSpec::Identifier(ref key, None) => { + match key.as_str() { + "rgba8" => { + storage = StorageClass::Sampler(SamplerFormat::RGBA8); + } + "rgba32f" => { + storage = StorageClass::Sampler(SamplerFormat::RGBA32F); + } + "rgba32i" => { + storage = StorageClass::Sampler(SamplerFormat::RGBA32I); + } + "r8" => { + storage = StorageClass::Sampler(SamplerFormat::R8); + } + _ => {} + } + } + syntax::LayoutQualifierSpec::Identifier(ref key, Some(ref e)) => { + match key.as_str() { + "location" => { + loc = get_expr_index(e); + } + "index" => { + index = get_expr_index(e); + } + _ => {} + } + } + _ => {} + } + } + if index >= 0 { + assert!(loc == 0); + assert!(index <= 1); + assert!(storage == StorageClass::None); + storage = StorageClass::FragColor(index); + } + } + _ => {} + } + } + let decl = if state.in_function.is_some() { + let run_class = match storage { + StorageClass::Const => RunClass::Scalar, + StorageClass::None => default_run_class, + _ => panic!("bad local storage {:?}", storage), + }; + SymDecl::Local(storage, ty.clone(), run_class) + } else { + let run_class = match storage { + StorageClass::Const | StorageClass::Uniform | StorageClass::Sampler(..) => { + RunClass::Scalar + } + StorageClass::In | StorageClass::Out | StorageClass::FragColor(..) + if interpolation == Some(syntax::InterpolationQualifier::Flat) => + { + RunClass::Scalar + } + _ => RunClass::Vector, + }; + SymDecl::Global(storage, interpolation, ty.clone(), run_class) + }; + (state.declare(name.as_str(), decl.clone()), decl) + } + None => panic!(), + }; + + let head = SingleDeclaration { + qualifier: lift_type_qualifier_for_declaration(state, &d.head.ty.qualifier), + name: sym, + ty, + ty_def, + initializer: d + .head + .initializer + .as_ref() + .map(|x| translate_initializater(state, x)), + }; + + let tail = d + .tail + .iter() + .map(|d| { + if let Some(_array) = &d.ident.array_spec { + panic!("unhandled array") + } + state.declare(d.ident.ident.as_str(), decl.clone()); + SingleDeclarationNoType { + ident: d.ident.clone(), + initializer: d + .initializer + .as_ref() + .map(|x| translate_initializater(state, x)), + } + }) + .collect(); + Declaration::InitDeclaratorList(InitDeclaratorList { head, tail }) +} + +fn translate_init_declarator_list( + state: &mut State, + l: &syntax::InitDeclaratorList, + default_run_class: RunClass, +) -> Declaration { + match &l.head.name { + Some(_name) => translate_variable_declaration(state, l, default_run_class), + None => translate_struct_declaration(state, &l.head), + } +} + +fn translate_declaration( + state: &mut State, + d: &syntax::Declaration, + default_run_class: RunClass, +) -> Declaration { + match d { + syntax::Declaration::Block(_) => panic!(), //Declaration::Block(..), + syntax::Declaration::FunctionPrototype(p) => { + Declaration::FunctionPrototype(translate_function_prototype(state, p)) + } + syntax::Declaration::Global(_ty, _ids) => { + panic!(); + // glsl non-es supports requalifying variables + // we don't right now + //Declaration::Global(..) + } + syntax::Declaration::InitDeclaratorList(dl) => { + translate_init_declarator_list(state, dl, default_run_class) + } + syntax::Declaration::Precision(p, ts) => Declaration::Precision(p.clone(), ts.clone()), + } +} + +fn is_vector(ty: &Type) -> bool { + match ty.kind { + TypeKind::Vec2 + | TypeKind::Vec3 + | TypeKind::Vec4 + | TypeKind::IVec2 + | TypeKind::IVec3 + | TypeKind::IVec4 => ty.array_sizes == None, + _ => false, + } +} + +fn index_matrix(ty: &Type) -> Option { + use TypeKind::*; + if ty.array_sizes != None { + return None; + } + Some(match ty.kind { + Mat2 => Vec2, + Mat3 => Vec3, + Mat4 => Vec4, + Mat23 => Vec3, + Mat24 => Vec4, + Mat32 => Vec2, + Mat34 => Vec4, + Mat42 => Vec2, + Mat43 => Vec3, + DMat2 => DVec2, + DMat3 => DVec3, + DMat4 => DVec4, + DMat23 => DVec3, + DMat24 => DVec4, + DMat32 => DVec2, + DMat34 => DVec4, + DMat42 => DVec2, + DMat43 => DVec3, + _ => return None, + }) +} + +fn is_ivec(ty: &Type) -> bool { + match ty.kind { + TypeKind::IVec2 | TypeKind::IVec3 | TypeKind::IVec4 => ty.array_sizes == None, + _ => false, + } +} + +fn compatible_type(lhs: &Type, rhs: &Type) -> bool { + // XXX: use an underlying type helper + if lhs == &Type::new(TypeKind::Double) && rhs == &Type::new(TypeKind::Float) { + true + } else if rhs == &Type::new(TypeKind::Double) && lhs == &Type::new(TypeKind::Float) { + true + } else if rhs == &Type::new(TypeKind::Int) && + (lhs == &Type::new(TypeKind::Float) || lhs == &Type::new(TypeKind::Double)) + { + true + } else if (rhs == &Type::new(TypeKind::Float) || rhs == &Type::new(TypeKind::Double)) && + lhs == &Type::new(TypeKind::Int) + { + true + } else if (rhs == &Type::new(TypeKind::Vec2) || rhs == &Type::new(TypeKind::DVec2)) && + lhs == &Type::new(TypeKind::IVec2) + { + true + } else if rhs == &Type::new(TypeKind::IVec2) && + (lhs == &Type::new(TypeKind::Vec2) || lhs == &Type::new(TypeKind::DVec2)) + { + true + } else { + lhs.kind == rhs.kind && lhs.array_sizes == rhs.array_sizes + } +} + +fn promoted_type(lhs: &Type, rhs: &Type) -> Type { + if lhs == &Type::new(TypeKind::Double) && rhs == &Type::new(TypeKind::Float) { + Type::new(TypeKind::Double) + } else if lhs == &Type::new(TypeKind::Float) && rhs == &Type::new(TypeKind::Double) { + Type::new(TypeKind::Double) + } else if lhs == &Type::new(TypeKind::Int) && rhs == &Type::new(TypeKind::Double) { + Type::new(TypeKind::Double) + } else if is_vector(&lhs) && + (rhs == &Type::new(TypeKind::Float) || + rhs == &Type::new(TypeKind::Double) || + rhs == &Type::new(TypeKind::Int)) + { + // scalars promote to vectors + lhs.clone() + } else if is_vector(&rhs) && + (lhs == &Type::new(TypeKind::Float) || + lhs == &Type::new(TypeKind::Double) || + lhs == &Type::new(TypeKind::Int)) + { + // scalars promote to vectors + rhs.clone() + } else if lhs == rhs { + lhs.clone() + } else if lhs.kind == rhs.kind { + if lhs.array_sizes == rhs.array_sizes { + // XXX: we need to be able to query the default precision here + match (&lhs.precision, &rhs.precision) { + (Some(PrecisionQualifier::High), _) => lhs.clone(), + (_, Some(PrecisionQualifier::High)) => rhs.clone(), + (None, _) => lhs.clone(), + (_, None) => rhs.clone(), + _ => panic!("precision mismatch {:?} {:?}", lhs.precision, rhs.precision), + } + } else { + panic!("array size mismatch") + } + } else { + assert_eq!(lhs, rhs); + lhs.clone() + } +} + +pub fn is_output(expr: &Expr, state: &State) -> Option { + match &expr.kind { + ExprKind::Variable(i) => match state.sym(*i).decl { + SymDecl::Global(storage, ..) => match storage { + StorageClass::Out => return Some(*i), + _ => {} + }, + SymDecl::Local(..) => {} + _ => panic!("should be variable"), + }, + ExprKind::SwizzleSelector(e, ..) => { + return is_output(e, state); + } + ExprKind::Bracket(e, ..) => { + return is_output(e, state); + } + ExprKind::Dot(e, ..) => { + return is_output(e, state); + } + _ => {} + }; + None +} + +pub fn get_texel_fetch_offset( + state: &State, + sampler_expr: &Expr, + uv_expr: &Expr, + offset_expr: &Expr, +) -> Option<(SymRef, SymRef, i32, i32)> { + if let ExprKind::Variable(ref sampler) = &sampler_expr.kind { + //if let ExprKind::Binary(BinaryOp::Add, ref lhs, ref rhs) = &uv_expr.kind { + if let ExprKind::Variable(ref base) = &uv_expr.kind { + if let ExprKind::FunCall(ref fun, ref args) = &offset_expr.kind { + if let FunIdentifier::Identifier(ref offset) = fun { + if state.sym(*offset).name == "ivec2" { + if let ExprKind::IntConst(ref x) = &args[0].kind { + if let ExprKind::IntConst(ref y) = &args[1].kind { + return Some((*sampler, *base, *x, *y)); + } + } + } + } + } + } + //} + } + None +} + +fn translate_expression(state: &mut State, e: &syntax::Expr) -> Expr { + match e { + syntax::Expr::Variable(i) => { + let sym = match state.lookup(i.as_str()) { + Some(sym) => sym, + None => panic!("missing declaration {}", i.as_str()), + }; + let ty = match &state.sym(sym).decl { + SymDecl::Global(_, _, ty, _) => { + let mut globals = state.used_globals.borrow_mut(); + if !globals.contains(&sym) { + globals.push(sym); + } + ty.clone() + } + SymDecl::Local(_, ty, _) => ty.clone(), + _ => panic!("bad variable type"), + }; + Expr { + kind: ExprKind::Variable(sym), + ty, + } + } + syntax::Expr::Assignment(lhs, op, rhs) => { + let lhs = Box::new(translate_expression(state, lhs)); + let rhs = Box::new(translate_expression(state, rhs)); + let ty = if op == &AssignmentOp::Mult { + if lhs.ty.kind == TypeKind::Vec4 && rhs.ty.kind == TypeKind::Float { + lhs.ty.clone() + } else { + promoted_type(&lhs.ty, &rhs.ty) + } + } else { + promoted_type(&lhs.ty, &rhs.ty) + }; + if let Some(global) = is_output(&lhs, state) { + let mut globals = state.modified_globals.borrow_mut(); + if !globals.contains(&global) { + globals.push(global); + } + } + Expr { + kind: ExprKind::Assignment(lhs, op.clone(), rhs), + ty, + } + } + syntax::Expr::Binary(op, lhs, rhs) => { + let lhs = Box::new(translate_expression(state, lhs)); + let rhs = Box::new(translate_expression(state, rhs)); + let ty = if op == &BinaryOp::Mult { + if lhs.ty.kind == TypeKind::Mat3 && rhs.ty.kind == TypeKind::Vec3 { + rhs.ty.clone() + } else if lhs.ty.kind == TypeKind::Mat4 && rhs.ty.kind == TypeKind::Vec4 { + rhs.ty.clone() + } else if lhs.ty.kind == TypeKind::Mat2 && rhs.ty.kind == TypeKind::Vec2 { + rhs.ty.clone() + } else if lhs.ty.kind == TypeKind::Mat2 && rhs.ty.kind == TypeKind::Float { + lhs.ty.clone() + } else { + promoted_type(&lhs.ty, &rhs.ty) + } + } else { + promoted_type(&lhs.ty, &rhs.ty) + }; + + // comparison operators have a bool result + let ty = match op { + BinaryOp::Equal | BinaryOp::GT | BinaryOp::GTE | BinaryOp::LT | BinaryOp::LTE => { + Type::new(TypeKind::Bool) + } + _ => ty, + }; + + Expr { + kind: ExprKind::Binary(op.clone(), lhs, rhs), + ty, + } + } + syntax::Expr::Unary(op, e) => { + let e = Box::new(translate_expression(state, e)); + let ty = e.ty.clone(); + Expr { + kind: ExprKind::Unary(op.clone(), e), + ty, + } + } + syntax::Expr::BoolConst(b) => Expr { + kind: ExprKind::BoolConst(*b), + ty: Type::new(TypeKind::Bool), + }, + syntax::Expr::Comma(lhs, rhs) => { + let lhs = Box::new(translate_expression(state, lhs)); + let rhs = Box::new(translate_expression(state, rhs)); + assert_eq!(lhs.ty, rhs.ty); + let ty = lhs.ty.clone(); + Expr { + kind: ExprKind::Comma(lhs, rhs), + ty, + } + } + syntax::Expr::DoubleConst(d) => Expr { + kind: ExprKind::DoubleConst(*d), + ty: Type::new(TypeKind::Double), + }, + syntax::Expr::FloatConst(f) => Expr { + kind: ExprKind::FloatConst(*f), + ty: Type::new(TypeKind::Float), + }, + syntax::Expr::FunCall(fun, params) => { + let ret_ty: Type; + let params: Vec = params + .iter() + .map(|x| translate_expression(state, x)) + .collect(); + Expr { + kind: ExprKind::FunCall( + match fun { + syntax::FunIdentifier::Identifier(i) => { + let name = i.as_str(); + if name == "texelFetchOffset" && params.len() >= 4 { + if let Some((sampler, base, x, y)) = get_texel_fetch_offset( + state, ¶ms[0], ¶ms[1], ¶ms[3], + ) { + if let Some(offsets) = + state.texel_fetches.get_mut(&(sampler, base)) + { + offsets.add_offset(x, y); + } else { + state + .texel_fetches + .insert((sampler, base), TexelFetchOffsets::new(x, y)); + } + } + } + let sym = match state.lookup(name) { + Some(s) => s, + None => panic!("missing symbol {}", name), + }; + match &state.sym(sym).decl { + SymDecl::NativeFunction(fn_ty, _) => { + let mut ret = None; + for sig in &fn_ty.signatures { + let mut matching = true; + for (e, p) in params.iter().zip(sig.params.iter()) { + if !compatible_type(&e.ty, p) { + matching = false; + break; + } + } + if matching { + ret = Some(sig.ret.clone()); + break; + } + } + ret_ty = match ret { + Some(t) => t, + None => { + dbg!(&fn_ty.signatures); + dbg!(params.iter().map(|p| p).collect::>()); + panic!("no matching func {}", i.as_str()) + } + }; + } + SymDecl::UserFunction(fd, _) => { + let mut globals = state.modified_globals.borrow_mut(); + for global in &fd.globals { + if !globals.contains(global) { + globals.push(*global); + } + } + let mut matching = true; + for (e, p) in params.iter().zip(fd.prototype.parameters.iter()) + { + matching &= match p { + FunctionParameterDeclaration::Named(q, d) => { + match q { + Some(ParameterQualifier::InOut) + | Some(ParameterQualifier::Out) => { + if let Some(global) = is_output(e, state) { + if !globals.contains(&global) { + globals.push(global); + } + } + } + _ => {} + } + compatible_type(&e.ty, &d.ty) + } + FunctionParameterDeclaration::Unnamed(..) => panic!(), + }; + } + assert!(matching); + ret_ty = fd.prototype.ty.clone(); + } + SymDecl::Struct(_) => ret_ty = Type::new(TypeKind::Struct(sym)), + _ => panic!("can only call functions"), + }; + FunIdentifier::Identifier(sym) + } + // array constructor + syntax::FunIdentifier::Expr(e) => { + let ty = match &**e { + syntax::Expr::Bracket(i, array) => { + let kind = match &**i { + syntax::Expr::Variable(i) => match i.as_str() { + "vec4" => TypeKind::Vec4, + "vec2" => TypeKind::Vec2, + _ => panic!("unexpected type constructor {:?}", i), + }, + _ => panic!(), + }; + + Type { + kind, + precision: None, + array_sizes: Some(Box::new(lift(state, array))), + } + } + _ => panic!(), + }; + ret_ty = ty.clone(); + + FunIdentifier::Constructor(ty) + } + }, + params, + ), + ty: ret_ty, + } + } + syntax::Expr::IntConst(i) => Expr { + kind: ExprKind::IntConst(*i), + ty: Type::new(TypeKind::Int), + }, + syntax::Expr::UIntConst(u) => Expr { + kind: ExprKind::UIntConst(*u), + ty: Type::new(TypeKind::UInt), + }, + syntax::Expr::PostDec(e) => { + let e = Box::new(translate_expression(state, e)); + let ty = e.ty.clone(); + Expr { + kind: ExprKind::PostDec(e), + ty, + } + } + syntax::Expr::PostInc(e) => { + let e = Box::new(translate_expression(state, e)); + let ty = e.ty.clone(); + Expr { + kind: ExprKind::PostInc(e), + ty, + } + } + syntax::Expr::Ternary(cond, lhs, rhs) => { + let cond = Box::new(translate_expression(state, cond)); + let lhs = Box::new(translate_expression(state, lhs)); + let rhs = Box::new(translate_expression(state, rhs)); + let ty = promoted_type(&lhs.ty, &rhs.ty); + Expr { + kind: ExprKind::Ternary(cond, lhs, rhs), + ty, + } + } + syntax::Expr::Dot(e, i) => { + let mut e = Box::new(translate_expression(state, e)); + let ty = e.ty.clone(); + let ivec = is_ivec(&ty); + if is_vector(&ty) { + let ty = Type::new(match i.as_str().len() { + 1 => { + if ivec { + TypeKind::Int + } else { + TypeKind::Float + } + } + 2 => { + if ivec { + TypeKind::IVec2 + } else { + TypeKind::Vec2 + } + } + 3 => { + if ivec { + TypeKind::IVec3 + } else { + TypeKind::Vec3 + } + } + 4 => { + if ivec { + TypeKind::IVec4 + } else { + TypeKind::Vec4 + } + } + _ => panic!(), + }); + + let mut sel = SwizzleSelector::parse(i.as_str()); + + if let ExprKind::Variable(ref mut sym) = &mut e.kind { + if state.sym(*sym).name == "gl_FragCoord" { + for c in &sel.components { + state.used_fragcoord |= 1 << c; + } + *sym = state.lookup("gl_FragCoordXY").unwrap(); + for c in &mut sel.components { + if *c >= 2 { + *c -= 2; + *sym = state.lookup("gl_FragCoordZW").unwrap(); + } + } + } + } + + Expr { + kind: ExprKind::SwizzleSelector(e, sel), + ty, + } + } else { + match ty.kind { + TypeKind::Struct(s) => { + let sym = state.sym(s); + let fields = match &sym.decl { + SymDecl::Struct(fields) => fields, + _ => panic!("expected struct"), + }; + let field = fields + .fields + .iter() + .find(|x| &x.name == i) + .expect("missing field"); + Expr { + kind: ExprKind::Dot(e, i.clone()), + ty: field.ty.clone(), + } + } + _ => panic!("expected struct found {:#?} {:#?}", e, ty), + } + } + } + syntax::Expr::Bracket(e, specifier) => { + let e = Box::new(translate_expression(state, e)); + let ty = if is_vector(&e.ty) { + Type::new(TypeKind::Float) + } else if let Some(ty) = index_matrix(&e.ty) { + Type::new(ty) + } else { + let a = match &e.ty.array_sizes { + Some(a) => { + let mut a = *a.clone(); + a.sizes.pop(); + if a.sizes.len() == 0 { + None + } else { + Some(Box::new(a)) + } + } + _ => panic!("{:#?}", e), + }; + Type { + kind: e.ty.kind.clone(), + precision: e.ty.precision.clone(), + array_sizes: a, + } + }; + let indx = match specifier { + ArraySpecifier::Unsized => panic!("need expression"), + ArraySpecifier::ExplicitlySized(e) => translate_expression(state, e), + }; + Expr { + kind: ExprKind::Bracket(e, Box::new(indx)), + ty, + } + } + } +} + +fn translate_switch(state: &mut State, s: &syntax::SwitchStatement) -> SwitchStatement { + let mut cases = Vec::new(); + + let mut case = None; + for stmt in &s.body { + match stmt { + syntax::Statement::Simple(s) => match &**s { + syntax::SimpleStatement::CaseLabel(label) => { + match case.take() { + Some(case) => cases.push(case), + _ => {} + } + case = Some(Case { + label: translate_case(state, &label), + stmts: Vec::new(), + }) + } + _ => match case { + Some(ref mut case) => case.stmts.push(translate_statement(state, stmt)), + _ => panic!("switch must start with case"), + }, + }, + _ => match case { + Some(ref mut case) => case.stmts.push(translate_statement(state, stmt)), + _ => panic!("switch must start with case"), + }, + } + } + match case.take() { + Some(case) => cases.push(case), + _ => {} + } + SwitchStatement { + head: Box::new(translate_expression(state, &s.head)), + cases, + } +} + +fn translate_jump(state: &mut State, s: &syntax::JumpStatement) -> JumpStatement { + match s { + syntax::JumpStatement::Break => JumpStatement::Break, + syntax::JumpStatement::Continue => JumpStatement::Continue, + syntax::JumpStatement::Discard => JumpStatement::Discard, + syntax::JumpStatement::Return(e) => { + JumpStatement::Return(e.as_ref().map(|e| Box::new(translate_expression(state, e)))) + } + } +} + +fn translate_condition(state: &mut State, c: &syntax::Condition) -> Condition { + match c { + syntax::Condition::Expr(e) => Condition::Expr(Box::new(translate_expression(state, e))), + _ => panic!(), + } +} + +fn translate_for_init(state: &mut State, s: &syntax::ForInitStatement) -> ForInitStatement { + match s { + syntax::ForInitStatement::Expression(e) => { + ForInitStatement::Expression(e.as_ref().map(|e| translate_expression(state, e))) + } + syntax::ForInitStatement::Declaration(d) => ForInitStatement::Declaration(Box::new( + translate_declaration(state, d, RunClass::Scalar), + )), + } +} + +fn translate_for_rest(state: &mut State, s: &syntax::ForRestStatement) -> ForRestStatement { + ForRestStatement { + condition: s.condition.as_ref().map(|c| translate_condition(state, c)), + post_expr: s + .post_expr + .as_ref() + .map(|e| Box::new(translate_expression(state, e))), + } +} + +fn translate_iteration(state: &mut State, s: &syntax::IterationStatement) -> IterationStatement { + match s { + syntax::IterationStatement::While(cond, s) => IterationStatement::While( + translate_condition(state, cond), + Box::new(translate_statement(state, s)), + ), + syntax::IterationStatement::For(init, rest, s) => IterationStatement::For( + translate_for_init(state, init), + translate_for_rest(state, rest), + Box::new(translate_statement(state, s)), + ), + syntax::IterationStatement::DoWhile(s, e) => IterationStatement::DoWhile( + Box::new(translate_statement(state, s)), + Box::new(translate_expression(state, e)), + ), + } +} + +fn translate_case(state: &mut State, c: &syntax::CaseLabel) -> CaseLabel { + match c { + syntax::CaseLabel::Def => CaseLabel::Def, + syntax::CaseLabel::Case(e) => CaseLabel::Case(Box::new(translate_expression(state, e))), + } +} + +fn translate_selection_rest( + state: &mut State, + s: &syntax::SelectionRestStatement, +) -> (Box, Option>) { + match s { + syntax::SelectionRestStatement::Statement(s) => { + (Box::new(translate_statement(state, s)), None) + } + syntax::SelectionRestStatement::Else(if_body, rest) => ( + Box::new(translate_statement(state, if_body)), + Some(Box::new(translate_statement(state, rest))), + ), + } +} + +fn translate_selection(state: &mut State, s: &syntax::SelectionStatement) -> SelectionStatement { + let cond = Box::new(translate_expression(state, &s.cond)); + let (body, else_stmt) = translate_selection_rest(state, &s.rest); + SelectionStatement { + cond, + body, + else_stmt, + } +} + +fn translate_simple_statement(state: &mut State, s: &syntax::SimpleStatement) -> SimpleStatement { + match s { + syntax::SimpleStatement::Declaration(d) => { + SimpleStatement::Declaration(translate_declaration(state, d, RunClass::Unknown)) + } + syntax::SimpleStatement::Expression(e) => { + SimpleStatement::Expression(e.as_ref().map(|e| translate_expression(state, e))) + } + syntax::SimpleStatement::Iteration(i) => { + SimpleStatement::Iteration(translate_iteration(state, i)) + } + syntax::SimpleStatement::Selection(s) => { + SimpleStatement::Selection(translate_selection(state, s)) + } + syntax::SimpleStatement::Jump(j) => SimpleStatement::Jump(translate_jump(state, j)), + syntax::SimpleStatement::Switch(s) => SimpleStatement::Switch(translate_switch(state, s)), + syntax::SimpleStatement::CaseLabel(_) => panic!("should be handled by translate_switch"), + } +} + +fn translate_statement(state: &mut State, s: &syntax::Statement) -> Statement { + match s { + syntax::Statement::Compound(s) => { + Statement::Compound(Box::new(translate_compound_statement(state, s))) + } + syntax::Statement::Simple(s) => { + Statement::Simple(Box::new(translate_simple_statement(state, s))) + } + } +} + +fn translate_compound_statement( + state: &mut State, + cs: &syntax::CompoundStatement, +) -> CompoundStatement { + CompoundStatement { + statement_list: cs + .statement_list + .iter() + .map(|x| translate_statement(state, x)) + .collect(), + } +} + +fn translate_function_parameter_declaration( + state: &mut State, + p: &syntax::FunctionParameterDeclaration, + index: usize, +) -> FunctionParameterDeclaration { + match p { + syntax::FunctionParameterDeclaration::Named(qual, p) => { + let mut ty: Type = lift(state, &p.ty); + if let Some(a) = &p.ident.array_spec { + ty.array_sizes = Some(Box::new(lift(state, a))); + } + + ty.precision = get_precision(qual); + + let decl = SymDecl::Local( + StorageClass::None, + ty.clone(), + RunClass::Dependent(1 << index), + ); + let d = FunctionParameterDeclarator { + ty, + name: p.ident.ident.clone(), + sym: state.declare(p.ident.ident.as_str(), decl), + }; + FunctionParameterDeclaration::Named(lift_type_qualifier_for_parameter(state, qual), d) + } + syntax::FunctionParameterDeclaration::Unnamed(qual, p) => { + FunctionParameterDeclaration::Unnamed( + lift_type_qualifier_for_parameter(state, qual), + p.clone(), + ) + } + } +} + +fn translate_prototype( + state: &mut State, + cs: &syntax::FunctionPrototype, +) -> (FunctionPrototype, SymRef) { + let prototype = FunctionPrototype { + ty: lift(state, &cs.ty), + name: cs.name.clone(), + parameters: cs + .parameters + .iter() + .enumerate() + .map(|(i, x)| translate_function_parameter_declaration(state, x, i)) + .collect(), + }; + let sym = if let Some(sym) = state.lookup(prototype.name.as_str()) { + match &state.sym(sym).decl { + SymDecl::UserFunction(..) => {} + _ => panic!( + "prototype conflicts with existing symbol: {}", + prototype.name.as_str() + ), + } + sym + } else { + let pfd = Rc::new(FunctionDefinition { + prototype: prototype.clone(), + body: CompoundStatement::new(), + globals: Vec::new(), + texel_fetches: HashMap::new(), + }); + state.declare( + prototype.name.as_str(), + SymDecl::UserFunction(pfd, RunClass::Unknown), + ) + }; + (prototype, sym) +} + +fn translate_function_prototype( + state: &mut State, + prototype: &syntax::FunctionPrototype, +) -> FunctionPrototype { + let (prototype, _) = translate_prototype(state, prototype); + prototype +} + +fn translate_function_definition( + state: &mut State, + sfd: &syntax::FunctionDefinition, +) -> Rc { + let (prototype, sym) = translate_prototype(state, &sfd.prototype); + + state.push_scope(prototype.name.as_str().into()); + state.in_function = Some(sym); + state.modified_globals.get_mut().clear(); + state.texel_fetches.clear(); + let body = translate_compound_statement(state, &sfd.statement); + let mut globals = Vec::new(); + mem::swap(&mut globals, state.modified_globals.get_mut()); + let mut texel_fetches = HashMap::new(); + mem::swap(&mut texel_fetches, &mut state.texel_fetches); + state.in_function = None; + state.pop_scope(); + + let fd = Rc::new(FunctionDefinition { + prototype, + body, + globals, + texel_fetches, + }); + state.sym_mut(sym).decl = SymDecl::UserFunction(fd.clone(), RunClass::Unknown); + fd +} + +fn translate_external_declaration( + state: &mut State, + ed: &syntax::ExternalDeclaration, +) -> ExternalDeclaration { + match ed { + syntax::ExternalDeclaration::Declaration(d) => { + ExternalDeclaration::Declaration(translate_declaration(state, d, RunClass::Unknown)) + } + syntax::ExternalDeclaration::FunctionDefinition(fd) => { + ExternalDeclaration::FunctionDefinition(translate_function_definition(state, fd)) + } + syntax::ExternalDeclaration::Preprocessor(p) => { + ExternalDeclaration::Preprocessor(p.clone()) + } + } +} + +fn declare_function( + state: &mut State, + name: &str, + cxx_name: Option<&'static str>, + ret: Type, + params: Vec, +) { + let sig = FunctionSignature { ret, params }; + match state.lookup_sym_mut(name) { + Some(Symbol { + decl: SymDecl::NativeFunction(f, ..), + .. + }) => f.signatures.push(sig), + None => { + state.declare( + name, + SymDecl::NativeFunction( + FunctionType { + signatures: NonEmpty::new(sig), + }, + cxx_name, + ), + ); + } + _ => panic!("overloaded function name {}", name), + } + //state.declare(name, Type::Function(FunctionType{ v})) +} + +pub fn ast_to_hir(state: &mut State, tu: &syntax::TranslationUnit) -> TranslationUnit { + // global scope + state.push_scope("global".into()); + use TypeKind::*; + declare_function( + state, + "vec2", + Some("make_vec2"), + Type::new(Vec2), + vec![Type::new(Float)], + ); + declare_function( + state, + "vec2", + Some("make_vec2"), + Type::new(Vec2), + vec![Type::new(IVec2)], + ); + declare_function( + state, + "vec2", + Some("make_vec2"), + Type::new(Vec2), + vec![Type::new(IVec3)], + ); + declare_function( + state, + "vec3", + Some("make_vec3"), + Type::new(Vec3), + vec![Type::new(Float), Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "vec3", + Some("make_vec3"), + Type::new(Vec3), + vec![Type::new(Float)], + ); + declare_function( + state, + "vec3", + Some("make_vec3"), + Type::new(Vec3), + vec![Type::new(Vec2), Type::new(Float)], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![Type::new(Vec3), Type::new(Float)], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![ + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + ], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![Type::new(Vec2), Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![Type::new(Float), Type::new(Float), Type::new(Vec2)], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![Type::new(Vec4)], + ); + + declare_function( + state, + "bvec2", + Some("make_bvec2"), + Type::new(BVec2), + vec![Type::new(UInt)], + ); + declare_function( + state, + "bvec4", + Some("make_bvec4"), + Type::new(BVec4), + vec![Type::new(BVec2), Type::new(BVec2)], + ); + + declare_function( + state, + "int", + Some("make_int"), + Type::new(Int), + vec![Type::new(Float)], + ); + declare_function( + state, + "float", + Some("make_float"), + Type::new(Float), + vec![Type::new(Float)], + ); + declare_function( + state, + "float", + Some("make_float"), + Type::new(Float), + vec![Type::new(Bool)], + ); + declare_function( + state, + "int", + Some("make_int"), + Type::new(Int), + vec![Type::new(UInt)], + ); + declare_function( + state, + "uint", + Some("make_uint"), + Type::new(UInt), + vec![Type::new(Float)], + ); + declare_function( + state, + "uint", + Some("make_uint"), + Type::new(UInt), + vec![Type::new(Int)], + ); + declare_function( + state, + "ivec2", + Some("make_ivec2"), + Type::new(IVec2), + vec![Type::new(UInt), Type::new(UInt)], + ); + declare_function( + state, + "ivec2", + Some("make_ivec2"), + Type::new(IVec2), + vec![Type::new(Int), Type::new(Int)], + ); + declare_function( + state, + "ivec2", + Some("make_ivec2"), + Type::new(IVec2), + vec![Type::new(Vec2)], + ); + declare_function( + state, + "ivec3", + Some("make_ivec3"), + Type::new(IVec3), + vec![Type::new(IVec2), Type::new(Int)], + ); + declare_function( + state, + "ivec4", + Some("make_ivec4"), + Type::new(IVec4), + vec![ + Type::new(Int), + Type::new(Int), + Type::new(Int), + Type::new(Int), + ], + ); + declare_function( + state, + "ivec4", + Some("make_ivec4"), + Type::new(IVec4), + vec![Type::new(IVec2), Type::new(Int), Type::new(Int)], + ); + + declare_function( + state, + "mat2", + Some("make_mat2"), + Type::new(Mat2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "mat2", + Some("make_mat2"), + Type::new(Mat2), + vec![Type::new(Float)], + ); + declare_function( + state, + "mat2", + Some("make_mat2"), + Type::new(Mat2), + vec![Type::new(Mat4)], + ); + declare_function( + state, + "mat3", + Some("make_mat3"), + Type::new(Mat3), + vec![Type::new(Vec3), Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "mat3", + Some("make_mat3"), + Type::new(Mat3), + vec![Type::new(Mat4)], + ); + declare_function( + state, + "mat3", + Some("make_mat3"), + Type::new(Mat3), + vec![ + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + ], + ); + declare_function( + state, + "mat4", + Some("make_mat4"), + Type::new(Mat4), + vec![ + Type::new(Vec4), + Type::new(Vec4), + Type::new(Vec4), + Type::new(Vec4), + ], + ); + declare_function( + state, + "mat4", + Some("make_mat4"), + Type::new(Mat4), + vec![ + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + ], + ); + declare_function(state, "abs", None, Type::new(Vec2), vec![Type::new(Vec2)]); + declare_function(state, "abs", None, Type::new(Vec3), vec![Type::new(Vec3)]); + declare_function(state, "abs", None, Type::new(Float), vec![Type::new(Float)]); + declare_function( + state, + "dot", + None, + Type::new(Float), + vec![Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "dot", + None, + Type::new(Float), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "min", + None, + Type::new(Float), + vec![Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "min", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "min", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3)], + ); + + declare_function( + state, + "max", + None, + Type::new(Float), + vec![Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "max", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "max", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Float)], + ); + declare_function( + state, + "max", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3)], + ); + + declare_function( + state, + "mix", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2), Type::new(BVec2)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2), Type::new(Float)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec4), + vec![Type::new(Vec4), Type::new(Vec4), Type::new(Vec4)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec4), + vec![Type::new(Vec4), Type::new(Vec4), Type::new(Float)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3), Type::new(Float)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3), Type::new(BVec3)], + ); + declare_function( + state, + "mix", + None, + Type::new(Float), + vec![Type::new(Float), Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec4), + vec![Type::new(Vec4), Type::new(Vec4), Type::new(BVec4)], + ); + declare_function( + state, + "step", + None, + Type::new(Float), + vec![Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "step", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "step", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "notEqual", + None, + Type::new(BVec4), + vec![Type::new(IVec4), Type::new(IVec4)], + ); + + declare_function( + state, + "fwidth", + None, + Type::new(Vec2), + vec![Type::new(Vec2)], + ); + declare_function(state, "cos", None, Type::new(Float), vec![Type::new(Float)]); + declare_function(state, "sin", None, Type::new(Float), vec![Type::new(Float)]); + declare_function( + state, + "clamp", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "clamp", + None, + Type::new(Double), + vec![Type::new(Double), Type::new(Double), Type::new(Double)], + ); + declare_function( + state, + "clamp", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "clamp", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "clamp", + None, + Type::new(Vec4), + vec![Type::new(Vec4), Type::new(Vec4), Type::new(Vec4)], + ); + declare_function( + state, + "length", + None, + Type::new(Float), + vec![Type::new(Vec2)], + ); + declare_function(state, "pow", None, Type::new(Vec3), vec![Type::new(Vec3)]); + declare_function(state, "pow", None, Type::new(Float), vec![Type::new(Float)]); + declare_function(state, "exp", None, Type::new(Float), vec![Type::new(Float)]); + declare_function( + state, + "inversesqrt", + None, + Type::new(Float), + vec![Type::new(Float)], + ); + declare_function( + state, + "sqrt", + None, + Type::new(Float), + vec![Type::new(Float)], + ); + declare_function( + state, + "distance", + None, + Type::new(Float), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + + declare_function( + state, + "lessThanEqual", + None, + Type::new(BVec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "lessThanEqual", + None, + Type::new(BVec3), + vec![Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "lessThanEqual", + None, + Type::new(BVec4), + vec![Type::new(Vec4), Type::new(Vec4)], + ); + declare_function( + state, + "lessThan", + None, + Type::new(BVec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "greaterThan", + None, + Type::new(BVec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "greaterThanEqual", + None, + Type::new(BVec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "greaterThanEqual", + None, + Type::new(BVec2), + vec![Type::new(Vec4), Type::new(Vec4)], + ); + declare_function(state, "any", None, Type::new(Bool), vec![Type::new(BVec2)]); + declare_function(state, "all", None, Type::new(Bool), vec![Type::new(BVec2)]); + declare_function(state, "all", None, Type::new(Bool), vec![Type::new(BVec4)]); + + declare_function( + state, + "if_then_else", + None, + Type::new(Vec3), + vec![Type::new(BVec3), Type::new(Vec3), Type::new(Vec3)], + ); + declare_function(state, "floor", None, Type::new(Vec4), vec![Type::new(Vec4)]); + declare_function(state, "floor", None, Type::new(Vec2), vec![Type::new(Vec2)]); + declare_function( + state, + "floor", + None, + Type::new(Double), + vec![Type::new(Double)], + ); + declare_function( + state, + "ceil", + None, + Type::new(Double), + vec![Type::new(Double)], + ); + declare_function( + state, + "round", + None, + Type::new(Double), + vec![Type::new(Double)], + ); + declare_function( + state, + "fract", + None, + Type::new(Float), + vec![Type::new(Float)], + ); + declare_function(state, "mod", None, Type::new(Vec2), vec![Type::new(Vec2)]); + declare_function(state, "mod", None, Type::new(Float), vec![Type::new(Float)]); + + declare_function( + state, + "texelFetch", + None, + Type::new(Vec4), + vec![Type::new(Sampler2D), Type::new(IVec2), Type::new(Int)], + ); + declare_function( + state, + "texelFetch", + None, + Type::new(Vec4), + vec![Type::new(Sampler2DArray), Type::new(IVec3), Type::new(Int)], + ); + declare_function( + state, + "texelFetch", + None, + Type::new(IVec4), + vec![Type::new(ISampler2D), Type::new(IVec2), Type::new(Int)], + ); + declare_function( + state, + "texelFetchOffset", + None, + Type::new(Vec4), + vec![ + Type::new(Sampler2D), + Type::new(IVec2), + Type::new(Int), + Type::new(IVec2), + ], + ); + declare_function( + state, + "texelFetchOffset", + None, + Type::new(IVec4), + vec![ + Type::new(ISampler2D), + Type::new(IVec2), + Type::new(Int), + Type::new(IVec2), + ], + ); + declare_function( + state, + "texture", + None, + Type::new(Vec4), + vec![Type::new(Sampler2D), Type::new(Vec3)], + ); + declare_function( + state, + "texture", + None, + Type::new(Vec4), + vec![Type::new(Sampler2D), Type::new(Vec2)], + ); + declare_function( + state, + "texture", + None, + Type::new(Vec4), + vec![Type::new(Sampler2DArray), Type::new(Vec3)], + ); + declare_function( + state, + "textureLod", + None, + Type::new(Vec4), + vec![Type::new(Sampler2DArray), Type::new(Vec3), Type::new(Float)], + ); + declare_function( + state, + "textureSize", + None, + Type::new(IVec3), + vec![Type::new(Sampler2DArray), Type::new(Int)], + ); + declare_function( + state, + "textureSize", + None, + Type::new(IVec2), + vec![Type::new(Sampler2D), Type::new(Int)], + ); + + declare_function( + state, + "inverse", + None, + Type::new(Mat2), + vec![Type::new(Mat2)], + ); + declare_function( + state, + "transpose", + None, + Type::new(Mat3), + vec![Type::new(Mat3)], + ); + declare_function( + state, + "normalize", + None, + Type::new(Vec2), + vec![Type::new(Vec2)], + ); + state.declare( + "gl_FragCoord", + SymDecl::Global(StorageClass::In, None, Type::new(Vec4), RunClass::Vector), + ); + state.declare( + "gl_FragCoordXY", + SymDecl::Global(StorageClass::In, None, Type::new(Vec2), RunClass::Vector), + ); + state.declare( + "gl_FragCoordZW", + SymDecl::Global(StorageClass::In, None, Type::new(Vec2), RunClass::Scalar), + ); + state.declare( + "gl_FragColor", + SymDecl::Global(StorageClass::Out, None, Type::new(Vec4), RunClass::Vector), + ); + state.declare( + "gl_Position", + SymDecl::Global(StorageClass::Out, None, Type::new(Vec4), RunClass::Vector), + ); + + TranslationUnit(tu.0.map(state, translate_external_declaration)) +} + +fn infer_expr_inner(state: &mut State, expr: &Expr, assign: &mut SymRef) -> RunClass { + match expr.kind { + ExprKind::Variable(ref i) => { + *assign = *i; + match &state.sym(*i).decl { + SymDecl::Local(_, _, ref run_class) => *run_class, + SymDecl::Global(_, _, _, ref run_class) => *run_class, + _ => panic!(), + } + } + ExprKind::IntConst(_) + | ExprKind::UIntConst(_) + | ExprKind::BoolConst(_) + | ExprKind::FloatConst(_) + | ExprKind::DoubleConst(_) => RunClass::Scalar, + ExprKind::Unary(_, ref e) => infer_expr(state, e), + ExprKind::Binary(_, ref l, ref r) => infer_expr(state, l).merge(infer_expr(state, r)), + ExprKind::Ternary(ref c, ref s, ref e) => infer_expr(state, c) + .merge(infer_expr(state, s)) + .merge(infer_expr(state, e)), + ExprKind::Assignment(ref v, _, ref e) => { + let mut sym = SymRef(!0); + let run_class = infer_expr_inner(state, v, &mut sym).merge(infer_expr(state, e)); + assert!(sym != SymRef(!0)); + state.merge_run_class(sym, run_class) + } + ExprKind::Bracket(ref e, ref indx) => { + infer_expr_inner(state, e, assign).merge(infer_expr(state, indx)) + } + ExprKind::FunCall(ref fun, ref args) => { + let arg_classes: Vec<(RunClass, SymRef)> = args + .iter() + .map(|e| { + let mut assign = SymRef(!0); + let run_class = infer_expr_inner(state, e, &mut assign); + (run_class, assign) + }) + .collect(); + let run_class = if args.is_empty() { + RunClass::Scalar + } else { + arg_classes + .iter() + .fold(RunClass::Unknown, |x, &(y, _)| x.merge(y)) + }; + match fun { + FunIdentifier::Identifier(ref sym) => match &state.sym(*sym).decl { + SymDecl::NativeFunction(..) => run_class, + SymDecl::UserFunction(ref fd, ref run_class) => { + for (&(mut arg_class, assign), param) in + arg_classes.iter().zip(fd.prototype.parameters.iter()) + { + if let FunctionParameterDeclaration::Named(Some(qual), p) = param { + match qual { + ParameterQualifier::InOut | ParameterQualifier::Out => { + if let SymDecl::Local(_, _, param_class) = + &state.sym(p.sym).decl + { + match param_class { + RunClass::Unknown | RunClass::Vector => { + arg_class = RunClass::Vector; + } + RunClass::Dependent(mask) => { + for i in 0 .. 31 { + if (mask & (1 << i)) != 0 { + arg_class = + arg_class.merge(arg_classes[i].0); + } + } + } + RunClass::Scalar => {} + } + } + assert!(assign != SymRef(!0)); + state.merge_run_class(assign, arg_class); + } + _ => {} + } + } + } + if fd.prototype.ty.kind == TypeKind::Void { + RunClass::Scalar + } else { + match *run_class { + RunClass::Unknown | RunClass::Vector => RunClass::Vector, + RunClass::Dependent(mask) => { + let mut ret_class = RunClass::Unknown; + for i in 0 .. 31 { + if (mask & (1 << i)) != 0 { + ret_class = ret_class.merge(arg_classes[i].0); + } + } + ret_class + } + RunClass::Scalar => RunClass::Scalar, + } + } + } + SymDecl::Struct(..) => run_class, + _ => panic!(), + }, + FunIdentifier::Constructor(..) => run_class, + } + } + ExprKind::Dot(ref e, _) => infer_expr_inner(state, e, assign), + ExprKind::SwizzleSelector(ref e, _) => infer_expr_inner(state, e, assign), + ExprKind::PostInc(ref e) => infer_expr_inner(state, e, assign), + ExprKind::PostDec(ref e) => infer_expr_inner(state, e, assign), + ExprKind::Comma(ref a, ref b) => { + infer_expr(state, a); + infer_expr(state, b) + } + ExprKind::Cond(_, ref e) => infer_expr(state, e), + ExprKind::CondMask => RunClass::Vector, + } +} + +fn infer_expr(state: &mut State, expr: &Expr) -> RunClass { + infer_expr_inner(state, expr, &mut SymRef(!0)) +} + +fn infer_condition(state: &mut State, c: &Condition) { + match *c { + Condition::Expr(ref e) => { + infer_expr(state, e); + } + } +} + +fn infer_iteration_statement(state: &mut State, ist: &IterationStatement) { + let changed = state.run_class_changed.replace(true); + match *ist { + IterationStatement::While(ref cond, ref body) => { + while state.run_class_changed.replace(false) { + infer_condition(state, cond); + infer_statement(state, body); + } + } + IterationStatement::DoWhile(ref body, ref cond) => { + while state.run_class_changed.replace(false) { + infer_statement(state, body); + infer_expr(state, cond); + } + } + IterationStatement::For(ref init, ref rest, ref body) => { + match *init { + ForInitStatement::Expression(ref expr) => { + if let Some(ref e) = *expr { + infer_expr(state, e); + } + } + ForInitStatement::Declaration(ref d) => { + infer_declaration(state, d); + } + } + while state.run_class_changed.replace(false) { + if let Some(ref cond) = rest.condition { + infer_condition(state, cond); + } + if let Some(ref e) = rest.post_expr { + infer_expr(state, e); + } + infer_statement(state, body); + } + } + } + state.run_class_changed.set(changed); +} + +fn infer_selection_statement(state: &mut State, sst: &SelectionStatement) { + let mut branch_run_class = state.branch_run_class.merge(infer_expr(state, &sst.cond)); + mem::swap(&mut state.branch_run_class, &mut branch_run_class); + let branch_declaration = state.branch_declaration; + state.branch_declaration = state.last_declaration; + infer_statement(state, &sst.body); + if let Some(ref else_st) = sst.else_stmt { + infer_statement(state, else_st); + } + state.branch_run_class = branch_run_class; + state.branch_declaration = branch_declaration; +} + +fn infer_expression_statement(state: &mut State, est: &ExprStatement) { + if let Some(ref e) = *est { + infer_expr(state, e); + } +} + +fn infer_switch_statement(state: &mut State, sst: &SwitchStatement) { + let mut branch_run_class = state.branch_run_class.merge(infer_expr(state, &sst.head)); + mem::swap(&mut state.branch_run_class, &mut branch_run_class); + let branch_declaration = state.branch_declaration; + state.branch_declaration = state.last_declaration; + for case in &sst.cases { + for st in &case.stmts { + infer_statement(state, st); + } + } + state.branch_run_class = branch_run_class; + state.branch_declaration = branch_declaration; +} + +fn infer_jump_statement(state: &mut State, j: &JumpStatement) { + match *j { + JumpStatement::Continue => {} + JumpStatement::Break => {} + JumpStatement::Discard => {} + JumpStatement::Return(ref e) => { + if let Some(e) = e { + let run_class = infer_expr(state, e); + state.return_run_class(run_class); + } + } + } +} + +fn infer_initializer(state: &mut State, i: &Initializer) -> RunClass { + match *i { + Initializer::Simple(ref e) => infer_expr(state, e), + Initializer::List(ref list) => { + let mut run_class = RunClass::Unknown; + for ini in list.0.iter() { + run_class = run_class.merge(infer_initializer(state, ini)); + } + run_class + } + } +} + +fn infer_declaration(state: &mut State, d: &Declaration) { + match *d { + Declaration::FunctionPrototype(..) => {} + Declaration::InitDeclaratorList(ref list) => { + state.last_declaration = list.head.name; + + let mut run_class = RunClass::Unknown; + for decl in &list.tail { + if let Some(ref initializer) = decl.initializer { + run_class = run_class.merge(infer_initializer(state, initializer)); + } + } + if let Some(ref initializer) = list.head.initializer { + run_class = run_class.merge(infer_initializer(state, initializer)); + state.merge_run_class(list.head.name, run_class); + } + } + Declaration::Precision(..) => {} + Declaration::Block(..) => {} + Declaration::Global(..) => {} + Declaration::StructDefinition(..) => {} + } +} + +fn infer_simple_statement(state: &mut State, sst: &SimpleStatement) { + match *sst { + SimpleStatement::Declaration(ref d) => infer_declaration(state, d), + SimpleStatement::Expression(ref e) => infer_expression_statement(state, e), + SimpleStatement::Selection(ref s) => infer_selection_statement(state, s), + SimpleStatement::Switch(ref s) => infer_switch_statement(state, s), + SimpleStatement::Iteration(ref i) => infer_iteration_statement(state, i), + SimpleStatement::Jump(ref j) => infer_jump_statement(state, j), + } +} + +fn infer_compound_statement(state: &mut State, cst: &CompoundStatement) { + for st in &cst.statement_list { + infer_statement(state, st); + } +} + +fn infer_statement(state: &mut State, st: &Statement) { + match *st { + Statement::Compound(ref cst) => infer_compound_statement(state, cst), + Statement::Simple(ref sst) => infer_simple_statement(state, sst), + } +} + +fn infer_function_definition(state: &mut State, fd: &FunctionDefinition) { + state.in_function = Some(state.lookup(fd.prototype.name.as_str()).unwrap()); + + state.run_class_changed.set(true); + while state.run_class_changed.replace(false) { + for st in &fd.body.statement_list { + infer_statement(state, st); + } + } + + state.in_function = None; +} + +fn infer_external_declaration(state: &mut State, ed: &ExternalDeclaration) { + match *ed { + ExternalDeclaration::Preprocessor(_) => {} + ExternalDeclaration::FunctionDefinition(ref fd) => infer_function_definition(state, fd), + ExternalDeclaration::Declaration(_) => {} + } +} + +pub fn infer_run_class(state: &mut State, tu: &TranslationUnit) { + for ed in &(tu.0).0 { + infer_external_declaration(state, ed); + } +} diff --git a/glsl-to-cxx/src/lib.rs b/glsl-to-cxx/src/lib.rs new file mode 100644 index 0000000000..7ce461abf0 --- /dev/null +++ b/glsl-to-cxx/src/lib.rs @@ -0,0 +1,3689 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate glsl; + +mod hir; + +use glsl::parser::Parse; +use glsl::syntax; +use glsl::syntax::{TranslationUnit, UnaryOp}; +use hir::{Statement, SwizzleSelector, Type}; +use std::cell::{Cell, RefCell}; +use std::collections::{BTreeMap, HashMap}; +use std::io::Read; +use std::mem; + +#[derive(PartialEq, Eq)] +enum ShaderKind { + Fragment, + Vertex, +} + +type UniformIndices = BTreeMap; + +fn build_uniform_indices(indices: &mut UniformIndices, state: &hir::State) { + for u in state.used_globals.borrow().iter() { + let sym = state.sym(*u); + match &sym.decl { + hir::SymDecl::Global(storage, _, ty, _) => match storage { + hir::StorageClass::Uniform | hir::StorageClass::Sampler(..) => { + let next_index = indices.len() as i32 + 1; + indices.entry(sym.name.clone()).or_insert(( + next_index, + ty.kind.clone(), + *storage, + )); + } + _ => {} + }, + _ => {} + } + } +} + +pub fn translate(args: &mut dyn Iterator) -> String { + let _cmd_name = args.next(); + let vertex_file = args.next().unwrap(); + + let vs_name = std::path::Path::new(&vertex_file) + .file_stem() + .unwrap() + .to_string_lossy() + .to_string(); + + let frag_file = args.next().unwrap(); + + let fs_name = std::path::Path::new(&frag_file) + .file_stem() + .unwrap() + .to_string_lossy() + .to_string(); + + let frag_include = args.next(); + + let (vs_state, vs_hir, vs_is_frag) = parse_shader(vertex_file); + let (fs_state, fs_hir, fs_is_frag) = parse_shader(frag_file); + + // we use a BTree so that iteration is stable + let mut uniform_indices = BTreeMap::new(); + build_uniform_indices(&mut uniform_indices, &vs_state); + build_uniform_indices(&mut uniform_indices, &fs_state); + + assert_eq!(fs_name, vs_name); + + let mut result = translate_shader( + vs_name, + vs_state, + vs_hir, + vs_is_frag, + &uniform_indices, + None, + ); + result += "\n"; + result += &translate_shader( + fs_name, + fs_state, + fs_hir, + fs_is_frag, + &uniform_indices, + frag_include, + ); + result +} + +fn parse_shader(file: String) -> (hir::State, hir::TranslationUnit, bool) { + let mut contents = String::new(); + let is_frag = file.contains("frag"); + std::fs::File::open(&file) + .unwrap() + .read_to_string(&mut contents) + .unwrap(); + let r = TranslationUnit::parse(contents); + + //println!("{:#?}", r); + let mut ast_glsl = String::new(); + let r = r.unwrap(); + glsl::transpiler::glsl::show_translation_unit(&mut ast_glsl, &r); + //let mut fast = std::fs::File::create("ast").unwrap(); + //fast.write(ast_glsl.as_bytes()); + + let mut state = hir::State::new(); + let hir = hir::ast_to_hir(&mut state, &r); + (state, hir, is_frag) +} + +fn translate_shader( + name: String, + mut state: hir::State, + hir: hir::TranslationUnit, + is_frag: bool, + uniform_indices: &UniformIndices, + include_file: Option, +) -> String { + //println!("{:#?}", state); + + hir::infer_run_class(&mut state, &hir); + + let mut uniforms = Vec::new(); + let mut inputs = Vec::new(); + let mut outputs = Vec::new(); + + for i in &hir { + match i { + hir::ExternalDeclaration::Declaration(hir::Declaration::InitDeclaratorList(ref d)) => { + match &state.sym(d.head.name).decl { + hir::SymDecl::Global(storage, ..) + if state.used_globals.borrow().contains(&d.head.name) => + { + match storage { + hir::StorageClass::Uniform | hir::StorageClass::Sampler(..) => { + uniforms.push(d.head.name); + } + hir::StorageClass::In => { + inputs.push(d.head.name); + } + hir::StorageClass::Out | hir::StorageClass::FragColor(_) => { + outputs.push(d.head.name); + } + _ => {} + } + } + _ => {} + } + } + _ => {} + } + } + + //println!("{:#?}", hir); + + let mut state = OutputState { + hir: state, + output: String::new(), + buffer: RefCell::new(String::new()), + indent: 0, + should_indent: false, + output_cxx: false, + mask: None, + cond_index: 0, + return_type: None, + return_declared: false, + return_vector: false, + is_scalar: Cell::new(false), + is_lval: Cell::new(false), + name: name.clone(), + kind: if is_frag { + ShaderKind::Fragment + } else { + ShaderKind::Vertex + }, + functions: HashMap::new(), + deps: RefCell::new(Vec::new()), + vector_mask: 0, + uses_discard: false, + has_draw_span_rgba8: false, + has_draw_span_r8: false, + used_globals: RefCell::new(Vec::new()), + texel_fetches: RefCell::new(Vec::new()), + }; + + show_translation_unit(&mut state, &hir); + let _output_glsl = state.finish_output(); + + state.should_indent = true; + state.output_cxx = true; + + if state.output_cxx { + let part_name = name.to_owned() + + match state.kind { + ShaderKind::Vertex => "_vert", + ShaderKind::Fragment => "_frag", + }; + let shader_impl = match state.kind { + ShaderKind::Vertex => "VertexShaderImpl", + ShaderKind::Fragment => "FragmentShaderImpl", + }; + + if state.kind == ShaderKind::Vertex { + write!(state, "struct {}_program : ProgramImpl {{\n", name); + write_get_uniform_index(&mut state, uniform_indices); + write_program_samplers(&mut state, uniform_indices); + write_bind_attrib_location(&mut state, &inputs); + write!(state, "VertexShaderImpl* get_vertex_shader() override;\n"); + write!( + state, + "FragmentShaderImpl* get_fragment_shader() override;\n" + ); + write!( + state, + "static ProgramImpl* loader() {{ return new {}_program; }}\n", + name + ); + write!(state, "}};\n\n"); + } + + write!(state, "struct {} : {} {{\n", part_name, shader_impl); + write!(state, "typedef {} Self;\n", part_name); + + show_translation_unit(&mut state, &hir); + + if let Some(include_file) = include_file { + write_include_file(&mut state, include_file); + } + + write_set_uniform_1i(&mut state, &uniforms, uniform_indices); + write_set_uniform_4fv(&mut state, &uniforms, uniform_indices); + write_set_uniform_matrix4fv(&mut state, &uniforms, uniform_indices); + + let pruned_inputs: Vec<_> = inputs + .iter() + .filter(|i| state.used_globals.borrow().contains(i)) + .cloned() + .collect(); + let pruned_uniforms: Vec<_> = uniforms + .iter() + .filter(|u| state.used_globals.borrow().contains(u)) + .cloned() + .collect(); + + if state.kind == ShaderKind::Vertex { + write_load_attribs(&mut state, &pruned_inputs); + write_store_outputs(&mut state, &outputs); + } else { + write_read_inputs(&mut state, &pruned_inputs); + } + write_bind_textures(&mut state, &pruned_uniforms); + + write_abi(&mut state); + write!(state, "}};\n\n"); + + if state.kind == ShaderKind::Vertex { + write!( + state, + "VertexShaderImpl* {}_program::get_vertex_shader() {{ return new {}; }}\n", + name, part_name + ); + } else { + write!( + state, + "FragmentShaderImpl* {}_program::get_fragment_shader() {{ return new {}; }}\n", + name, part_name + ); + } + + define_global_consts(&mut state, &hir, &part_name); + } else { + show_translation_unit(&mut state, &hir); + } + let output_cxx = state.finish_output(); + + //let mut hir = std::fs::File::create("hir").unwrap(); + //hir.write(output_glsl.as_bytes()); + + output_cxx +} + +fn write_get_uniform_index(state: &mut OutputState, uniform_indices: &UniformIndices) { + write!( + state, + "int get_uniform(const char *name) const override {{\n" + ); + for (uniform_name, (index, _, _)) in uniform_indices.iter() { + write!( + state, + " if (strcmp(\"{}\", name) == 0) {{ return {}; }}\n", + uniform_name, index + ); + } + write!(state, " return -1;\n"); + write!(state, "}}\n"); +} + +fn float4_compatible(ty: hir::TypeKind) -> bool { + match ty { + _ => false, + } +} + +fn matrix4_compatible(ty: hir::TypeKind) -> bool { + match ty { + hir::TypeKind::Mat4 => true, + _ => false, + } +} + +fn write_program_samplers(state: &mut OutputState, uniform_indices: &UniformIndices) { + write!(state, "struct Samplers {{\n"); + for (name, (_, tk, storage)) in uniform_indices.iter() { + match tk { + hir::TypeKind::Sampler2D + | hir::TypeKind::ISampler2D + | hir::TypeKind::Sampler2DArray => { + write!(state, " "); + show_type_kind(state, &tk); + let suffix = if let hir::StorageClass::Sampler(format) = storage { + format.type_suffix() + } else { + None + }; + write!(state, "{}_impl {}_impl;\n", suffix.unwrap_or(""), name); + write!(state, " int {}_slot;\n", name); + } + _ => {} + } + } + write!(state, "}} samplers;\n"); + + write!( + state, + "bool set_sampler(int index, int value) override {{\n" + ); + write!(state, " switch (index) {{\n"); + for (name, (index, tk, _)) in uniform_indices.iter() { + match tk { + hir::TypeKind::Sampler2D + | hir::TypeKind::ISampler2D + | hir::TypeKind::Sampler2DArray => { + write!(state, " case {}:\n", index); + write!(state, " samplers.{}_slot = value;\n", name); + write!(state, " return true;\n"); + } + _ => {} + } + } + write!(state, " }}\n"); + write!(state, " return false;\n"); + write!(state, "}}\n"); +} + +fn write_bind_textures(state: &mut OutputState, uniforms: &[hir::SymRef]) { + write!( + state, + "static void bind_textures(Self *self, {}_program *prog) {{\n", + state.name + ); + for i in uniforms { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(hir::StorageClass::Sampler(_format), _, ty, _) => { + let name = sym.name.as_str(); + match ty.kind { + hir::TypeKind::Sampler2D => write!(state, + " self->{0} = lookup_sampler(&prog->samplers.{0}_impl, prog->samplers.{0}_slot);\n", + name), + hir::TypeKind::ISampler2D => write!(state, + " self->{0} = lookup_isampler(&prog->samplers.{0}_impl, prog->samplers.{0}_slot);\n", + name), + hir::TypeKind::Sampler2DArray => write!(state, + " self->{0} = lookup_sampler_array(&prog->samplers.{0}_impl, prog->samplers.{0}_slot);\n", + name), + _ => {} + }; + } + hir::SymDecl::Global(..) => {} + _ => panic!(), + } + } + write!(state, "}}\n"); +} + +fn write_set_uniform_1i( + state: &mut OutputState, + uniforms: &[hir::SymRef], + uniform_indices: &UniformIndices, +) { + write!( + state, + "static void set_uniform_1i(Self *self, int index, int value) {{\n" + ); + write!(state, " switch (index) {{\n"); + for i in uniforms { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, ty, _) => { + let name = sym.name.as_str(); + let (index, _, _) = uniform_indices.get(name).unwrap(); + write!(state, " case {}:\n", index); + match ty.kind { + hir::TypeKind::Int => write!( + state, + " self->{} = {}(value);\n", + name, + scalar_type_name(state, ty) + ), + _ => write!(state, " assert(0); // {}\n", name), + }; + write!(state, " break;\n"); + } + _ => panic!(), + } + } + write!(state, " }}\n"); + write!(state, "}}\n"); +} + +fn write_set_uniform_4fv( + state: &mut OutputState, + uniforms: &[hir::SymRef], + uniform_indices: &UniformIndices, +) { + write!( + state, + "static void set_uniform_4fv(Self *self, int index, const float *value) {{\n" + ); + write!(state, " switch (index) {{\n"); + for i in uniforms { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, ty, _) => { + let name = sym.name.as_str(); + let (index, _, _) = uniform_indices.get(name).unwrap(); + write!(state, " case {}:\n", index); + if float4_compatible(ty.kind.clone()) { + write!( + state, + " self->{} = {}(value);\n", + name, + scalar_type_name(state, ty) + ); + } else { + write!(state, " assert(0); // {}\n", name); + } + write!(state, " break;\n"); + } + _ => panic!(), + } + } + write!(state, " }}\n"); + write!(state, "}}\n"); +} + +fn write_set_uniform_matrix4fv( + state: &mut OutputState, + uniforms: &[hir::SymRef], + uniform_indices: &UniformIndices, +) { + write!( + state, + "static void set_uniform_matrix4fv(Self *self, int index, const float *value) {{\n" + ); + write!(state, " switch (index) {{\n"); + for i in uniforms { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, ty, _) => { + let name = sym.name.as_str(); + let (index, _, _) = uniform_indices.get(name).unwrap(); + + write!(state, " case {}:\n", index); + if matrix4_compatible(ty.kind.clone()) { + write!( + state, + " self->{} = mat4_scalar::load_from_ptr(value);\n", + name + ); + } else { + write!(state, " assert(0); // {}\n", name); + } + write!(state, " break;\n"); + } + _ => panic!(), + } + } + write!(state, " }}\n"); + write!(state, "}}\n"); +} + +fn write_bind_attrib_location(state: &mut OutputState, attribs: &[hir::SymRef]) { + write!(state, "struct AttribLocations {{\n"); + for i in attribs { + let sym = state.hir.sym(*i); + write!(state, " int {} = NULL_ATTRIB;\n", sym.name.as_str()); + } + write!(state, "}} attrib_locations;\n"); + + write!( + state, + "void bind_attrib(const char* name, int index) override {{\n" + ); + for i in attribs { + let sym = state.hir.sym(*i); + write!( + state, + " if (strcmp(\"{0}\", name) == 0) {{ attrib_locations.{0} = index; return; }}\n", + sym.name.as_str() + ); + } + write!(state, "}}\n"); + + write!( + state, + "int get_attrib(const char* name) const override {{\n" + ); + for i in attribs { + let sym = state.hir.sym(*i); + write!(state, + " if (strcmp(\"{0}\", name) == 0) {{\ + return attrib_locations.{0} != NULL_ATTRIB ? attrib_locations.{0} : -1;\ + }}\n", + sym.name.as_str()); + } + write!(state, " return -1;\n"); + write!(state, "}}\n"); +} + +fn scalar_type_name(state: &OutputState, ty: &Type) -> String { + let kind_name = if let Some(name) = ty.kind.cxx_primitive_scalar_type_name() { + name.into() + } else { + let buffer = state.push_buffer(); + show_type(state, ty); + state.pop_buffer(buffer) + "_scalar" + }; + if let Some(ref array) = ty.array_sizes { + let size = match &array.sizes[..] { + [size] => size, + _ => panic!(), + }; + let buffer = state.push_buffer(); + show_hir_expr(state, size); + let size_string = state.pop_buffer(buffer); + + format!("Array<{}, {}>", kind_name, size_string) + } else { + kind_name + } +} + +//fn type_name(state: &OutputState, ty: &Type) -> String { +// let buffer = state.push_buffer(); +// show_type(state, ty); +// state.pop_buffer(buffer) +//} + +fn write_load_attribs(state: &mut OutputState, attribs: &[hir::SymRef]) { + write!(state, "static void load_attribs(\ + Self *self, {}_program *prog, VertexAttrib *attribs, unsigned short *indices, \ + int start, int instance, int count) {{\n", state.name); + for i in attribs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _interpolation, _ty, run_class) => { + let name = sym.name.as_str(); + let func = if *run_class == hir::RunClass::Scalar { + "load_flat_attrib" + } else { + "load_attrib" + }; + write!(state, + " {0}(self->{1}, attribs[prog->attrib_locations.{1}], indices, start, instance, count);\n", + func, name); + } + _ => panic!(), + } + } + write!(state, "}}\n"); +} + +fn write_store_outputs(state: &mut OutputState, outputs: &[hir::SymRef]) { + let is_scalar = state.is_scalar.replace(true); + write!(state, "struct FlatOutputs {{\n"); + for i in outputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, ty, run_class) => { + if *run_class == hir::RunClass::Scalar { + show_type(state, ty); + write!(state, " {};\n", sym.name.as_str()); + } + } + _ => panic!(), + } + } + write!(state, "}};\n"); + + write!(state, "struct InterpOutputs {{\n"); + for i in outputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, ty, run_class) => { + if *run_class != hir::RunClass::Scalar { + show_type(state, ty); + write!(state, " {};\n", sym.name.as_str()); + } + } + _ => panic!(), + } + } + + write!(state, "}};\n"); + state.is_scalar.set(is_scalar); + + write!( + state, + "ALWAYS_INLINE void store_flat_outputs(char* dest_ptr) {{\n" + ); + write!( + state, + " auto* dest = reinterpret_cast(dest_ptr);\n" + ); + for i in outputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class == hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!(state, " dest->{} = {};\n", name, name); + } + } + _ => panic!(), + } + } + write!(state, "}}\n"); + + write!( + state, + "ALWAYS_INLINE void store_interp_outputs(char* dest_ptr, size_t stride) {{\n" + ); + write!(state, " for(int n = 0; n < 4; n++) {{\n"); + write!( + state, + " auto* dest = reinterpret_cast(dest_ptr);\n" + ); + for i in outputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class != hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!(state, " dest->{} = get_nth({}, n);\n", name, name); + } + } + _ => panic!(), + } + } + write!(state, " dest_ptr += stride;\n"); + write!(state, " }}\n"); + write!(state, "}}\n"); +} + +fn write_read_inputs(state: &mut OutputState, inputs: &[hir::SymRef]) { + write!( + state, + "typedef {}_vert::FlatOutputs FlatInputs;\n", + state.name + ); + write!( + state, + "typedef {}_vert::InterpOutputs InterpInputs;\n", + state.name + ); + + write!( + state, + "static void read_flat_inputs(Self *self, const FlatInputs *src) {{\n" + ); + for i in inputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class == hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!(state, " self->{} = src->{};\n", name, name); + } + } + _ => panic!(), + } + } + write!(state, "}}\n"); + + write!(state, "InterpInputs interp_step;\n"); + write!(state, + "static void read_interp_inputs(\ + Self *self, const InterpInputs *init, const InterpInputs *step, float step_width) {{\n"); + for i in inputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class != hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!( + state, + " self->{0} = init_interp(init->{0}, step->{0});\n", + name + ); + write!( + state, + " self->interp_step.{0} = step->{0} * step_width;\n", + name + ); + } + } + _ => panic!(), + } + } + write!(state, "}}\n"); + + write!(state, "ALWAYS_INLINE void step_interp_inputs() {{\n"); + if (state.hir.used_fragcoord & 1) != 0 { + write!(state, " step_fragcoord();\n"); + } + for i in inputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class != hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!(state, " {} += interp_step.{};\n", name, name); + } + } + _ => panic!(), + } + } + write!(state, "}}\n"); + + if state.has_draw_span_rgba8 || state.has_draw_span_r8 { + write!( + state, + "ALWAYS_INLINE void step_interp_inputs(int chunks) {{\n" + ); + if (state.hir.used_fragcoord & 1) != 0 { + write!(state, " step_fragcoord(chunks);\n"); + } + for i in inputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class != hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!(state, " {} += interp_step.{} * chunks;\n", name, name); + } + } + _ => panic!(), + } + } + write!(state, "}}\n"); + } +} + +fn write_include_file(state: &mut OutputState, include_file: String) { + let include_contents = std::fs::read_to_string(&include_file).unwrap(); + + let mut offset = 0; + while offset < include_contents.len() { + let s = &include_contents[offset ..]; + if let Some(start_proto) = s.find("draw_span") { + let s = &s[start_proto ..]; + if let Some(end_proto) = s.find(')') { + let proto = &s[.. end_proto]; + if proto.contains("uint32_t") { + state.has_draw_span_rgba8 = true; + } else if proto.contains("uint8_t") { + state.has_draw_span_r8 = true; + } + offset += start_proto + end_proto; + continue; + } + } + break; + } + + let include_name = std::path::Path::new(&include_file) + .file_name() + .unwrap() + .to_string_lossy(); + write!(state, "\n#include \"{}\"\n\n", include_name); +} + +pub struct OutputState { + hir: hir::State, + output: String, + buffer: RefCell, + should_indent: bool, + output_cxx: bool, + indent: i32, + mask: Option>, + cond_index: usize, + return_type: Option>, + return_declared: bool, + return_vector: bool, + is_scalar: Cell, + is_lval: Cell, + name: String, + kind: ShaderKind, + functions: HashMap<(hir::SymRef, u32), bool>, + deps: RefCell>, + vector_mask: u32, + uses_discard: bool, + has_draw_span_rgba8: bool, + has_draw_span_r8: bool, + used_globals: RefCell>, + texel_fetches: RefCell>, +} + +use std::fmt::{Arguments, Write}; + +impl OutputState { + fn indent(&mut self) { + if self.should_indent { + self.indent += 1 + } + } + fn outdent(&mut self) { + if self.should_indent { + self.indent -= 1 + } + } + + fn write(&self, s: &str) { + self.buffer.borrow_mut().push_str(s); + } + + fn flush_buffer(&mut self) { + self.output.push_str(&self.buffer.borrow()); + self.buffer.borrow_mut().clear(); + } + + fn finish_output(&mut self) -> String { + self.flush_buffer(); + + let mut s = String::new(); + mem::swap(&mut self.output, &mut s); + s + } + + fn push_buffer(&self) -> String { + self.buffer.replace(String::new()) + } + + fn pop_buffer(&self, s: String) -> String { + self.buffer.replace(s) + } + + fn write_fmt(&self, args: Arguments) { + let _ = self.buffer.borrow_mut().write_fmt(args); + } +} + +pub fn show_identifier(state: &OutputState, i: &syntax::Identifier) { + state.write(&i.0); +} + +fn glsl_primitive_type_name_to_cxx(glsl_name: &str) -> &str { + hir::TypeKind::from_glsl_primitive_type_name(glsl_name) + .and_then(|kind| kind.cxx_primitive_type_name()) + .unwrap_or(glsl_name) +} + +fn add_used_global(state: &OutputState, i: &hir::SymRef) { + let mut globals = state.used_globals.borrow_mut(); + if !globals.contains(i) { + globals.push(*i); + } +} + +pub fn show_sym(state: &OutputState, i: &hir::SymRef) { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::NativeFunction(_, ref cxx_name) => { + let mut name = sym.name.as_str(); + if state.output_cxx { + name = cxx_name.unwrap_or(name); + } + state.write(name); + } + hir::SymDecl::Global(..) => { + if state.output_cxx { + add_used_global(state, i); + } + let mut name = sym.name.as_str(); + if state.output_cxx { + name = glsl_primitive_type_name_to_cxx(name); + } + state.write(name); + } + hir::SymDecl::UserFunction(..) | hir::SymDecl::Local(..) | hir::SymDecl::Struct(..) => { + let mut name = sym.name.as_str(); + // we want to replace constructor names + if state.output_cxx { + name = glsl_primitive_type_name_to_cxx(name); + } + state.write(name); + } + } +} + +pub fn show_variable(state: &OutputState, i: &hir::SymRef) { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, ty, _) => { + show_type(state, ty); + state.write(" "); + let mut name = sym.name.as_str(); + if state.output_cxx { + name = glsl_primitive_type_name_to_cxx(name); + } + state.write(name); + } + _ => panic!(), + } +} + +pub fn write_default_constructor(state: &OutputState, name: &str) { + // write default constructor + let _ = write!(state, "{}() = default;\n", name); +} + +pub fn write_constructor(state: &OutputState, name: &str, s: &hir::StructFields) { + if s.fields.len() == 1 { + state.write("explicit "); + } + let _ = write!(state, "{}(", name); + let mut first_field = true; + for field in &s.fields { + if !first_field { + state.write(", "); + } + show_type(state, &field.ty); + state.write(" "); + show_identifier_and_type(state, &field.name, &field.ty); + first_field = false; + } + state.write(") : "); + + let mut first_field = true; + for field in &s.fields { + if !first_field { + state.write(", "); + } + let _ = write!(state, "{}({})", field.name, field.name); + first_field = false; + } + state.write("{}\n"); +} + +pub fn write_convert_constructor(state: &OutputState, name: &str, s: &hir::StructFields) { + if s.fields.len() == 1 { + state.write("explicit "); + } + let _ = write!(state, "{}(", name); + let mut first_field = true; + for field in &s.fields { + if !first_field { + state.write(", "); + } + + let is_scalar = state.is_scalar.replace(true); + show_type(state, &field.ty); + state.is_scalar.set(is_scalar); + + state.write(" "); + + show_identifier_and_type(state, &field.name, &field.ty); + first_field = false; + } + state.write(")"); + + let mut first_field = true; + for hir::StructField { ty, name } in &s.fields { + if ty.array_sizes.is_none() { + if first_field { + state.write(":"); + } else { + state.write(","); + } + let _ = write!(state, "{}({})", name, name); + first_field = false; + } + } + state.write("{\n"); + for hir::StructField { ty, name } in &s.fields { + if ty.array_sizes.is_some() { + let _ = write!(state, "this->{}.convert({});\n", name, name); + } + } + state.write("}\n"); + + let _ = write!(state, "IMPLICIT {}({}_scalar s)", name, name); + let mut first_field = true; + for hir::StructField { ty, name } in &s.fields { + if ty.array_sizes.is_none() { + if first_field { + state.write(":"); + } else { + state.write(","); + } + let _ = write!(state, "{}(s.{})", name, name); + first_field = false; + } + } + state.write("{\n"); + for hir::StructField { ty, name } in &s.fields { + if ty.array_sizes.is_some() { + let _ = write!(state, "{}.convert(s.{});\n", name, name); + } + } + state.write("}\n"); +} + +pub fn write_if_then_else(state: &OutputState, name: &str, s: &hir::StructFields) { + let _ = write!( + state, + "friend {} if_then_else(I32 c, {} t, {} e) {{ return {}(\n", + name, name, name, name + ); + let mut first_field = true; + for field in &s.fields { + if !first_field { + state.write(", "); + } + let _ = write!(state, "if_then_else(c, t.{}, e.{})", field.name, field.name); + first_field = false; + } + state.write(");\n}"); +} + +pub fn show_storage_class(state: &OutputState, q: &hir::StorageClass) { + match *q { + hir::StorageClass::None => {} + hir::StorageClass::Const => { + state.write("const "); + } + hir::StorageClass::In => { + state.write("in "); + } + hir::StorageClass::Out => { + state.write("out "); + } + hir::StorageClass::FragColor(index) => { + write!(state, "layout(location = 0, index = {}) out ", index); + } + hir::StorageClass::Uniform | hir::StorageClass::Sampler(..) => { + state.write("uniform "); + } + } +} + +pub fn show_sym_decl(state: &OutputState, i: &hir::SymRef) { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(storage, ..) => { + if !state.output_cxx { + show_storage_class(state, storage) + } + if storage == &hir::StorageClass::Const { + state.write("static constexpr "); + } + let mut name = sym.name.as_str(); + if state.output_cxx { + name = glsl_primitive_type_name_to_cxx(name); + } + state.write(name); + } + hir::SymDecl::Local(storage, ..) => { + if !state.output_cxx { + show_storage_class(state, storage) + } + if storage == &hir::StorageClass::Const { + state.write("const "); + } + let mut name = sym.name.as_str(); + if state.output_cxx { + name = glsl_primitive_type_name_to_cxx(name); + } + state.write(name); + } + hir::SymDecl::Struct(s) => { + let name = sym.name.as_str(); + + if state.output_cxx { + let name_scalar = format!("{}_scalar", name); + write!(state, "struct {} {{\n", name_scalar); + let is_scalar = state.is_scalar.replace(true); + for field in &s.fields { + show_struct_field(state, field); + } + write_default_constructor(state, &name_scalar); + write_constructor(state, &name_scalar, s); + state.is_scalar.set(is_scalar); + state.write("};\n"); + } + + write!(state, "struct {} {{\n", name); + for field in &s.fields { + show_struct_field(state, field); + } + + // write if_then_else + if state.output_cxx { + write_default_constructor(state, name); + write_constructor(state, name, s); + write_convert_constructor(state, name, s); + write_if_then_else(state, name, s); + } + state.write("}"); + } + _ => panic!(), + } +} + +pub fn show_type_name(state: &OutputState, t: &syntax::TypeName) { + state.write(&t.0); +} + +pub fn show_type_specifier_non_array(state: &mut OutputState, t: &syntax::TypeSpecifierNonArray) { + if let Some(kind) = hir::TypeKind::from_primitive_type_specifier(t) { + show_type_kind(state, &kind); + } else { + match t { + syntax::TypeSpecifierNonArray::Struct(ref _s) => panic!(), //show_struct_non_declaration(state, s), + syntax::TypeSpecifierNonArray::TypeName(ref tn) => show_type_name(state, tn), + _ => unreachable!(), + } + } +} + +pub fn show_type_kind(state: &OutputState, t: &hir::TypeKind) { + if state.output_cxx { + if state.is_scalar.get() { + if let Some(name) = t.cxx_primitive_scalar_type_name() { + state.write(name); + } else if let Some(name) = t.cxx_primitive_type_name() { + let mut scalar_name = String::from(name); + scalar_name.push_str("_scalar"); + state.write(scalar_name.as_str()); + } else { + match t { + hir::TypeKind::Struct(ref s) => { + let mut scalar_name = String::from(state.hir.sym(*s).name.as_str()); + scalar_name.push_str("_scalar"); + state.write(scalar_name.as_str()); + } + _ => unreachable!(), + } + } + } else if let Some(name) = t.cxx_primitive_type_name() { + state.write(name); + } else { + match t { + hir::TypeKind::Struct(ref s) => { + state.write(state.hir.sym(*s).name.as_str()); + } + _ => unreachable!(), + } + } + } else if let Some(name) = t.glsl_primitive_type_name() { + state.write(name); + } else { + match t { + hir::TypeKind::Struct(ref s) => { + state.write(state.hir.sym(*s).name.as_str()); + } + _ => unreachable!(), + } + } +} + +pub fn show_type_specifier(state: &mut OutputState, t: &syntax::TypeSpecifier) { + show_type_specifier_non_array(state, &t.ty); + + if let Some(ref arr_spec) = t.array_specifier { + show_array_spec(state, arr_spec); + } +} + +pub fn show_type(state: &OutputState, t: &Type) { + if !state.output_cxx { + if let Some(ref precision) = t.precision { + show_precision_qualifier(state, precision); + state.write(" "); + } + } + + if state.output_cxx { + if let Some(ref array) = t.array_sizes { + state.write("Array<"); + show_type_kind(state, &t.kind); + let size = match &array.sizes[..] { + [size] => size, + _ => panic!(), + }; + state.write(","); + show_hir_expr(state, size); + state.write(">"); + } else { + show_type_kind(state, &t.kind); + } + } else { + show_type_kind(state, &t.kind); + } + + /*if let Some(ref arr_spec) = t.array_sizes { + panic!(); + }*/ +} + +/*pub fn show_fully_specified_type(state: &mut OutputState, t: &FullySpecifiedType) { + state.flat = false; + if let Some(ref qual) = t.qualifier { + if !state.output_cxx { + show_type_qualifier(state, &qual); + } else { + state.flat = + qual.qualifiers.0.iter() + .flat_map(|q| match q { syntax::TypeQualifierSpec::Interpolation(Flat) => Some(()), _ => None}) + .next().is_some(); + } + state.write(" "); + } + + show_type_specifier(state, &t.ty); +}*/ + +/*pub fn show_struct_non_declaration(state: &mut OutputState, s: &syntax::StructSpecifier) { + state.write("struct "); + + if let Some(ref name) = s.name { + let _ = write!(state, "{} ", name); + } + + state.write("{\n"); + + for field in &s.fields.0 { + show_struct_field(state, field); + } + + state.write("}"); +}*/ + +pub fn show_struct(_state: &OutputState, _s: &syntax::StructSpecifier) { + panic!(); + //show_struct_non_declaration(state, s); + //state.write(";\n"); +} + +pub fn show_struct_field(state: &OutputState, field: &hir::StructField) { + show_type(state, &field.ty); + state.write(" "); + + show_identifier_and_type(state, &field.name, &field.ty); + + state.write(";\n"); +} + +pub fn show_array_spec(state: &OutputState, a: &syntax::ArraySpecifier) { + match *a { + syntax::ArraySpecifier::Unsized => { + state.write("[]"); + } + syntax::ArraySpecifier::ExplicitlySized(ref e) => { + state.write("["); + show_expr(state, &e); + state.write("]"); + } + } +} + +pub fn show_identifier_and_type(state: &OutputState, ident: &syntax::Identifier, ty: &hir::Type) { + let _ = write!(state, "{}", ident); + + if !state.output_cxx { + if let Some(ref arr_spec) = ty.array_sizes { + show_array_sizes(state, &arr_spec); + } + } +} + +pub fn show_arrayed_identifier(state: &OutputState, ident: &syntax::ArrayedIdentifier) { + let _ = write!(state, "{}", ident.ident); + + if let Some(ref arr_spec) = ident.array_spec { + show_array_spec(state, &arr_spec); + } +} + +pub fn show_array_sizes(state: &OutputState, a: &hir::ArraySizes) { + state.write("["); + match &a.sizes[..] { + [a] => show_hir_expr(state, a), + _ => panic!(), + } + + state.write("]"); + /* + match *a { + syntax::ArraySpecifier::Unsized => { state.write("[]"); } + syntax::ArraySpecifier::ExplicitlySized(ref e) => { + state.write("["); + show_expr(state, &e); + state.write("]"); + } + }*/ +} + +pub fn show_type_qualifier(state: &OutputState, q: &hir::TypeQualifier) { + let mut qualifiers = q.qualifiers.0.iter(); + let first = qualifiers.next().unwrap(); + + show_type_qualifier_spec(state, first); + + for qual_spec in qualifiers { + state.write(" "); + show_type_qualifier_spec(state, qual_spec) + } +} + +pub fn show_type_qualifier_spec(state: &OutputState, q: &hir::TypeQualifierSpec) { + match *q { + hir::TypeQualifierSpec::Layout(ref l) => show_layout_qualifier(state, &l), + hir::TypeQualifierSpec::Parameter(ref _p) => panic!(), + hir::TypeQualifierSpec::Memory(ref _m) => panic!(), + hir::TypeQualifierSpec::Invariant => { + state.write("invariant"); + } + hir::TypeQualifierSpec::Precise => { + state.write("precise"); + } + } +} + +pub fn show_syntax_storage_qualifier(state: &OutputState, q: &syntax::StorageQualifier) { + match *q { + syntax::StorageQualifier::Const => { + state.write("const"); + } + syntax::StorageQualifier::InOut => { + state.write("inout"); + } + syntax::StorageQualifier::In => { + state.write("in"); + } + syntax::StorageQualifier::Out => { + state.write("out"); + } + syntax::StorageQualifier::Centroid => { + state.write("centroid"); + } + syntax::StorageQualifier::Patch => { + state.write("patch"); + } + syntax::StorageQualifier::Sample => { + state.write("sample"); + } + syntax::StorageQualifier::Uniform => { + state.write("uniform"); + } + syntax::StorageQualifier::Attribute => { + state.write("attribute"); + } + syntax::StorageQualifier::Varying => { + state.write("varying"); + } + syntax::StorageQualifier::Buffer => { + state.write("buffer"); + } + syntax::StorageQualifier::Shared => { + state.write("shared"); + } + syntax::StorageQualifier::Coherent => { + state.write("coherent"); + } + syntax::StorageQualifier::Volatile => { + state.write("volatile"); + } + syntax::StorageQualifier::Restrict => { + state.write("restrict"); + } + syntax::StorageQualifier::ReadOnly => { + state.write("readonly"); + } + syntax::StorageQualifier::WriteOnly => { + state.write("writeonly"); + } + syntax::StorageQualifier::Subroutine(ref n) => show_subroutine(state, &n), + } +} + +pub fn show_subroutine(state: &OutputState, types: &[syntax::TypeName]) { + state.write("subroutine"); + + if !types.is_empty() { + state.write("("); + + let mut types_iter = types.iter(); + let first = types_iter.next().unwrap(); + + show_type_name(state, first); + + for type_name in types_iter { + state.write(", "); + show_type_name(state, type_name); + } + + state.write(")"); + } +} + +pub fn show_layout_qualifier(state: &OutputState, l: &syntax::LayoutQualifier) { + let mut qualifiers = l.ids.0.iter(); + let first = qualifiers.next().unwrap(); + + state.write("layout ("); + show_layout_qualifier_spec(state, first); + + for qual_spec in qualifiers { + state.write(", "); + show_layout_qualifier_spec(state, qual_spec); + } + + state.write(")"); +} + +pub fn show_layout_qualifier_spec(state: &OutputState, l: &syntax::LayoutQualifierSpec) { + match *l { + syntax::LayoutQualifierSpec::Identifier(ref i, Some(ref e)) => { + let _ = write!(state, "{} = ", i); + show_expr(state, &e); + } + syntax::LayoutQualifierSpec::Identifier(ref i, None) => show_identifier(state, &i), + syntax::LayoutQualifierSpec::Shared => { + state.write("shared"); + } + } +} + +pub fn show_precision_qualifier(state: &OutputState, p: &syntax::PrecisionQualifier) { + match *p { + syntax::PrecisionQualifier::High => { + state.write("highp"); + } + syntax::PrecisionQualifier::Medium => { + state.write("mediump"); + } + syntax::PrecisionQualifier::Low => { + state.write("low"); + } + } +} + +pub fn show_interpolation_qualifier(state: &OutputState, i: &syntax::InterpolationQualifier) { + match *i { + syntax::InterpolationQualifier::Smooth => { + state.write("smooth"); + } + syntax::InterpolationQualifier::Flat => { + state.write("flat"); + } + syntax::InterpolationQualifier::NoPerspective => { + state.write("noperspective"); + } + } +} + +pub fn show_parameter_qualifier(state: &mut OutputState, i: &Option) { + if let Some(i) = i { + if state.output_cxx { + match *i { + hir::ParameterQualifier::Out => { + state.write("&"); + } + hir::ParameterQualifier::InOut => { + state.write("&"); + } + _ => {} + } + } else { + match *i { + hir::ParameterQualifier::Const => { + state.write("const"); + } + hir::ParameterQualifier::In => { + state.write("in"); + } + hir::ParameterQualifier::Out => { + state.write("out"); + } + hir::ParameterQualifier::InOut => { + state.write("inout"); + } + } + } + } +} + +pub fn show_float(state: &OutputState, x: f32) { + if x.fract() == 0. { + write!(state, "{}.f", x); + } else { + write!(state, "{}f", x); + } +} + +pub fn show_double(state: &OutputState, x: f64) { + // force doubles to print as floats + if x.fract() == 0. { + write!(state, "{}.f", x); + } else { + write!(state, "{}f", x); + } +} + +trait SwizzelSelectorExt { + fn to_args(&self) -> String; +} + +impl SwizzelSelectorExt for SwizzleSelector { + fn to_args(&self) -> String { + let mut s = Vec::new(); + let fs = match self.field_set { + hir::FieldSet::Rgba => ["R", "G", "B", "A"], + hir::FieldSet::Xyzw => ["X", "Y", "Z", "W"], + hir::FieldSet::Stpq => ["S", "T", "P", "Q"], + }; + for i in &self.components { + s.push(fs[*i as usize]) + } + s.join(", ") + } +} + +fn expr_run_class(state: &OutputState, expr: &hir::Expr) -> hir::RunClass { + match &expr.kind { + hir::ExprKind::Variable(i) => symbol_run_class(&state.hir.sym(*i).decl, state.vector_mask), + hir::ExprKind::IntConst(_) + | hir::ExprKind::UIntConst(_) + | hir::ExprKind::BoolConst(_) + | hir::ExprKind::FloatConst(_) + | hir::ExprKind::DoubleConst(_) => hir::RunClass::Scalar, + hir::ExprKind::Unary(_, ref e) => expr_run_class(state, e), + hir::ExprKind::Binary(_, ref l, ref r) => { + expr_run_class(state, l).merge(expr_run_class(state, r)) + } + hir::ExprKind::Ternary(ref c, ref s, ref e) => expr_run_class(state, c) + .merge(expr_run_class(state, s)) + .merge(expr_run_class(state, e)), + hir::ExprKind::Assignment(ref v, _, ref e) => { + expr_run_class(state, v).merge(expr_run_class(state, e)) + } + hir::ExprKind::Bracket(ref e, ref indx) => { + expr_run_class(state, e).merge(expr_run_class(state, indx)) + } + hir::ExprKind::FunCall(ref fun, ref args) => { + let arg_mask: u32 = args.iter().enumerate().fold(0, |mask, (idx, e)| { + if expr_run_class(state, e) == hir::RunClass::Vector { + mask | (1 << idx) + } else { + mask + } + }); + match fun { + hir::FunIdentifier::Identifier(ref sym) => match &state.hir.sym(*sym).decl { + hir::SymDecl::NativeFunction(..) => { + if arg_mask != 0 { + hir::RunClass::Vector + } else { + hir::RunClass::Scalar + } + } + hir::SymDecl::UserFunction(ref fd, ref run_class) => { + let param_mask: u32 = fd.prototype.parameters.iter().enumerate().fold( + arg_mask, + |mask, (idx, param)| { + if let hir::FunctionParameterDeclaration::Named(Some(qual), p) = + param + { + match qual { + hir::ParameterQualifier::InOut + | hir::ParameterQualifier::Out => { + if symbol_run_class( + &state.hir.sym(p.sym).decl, + arg_mask, + ) == hir::RunClass::Vector + { + mask | (1 << idx) + } else { + mask + } + } + _ => mask, + } + } else { + mask + } + }, + ); + match *run_class { + hir::RunClass::Scalar => hir::RunClass::Scalar, + hir::RunClass::Dependent(mask) => { + if (mask & param_mask) != 0 { + hir::RunClass::Vector + } else { + hir::RunClass::Scalar + } + } + _ => hir::RunClass::Vector, + } + } + hir::SymDecl::Struct(..) => { + if arg_mask != 0 { + hir::RunClass::Vector + } else { + hir::RunClass::Scalar + } + } + _ => panic!(), + }, + hir::FunIdentifier::Constructor(..) => { + if arg_mask != 0 { + hir::RunClass::Vector + } else { + hir::RunClass::Scalar + } + } + } + } + hir::ExprKind::Dot(ref e, _) => expr_run_class(state, e), + hir::ExprKind::SwizzleSelector(ref e, _) => expr_run_class(state, e), + hir::ExprKind::PostInc(ref e) => expr_run_class(state, e), + hir::ExprKind::PostDec(ref e) => expr_run_class(state, e), + hir::ExprKind::Comma(_, ref e) => expr_run_class(state, e), + hir::ExprKind::Cond(_, ref e) => expr_run_class(state, e), + hir::ExprKind::CondMask => hir::RunClass::Vector, + } +} + +pub fn show_hir_expr(state: &OutputState, expr: &hir::Expr) { + show_hir_expr_inner(state, expr, false); +} + +pub fn show_hir_expr_inner(state: &OutputState, expr: &hir::Expr, top_level: bool) { + match expr.kind { + hir::ExprKind::Variable(ref i) => show_sym(state, i), + hir::ExprKind::IntConst(ref x) => { + let _ = write!(state, "{}", x); + } + hir::ExprKind::UIntConst(ref x) => { + let _ = write!(state, "{}u", x); + } + hir::ExprKind::BoolConst(ref x) => { + let _ = write!(state, "{}", x); + } + hir::ExprKind::FloatConst(ref x) => show_float(state, *x), + hir::ExprKind::DoubleConst(ref x) => show_double(state, *x), + hir::ExprKind::Unary(ref op, ref e) => { + show_unary_op(state, &op); + state.write("("); + show_hir_expr(state, &e); + state.write(")"); + } + hir::ExprKind::Binary(ref op, ref l, ref r) => { + state.write("("); + show_hir_expr(state, &l); + state.write(")"); + show_binary_op(state, &op); + state.write("("); + show_hir_expr(state, &r); + state.write(")"); + } + hir::ExprKind::Ternary(ref c, ref s, ref e) => { + if state.output_cxx && expr_run_class(state, c) != hir::RunClass::Scalar { + state.write("if_then_else("); + show_hir_expr(state, &c); + state.write(", "); + show_hir_expr(state, &s); + state.write(", "); + show_hir_expr(state, &e); + state.write(")"); + } else { + show_hir_expr(state, &c); + state.write(" ? "); + show_hir_expr(state, &s); + state.write(" : "); + show_hir_expr(state, &e); + } + } + hir::ExprKind::Assignment(ref v, ref op, ref e) => { + let is_output = hir::is_output(v, &state.hir).is_some(); + let is_scalar_var = expr_run_class(state, v) == hir::RunClass::Scalar; + let is_scalar_expr = expr_run_class(state, e) == hir::RunClass::Scalar; + let force_scalar = is_scalar_var && !is_scalar_expr; + + if let Some(mask) = &state.mask { + let is_scalar_mask = expr_run_class(state, mask) == hir::RunClass::Scalar; + let force_scalar_mask = is_scalar_var && is_scalar_expr && !is_scalar_mask; + + if force_scalar || force_scalar_mask { + if top_level { + state.write("if ("); + } else { + state.write("("); + } + } else { + state.is_lval.set(true); + show_hir_expr(state, &v); + state.is_lval.set(false); + state.write(" = if_then_else("); + } + + if is_output && state.return_declared { + state.write("(("); + show_hir_expr(state, mask); + state.write(")&ret_mask)"); + } else { + show_hir_expr(state, mask); + } + if force_scalar || force_scalar_mask { + if top_level { + state.write("[0]) { "); + } else { + state.write("[0] ? "); + } + state.is_lval.set(true); + show_hir_expr(state, &v); + state.is_lval.set(false); + state.write(" = "); + } else { + state.write(","); + } + + if op != &syntax::AssignmentOp::Equal { + show_hir_expr(state, &v); + } + + match *op { + syntax::AssignmentOp::Equal => {} + syntax::AssignmentOp::Mult => { + state.write("*"); + } + syntax::AssignmentOp::Div => { + state.write("/"); + } + syntax::AssignmentOp::Mod => { + state.write("%"); + } + syntax::AssignmentOp::Add => { + state.write("+"); + } + syntax::AssignmentOp::Sub => { + state.write("-"); + } + syntax::AssignmentOp::LShift => { + state.write("<<"); + } + syntax::AssignmentOp::RShift => { + state.write(">>"); + } + syntax::AssignmentOp::And => { + state.write("&"); + } + syntax::AssignmentOp::Xor => { + state.write("^"); + } + syntax::AssignmentOp::Or => { + state.write("|"); + } + } + if force_scalar { + state.write("force_scalar("); + } + show_hir_expr(state, &e); + if force_scalar { + state.write(")"); + } + if force_scalar || force_scalar_mask { + if top_level { + state.write("; }"); + } else { + state.write(" : "); + show_hir_expr(state, &v); + state.write(")"); + } + } else { + state.write(","); + show_hir_expr(state, &v); + state.write(")"); + } + } else { + state.is_lval.set(true); + show_hir_expr(state, &v); + state.is_lval.set(false); + state.write(" "); + + if is_output && state.return_declared { + state.write("= "); + if force_scalar { + state.write("force_scalar("); + } + state.write("if_then_else(ret_mask,"); + + if op != &syntax::AssignmentOp::Equal { + show_hir_expr(state, &v); + } + + match *op { + syntax::AssignmentOp::Equal => {} + syntax::AssignmentOp::Mult => { + state.write("*"); + } + syntax::AssignmentOp::Div => { + state.write("/"); + } + syntax::AssignmentOp::Mod => { + state.write("%"); + } + syntax::AssignmentOp::Add => { + state.write("+"); + } + syntax::AssignmentOp::Sub => { + state.write("-"); + } + syntax::AssignmentOp::LShift => { + state.write("<<"); + } + syntax::AssignmentOp::RShift => { + state.write(">>"); + } + syntax::AssignmentOp::And => { + state.write("&"); + } + syntax::AssignmentOp::Xor => { + state.write("^"); + } + syntax::AssignmentOp::Or => { + state.write("|"); + } + } + show_hir_expr(state, &e); + state.write(","); + show_hir_expr(state, &v); + state.write(")"); + } else { + show_assignment_op(state, &op); + state.write(" "); + if force_scalar { + state.write("force_scalar("); + } + show_hir_expr(state, &e); + } + + if force_scalar { + state.write(")"); + } + } + } + hir::ExprKind::Bracket(ref e, ref indx) => { + show_hir_expr(state, &e); + state.write("["); + show_hir_expr(state, indx); + state.write("]"); + } + hir::ExprKind::FunCall(ref fun, ref args) => { + let mut cond_mask: u32 = 0; + let mut adapt_mask: u32 = 0; + let mut has_ret = false; + let mut array_constructor = false; + + let mut arg_mask: u32 = 0; + for (idx, e) in args.iter().enumerate() { + if expr_run_class(state, e) == hir::RunClass::Vector { + arg_mask |= 1 << idx; + } + } + + match fun { + hir::FunIdentifier::Constructor(t) => { + let is_scalar = state.is_scalar.replace(arg_mask == 0); + show_type(state, t); + state.is_scalar.set(is_scalar); + array_constructor = t.array_sizes.is_some(); + } + hir::FunIdentifier::Identifier(name) => { + if state.output_cxx { + let sym = state.hir.sym(*name); + match &sym.decl { + hir::SymDecl::NativeFunction(..) => { + if sym.name == "texelFetchOffset" && args.len() >= 4 { + if let Some((sampler, base, x, y)) = hir::get_texel_fetch_offset( + &state.hir, &args[0], &args[1], &args[3], + ) { + let base_sym = state.hir.sym(base); + if symbol_run_class(&base_sym.decl, state.vector_mask) + == hir::RunClass::Scalar + { + let sampler_sym = state.hir.sym(sampler); + add_used_global(state, &sampler); + if let hir::SymDecl::Global(..) = &base_sym.decl { + add_used_global(state, &base); + } + if y != 0 { + write!( + state, + "{}_{}_fetch[{}+{}*{}->stride]", + sampler_sym.name, + base_sym.name, + x, + y, + sampler_sym.name + ); + } else { + write!( + state, + "{}_{}_fetch[{}]", + sampler_sym.name, base_sym.name, x + ); + } + return; + } + } + } + show_sym(state, name) + } + hir::SymDecl::UserFunction(ref fd, ref _run_class) => { + if (state.mask.is_some() || state.return_declared) && + !fd.globals.is_empty() + { + cond_mask |= 1 << 31; + } + let mut param_mask: u32 = 0; + for (idx, (param, e)) in + fd.prototype.parameters.iter().zip(args.iter()).enumerate() + { + if let hir::FunctionParameterDeclaration::Named(qual, p) = param + { + if symbol_run_class(&state.hir.sym(p.sym).decl, arg_mask) + == hir::RunClass::Vector + { + param_mask |= 1 << idx; + } + match qual { + Some(hir::ParameterQualifier::InOut) + | Some(hir::ParameterQualifier::Out) => { + if state.mask.is_some() || state.return_declared { + cond_mask |= 1 << idx; + } + if (!arg_mask & param_mask & (1 << idx)) != 0 { + if adapt_mask == 0 { + state.write(if top_level { + "{ " + } else { + "({ " + }); + } + show_type(state, &p.ty); + write!(state, " _arg{}_ = ", idx); + show_hir_expr(state, e); + state.write("; "); + adapt_mask |= 1 << idx; + } + } + _ => {} + } + } + } + if adapt_mask != 0 && + fd.prototype.ty.kind != hir::TypeKind::Void && + !top_level + { + state.write("auto _ret_ = "); + has_ret = true; + } + show_sym(state, name); + let mut deps = state.deps.borrow_mut(); + let dep_key = ( + *name, + if cond_mask != 0 { + param_mask | (1 << 31) + } else { + param_mask + }, + ); + if !deps.contains(&dep_key) { + deps.push(dep_key); + } + } + hir::SymDecl::Struct(..) => { + show_sym(state, name); + if arg_mask == 0 { + state.write("_scalar"); + } + } + _ => panic!("bad identifier to function call"), + } + } + } + } + + if array_constructor { + state.write("{{"); + } else { + state.write("("); + } + + for (idx, e) in args.iter().enumerate() { + if idx != 0 { + state.write(", "); + } + if (adapt_mask & (1 << idx)) != 0 { + write!(state, "_arg{}_", idx); + } else { + show_hir_expr(state, e); + } + } + + if cond_mask != 0 { + if !args.is_empty() { + state.write(", "); + } + if let Some(mask) = &state.mask { + if state.return_declared { + state.write("("); + show_hir_expr(state, mask); + state.write(")&ret_mask"); + } else { + show_hir_expr(state, mask); + } + } else if state.return_declared { + state.write("ret_mask"); + } else { + state.write("~0"); + } + } + + if array_constructor { + state.write("}}"); + } else { + state.write(")"); + } + + if adapt_mask != 0 { + state.write("; "); + for (idx, e) in args.iter().enumerate() { + if (adapt_mask & (1 << idx)) != 0 { + state.is_lval.set(true); + show_hir_expr(state, e); + state.is_lval.set(false); + write!(state, " = force_scalar(_arg{}_); ", idx); + } + } + if has_ret { + state.write("_ret_; })"); + } else { + state.write(if top_level { "}" } else { "})" }); + } + } + } + hir::ExprKind::Dot(ref e, ref i) => { + state.write("("); + show_hir_expr(state, &e); + state.write(")"); + state.write("."); + show_identifier(state, i); + } + hir::ExprKind::SwizzleSelector(ref e, ref s) => { + if state.output_cxx { + state.write("("); + show_hir_expr(state, &e); + if state.is_lval.get() && s.components.len() > 1 { + state.write(").lsel("); + } else { + state.write(").sel("); + } + state.write(&s.to_args()); + state.write(")"); + } else { + state.write("("); + show_hir_expr(state, &e); + state.write(")"); + state.write("."); + state.write(&s.to_string()); + } + } + hir::ExprKind::PostInc(ref e) => { + show_hir_expr(state, &e); + state.write("++"); + } + hir::ExprKind::PostDec(ref e) => { + show_hir_expr(state, &e); + state.write("--"); + } + hir::ExprKind::Comma(ref a, ref b) => { + show_hir_expr(state, &a); + state.write(", "); + show_hir_expr(state, &b); + } + hir::ExprKind::Cond(index, _) => { + write!(state, "_c{}_", index); + } + hir::ExprKind::CondMask => { + state.write("_cond_mask_"); + } + } +} + +pub fn show_expr(state: &OutputState, expr: &syntax::Expr) { + match *expr { + syntax::Expr::Variable(ref i) => show_identifier(state, &i), + syntax::Expr::IntConst(ref x) => { + let _ = write!(state, "{}", x); + } + syntax::Expr::UIntConst(ref x) => { + let _ = write!(state, "{}u", x); + } + syntax::Expr::BoolConst(ref x) => { + let _ = write!(state, "{}", x); + } + syntax::Expr::FloatConst(ref x) => show_float(state, *x), + syntax::Expr::DoubleConst(ref x) => show_double(state, *x), + syntax::Expr::Unary(ref op, ref e) => { + show_unary_op(state, &op); + state.write("("); + show_expr(state, &e); + state.write(")"); + } + syntax::Expr::Binary(ref op, ref l, ref r) => { + state.write("("); + show_expr(state, &l); + state.write(")"); + show_binary_op(state, &op); + state.write("("); + show_expr(state, &r); + state.write(")"); + } + syntax::Expr::Ternary(ref c, ref s, ref e) => { + show_expr(state, &c); + state.write(" ? "); + show_expr(state, &s); + state.write(" : "); + show_expr(state, &e); + } + syntax::Expr::Assignment(ref v, ref op, ref e) => { + show_expr(state, &v); + state.write(" "); + show_assignment_op(state, &op); + state.write(" "); + show_expr(state, &e); + } + syntax::Expr::Bracket(ref e, ref a) => { + show_expr(state, &e); + show_array_spec(state, &a); + } + syntax::Expr::FunCall(ref fun, ref args) => { + show_function_identifier(state, &fun); + state.write("("); + + if !args.is_empty() { + let mut args_iter = args.iter(); + let first = args_iter.next().unwrap(); + show_expr(state, first); + + for e in args_iter { + state.write(", "); + show_expr(state, e); + } + } + + state.write(")"); + } + syntax::Expr::Dot(ref e, ref i) => { + state.write("("); + show_expr(state, &e); + state.write(")"); + state.write("."); + show_identifier(state, &i); + } + syntax::Expr::PostInc(ref e) => { + show_expr(state, &e); + state.write("++"); + } + syntax::Expr::PostDec(ref e) => { + show_expr(state, &e); + state.write("--"); + } + syntax::Expr::Comma(ref a, ref b) => { + show_expr(state, &a); + state.write(", "); + show_expr(state, &b); + } + } +} + +pub fn show_unary_op(state: &OutputState, op: &syntax::UnaryOp) { + match *op { + syntax::UnaryOp::Inc => { + state.write("++"); + } + syntax::UnaryOp::Dec => { + state.write("--"); + } + syntax::UnaryOp::Add => { + state.write("+"); + } + syntax::UnaryOp::Minus => { + state.write("-"); + } + syntax::UnaryOp::Not => { + state.write("!"); + } + syntax::UnaryOp::Complement => { + state.write("~"); + } + } +} + +pub fn show_binary_op(state: &OutputState, op: &syntax::BinaryOp) { + match *op { + syntax::BinaryOp::Or => { + state.write("||"); + } + syntax::BinaryOp::Xor => { + state.write("^^"); + } + syntax::BinaryOp::And => { + state.write("&&"); + } + syntax::BinaryOp::BitOr => { + state.write("|"); + } + syntax::BinaryOp::BitXor => { + state.write("^"); + } + syntax::BinaryOp::BitAnd => { + state.write("&"); + } + syntax::BinaryOp::Equal => { + state.write("=="); + } + syntax::BinaryOp::NonEqual => { + state.write("!="); + } + syntax::BinaryOp::LT => { + state.write("<"); + } + syntax::BinaryOp::GT => { + state.write(">"); + } + syntax::BinaryOp::LTE => { + state.write("<="); + } + syntax::BinaryOp::GTE => { + state.write(">="); + } + syntax::BinaryOp::LShift => { + state.write("<<"); + } + syntax::BinaryOp::RShift => { + state.write(">>"); + } + syntax::BinaryOp::Add => { + state.write("+"); + } + syntax::BinaryOp::Sub => { + state.write("-"); + } + syntax::BinaryOp::Mult => { + state.write("*"); + } + syntax::BinaryOp::Div => { + state.write("/"); + } + syntax::BinaryOp::Mod => { + state.write("%"); + } + } +} + +pub fn show_assignment_op(state: &OutputState, op: &syntax::AssignmentOp) { + match *op { + syntax::AssignmentOp::Equal => { + state.write("="); + } + syntax::AssignmentOp::Mult => { + state.write("*="); + } + syntax::AssignmentOp::Div => { + state.write("/="); + } + syntax::AssignmentOp::Mod => { + state.write("%="); + } + syntax::AssignmentOp::Add => { + state.write("+="); + } + syntax::AssignmentOp::Sub => { + state.write("-="); + } + syntax::AssignmentOp::LShift => { + state.write("<<="); + } + syntax::AssignmentOp::RShift => { + state.write(">>="); + } + syntax::AssignmentOp::And => { + state.write("&="); + } + syntax::AssignmentOp::Xor => { + state.write("^="); + } + syntax::AssignmentOp::Or => { + state.write("|="); + } + } +} + +pub fn show_function_identifier(state: &OutputState, i: &syntax::FunIdentifier) { + match *i { + syntax::FunIdentifier::Identifier(ref n) => show_identifier(state, &n), + syntax::FunIdentifier::Expr(ref e) => show_expr(state, &*e), + } +} + +pub fn show_hir_function_identifier(state: &OutputState, i: &hir::FunIdentifier) { + match *i { + hir::FunIdentifier::Identifier(ref n) => show_sym(state, n), + hir::FunIdentifier::Constructor(ref t) => show_type(state, &*t), + } +} + +pub fn show_declaration(state: &mut OutputState, d: &hir::Declaration) { + show_indent(state); + match *d { + hir::Declaration::FunctionPrototype(ref proto) => { + if !state.output_cxx { + show_function_prototype(state, &proto); + state.write(";\n"); + } + } + hir::Declaration::InitDeclaratorList(ref list) => { + show_init_declarator_list(state, &list); + state.write(";\n"); + + if state.output_cxx { + let base = list.head.name; + let base_sym = state.hir.sym(base); + if let hir::SymDecl::Local(..) = &base_sym.decl { + if symbol_run_class(&base_sym.decl, state.vector_mask) == hir::RunClass::Scalar + { + let mut texel_fetches = state.texel_fetches.borrow_mut(); + while let Some(idx) = texel_fetches.iter().position(|&(_, b, _)| b == base) + { + let (sampler, _, offsets) = texel_fetches.remove(idx); + let sampler_sym = state.hir.sym(sampler); + define_texel_fetch_ptr(state, &base_sym, &sampler_sym, &offsets); + } + } + } + } + } + hir::Declaration::Precision(ref qual, ref ty) => { + if !state.output_cxx { + show_precision_qualifier(state, &qual); + show_type_specifier(state, &ty); + state.write(";\n"); + } + } + hir::Declaration::Block(ref _block) => { + panic!(); + //show_block(state, &block); + //state.write(";\n"); + } + hir::Declaration::Global(ref qual, ref identifiers) => { + show_type_qualifier(state, &qual); + + if !identifiers.is_empty() { + let mut iter = identifiers.iter(); + let first = iter.next().unwrap(); + show_identifier(state, first); + + for identifier in iter { + let _ = write!(state, ", {}", identifier); + } + } + + state.write(";\n"); + } + hir::Declaration::StructDefinition(ref sym) => { + show_sym_decl(state, sym); + + state.write(";\n"); + } + } +} + +pub fn show_function_prototype(state: &mut OutputState, fp: &hir::FunctionPrototype) { + let is_scalar = state.is_scalar.replace(!state.return_vector); + show_type(state, &fp.ty); + state.is_scalar.set(is_scalar); + + state.write(" "); + show_identifier(state, &fp.name); + + state.write("("); + + if !fp.parameters.is_empty() { + let mut iter = fp.parameters.iter(); + let first = iter.next().unwrap(); + show_function_parameter_declaration(state, first); + + for param in iter { + state.write(", "); + show_function_parameter_declaration(state, param); + } + } + + if state.output_cxx && (state.vector_mask & (1 << 31)) != 0 { + if !fp.parameters.is_empty() { + state.write(", "); + } + state.write("I32 _cond_mask_"); + } + + state.write(")"); +} + +pub fn show_function_parameter_declaration( + state: &mut OutputState, + p: &hir::FunctionParameterDeclaration, +) { + match *p { + hir::FunctionParameterDeclaration::Named(ref qual, ref fpd) => { + if state.output_cxx { + let is_scalar = state.is_scalar.replace( + symbol_run_class(&state.hir.sym(fpd.sym).decl, state.vector_mask) + == hir::RunClass::Scalar, + ); + show_type(state, &fpd.ty); + state.is_scalar.set(is_scalar); + show_parameter_qualifier(state, qual); + } else { + show_parameter_qualifier(state, qual); + state.write(" "); + show_type(state, &fpd.ty); + } + state.write(" "); + show_identifier_and_type(state, &fpd.name, &fpd.ty); + } + hir::FunctionParameterDeclaration::Unnamed(ref qual, ref ty) => { + if state.output_cxx { + show_type_specifier(state, ty); + show_parameter_qualifier(state, qual); + } else { + show_parameter_qualifier(state, qual); + state.write(" "); + show_type_specifier(state, ty); + } + } + } +} + +pub fn show_init_declarator_list(state: &mut OutputState, i: &hir::InitDeclaratorList) { + show_single_declaration(state, &i.head); + + for decl in &i.tail { + state.write(", "); + show_single_declaration_no_type(state, decl); + } +} + +pub fn show_single_declaration(state: &mut OutputState, d: &hir::SingleDeclaration) { + if state.output_cxx { + show_single_declaration_cxx(state, d) + } else { + show_single_declaration_glsl(state, d) + } +} + +pub fn show_single_declaration_glsl(state: &mut OutputState, d: &hir::SingleDeclaration) { + if let Some(ref qual) = d.qualifier { + show_type_qualifier(state, &qual); + state.write(" "); + } + + let sym = state.hir.sym(d.name); + match &sym.decl { + hir::SymDecl::Global(storage, interpolation, ..) => { + show_storage_class(state, storage); + if let Some(i) = interpolation { + show_interpolation_qualifier(state, i); + } + } + hir::SymDecl::Local(storage, ..) => show_storage_class(state, storage), + _ => panic!("should be variable"), + } + + if let Some(ty_def) = d.ty_def { + show_sym_decl(state, &ty_def); + } else { + show_type(state, &d.ty); + } + + state.write(" "); + state.write(sym.name.as_str()); + + if let Some(ref arr_spec) = d.ty.array_sizes { + show_array_sizes(state, &arr_spec); + } + + if let Some(ref initializer) = d.initializer { + state.write(" = "); + show_initializer(state, initializer); + } +} + +fn symbol_run_class(decl: &hir::SymDecl, vector_mask: u32) -> hir::RunClass { + let run_class = match decl { + hir::SymDecl::Global(_, _, _, run_class) => *run_class, + hir::SymDecl::Local(_, _, run_class) => *run_class, + _ => hir::RunClass::Vector, + }; + match run_class { + hir::RunClass::Scalar => hir::RunClass::Scalar, + hir::RunClass::Dependent(mask) => { + if (mask & vector_mask) != 0 { + hir::RunClass::Vector + } else { + hir::RunClass::Scalar + } + } + _ => hir::RunClass::Vector, + } +} + +pub fn show_single_declaration_cxx(state: &mut OutputState, d: &hir::SingleDeclaration) { + let sym = state.hir.sym(d.name); + match &sym.decl { + hir::SymDecl::Global(hir::StorageClass::Sampler(format), ..) => { + write!( + state, + "{}{} {}", + d.ty.kind.cxx_primitive_type_name().unwrap(), + format.type_suffix().unwrap_or(""), + sym.name.as_str() + ); + return; + } + _ => {} + } + if state.kind == ShaderKind::Fragment { + match &sym.decl { + hir::SymDecl::Global(hir::StorageClass::FragColor(index), ..) => { + let fragcolor = match index { + 0 => "gl_FragColor", + 1 => "gl_SecondaryFragColor", + _ => panic!(), + }; + write!(state, "#define {} {}\n", sym.name, fragcolor); + show_indent(state); + state.write("// "); + } + hir::SymDecl::Global(hir::StorageClass::Out, ..) => { + write!(state, "#define {} gl_FragColor\n", sym.name); + show_indent(state); + state.write("// "); + } + _ => {} + } + } + let is_scalar = state + .is_scalar + .replace(symbol_run_class(&sym.decl, state.vector_mask) == hir::RunClass::Scalar); + + if let Some(ref _array) = d.ty.array_sizes { + show_type(state, &d.ty); + } else { + if let Some(ty_def) = d.ty_def { + show_sym_decl(state, &ty_def); + } else { + show_type(state, &d.ty); + } + } + + // XXX: this is pretty grotty + state.write(" "); + show_sym_decl(state, &d.name); + + state.is_scalar.set(is_scalar); + + if let Some(ref initializer) = d.initializer { + state.write(" = "); + show_initializer(state, initializer); + } +} + +pub fn show_single_declaration_no_type(state: &OutputState, d: &hir::SingleDeclarationNoType) { + show_arrayed_identifier(state, &d.ident); + + if let Some(ref initializer) = d.initializer { + state.write(" = "); + show_initializer(state, initializer); + } +} + +pub fn show_initializer(state: &OutputState, i: &hir::Initializer) { + match *i { + hir::Initializer::Simple(ref e) => show_hir_expr(state, e), + hir::Initializer::List(ref list) => { + let mut iter = list.0.iter(); + let first = iter.next().unwrap(); + + state.write("{ "); + show_initializer(state, first); + + for ini in iter { + state.write(", "); + show_initializer(state, ini); + } + + state.write(" }"); + } + } +} + +/* +pub fn show_block(state: &mut OutputState, b: &hir::Block) { + show_type_qualifier(state, &b.qualifier); + state.write(" "); + show_identifier(state, &b.name); + state.write(" {"); + + for field in &b.fields { + show_struct_field(state, field); + state.write("\n"); + } + state.write("}"); + + if let Some(ref ident) = b.identifier { + show_arrayed_identifier(state, ident); + } +} +*/ + +// This is a hack to run through the first time with an empty writter to find if 'return' is declared. +pub fn has_conditional_return(state: &mut OutputState, cst: &hir::CompoundStatement) -> bool { + let buffer = state.push_buffer(); + show_compound_statement(state, cst); + state.pop_buffer(buffer); + let result = state.return_declared; + state.return_declared = false; + result +} + +fn define_texel_fetch_ptr( + state: &OutputState, + base_sym: &hir::Symbol, + sampler_sym: &hir::Symbol, + offsets: &hir::TexelFetchOffsets, +) { + show_indent(state); + if let hir::SymDecl::Global(_, _, ty, _) = &sampler_sym.decl { + match ty.kind { + hir::TypeKind::Sampler2D => { + write!( + state, + "vec4_scalar* {}_{}_fetch = ", + sampler_sym.name, base_sym.name + ); + } + hir::TypeKind::ISampler2D => { + write!( + state, + "ivec4_scalar* {}_{}_fetch = ", + sampler_sym.name, base_sym.name + ); + } + _ => panic!(), + } + } else { + panic!(); + } + write!( + state, + "texelFetchPtr({}, {}, {}, {}, {}, {});\n", + sampler_sym.name, base_sym.name, offsets.min_x, offsets.max_x, offsets.min_y, offsets.max_y + ); +} + +pub fn show_function_definition( + state: &mut OutputState, + fd: &hir::FunctionDefinition, + vector_mask: u32, +) { + // println!("start {:?} {:?}", fd.prototype.name, vector_mask); + if state.output_cxx && fd.prototype.name.as_str() == "main" { + state.write("ALWAYS_INLINE "); + } + show_function_prototype(state, &fd.prototype); + state.write(" "); + state.return_type = Some(Box::new(fd.prototype.ty.clone())); + + if state.output_cxx && (vector_mask & (1 << 31)) != 0 { + state.mask = Some(Box::new(hir::Expr { + kind: hir::ExprKind::CondMask, + ty: hir::Type::new(hir::TypeKind::Bool), + })); + } + + show_indent(state); + state.write("{\n"); + + state.indent(); + if has_conditional_return(state, &fd.body) { + show_indent(state); + state.write(if state.return_vector { + "I32" + } else { + "int32_t" + }); + state.write(" ret_mask = "); + if let Some(mask) = &state.mask { + show_hir_expr(state, mask); + } else { + state.write("~0"); + } + state.write(";\n"); + // XXX: the cloning here is bad + show_indent(state); + if fd.prototype.ty != Type::new(hir::TypeKind::Void) { + let is_scalar = state.is_scalar.replace(!state.return_vector); + show_type(state, &state.return_type.clone().unwrap()); + state.write(" ret;\n"); + state.is_scalar.set(is_scalar); + } + } + + if state.output_cxx { + let mut texel_fetches = state.texel_fetches.borrow_mut(); + texel_fetches.clear(); + for ((sampler, base), offsets) in fd.texel_fetches.iter() { + let base_sym = state.hir.sym(*base); + if symbol_run_class(&base_sym.decl, vector_mask) == hir::RunClass::Scalar { + add_used_global(state, sampler); + let sampler_sym = state.hir.sym(*sampler); + match &base_sym.decl { + hir::SymDecl::Global(..) => { + add_used_global(state, base); + define_texel_fetch_ptr(state, &base_sym, &sampler_sym, &offsets); + } + hir::SymDecl::Local(..) => { + if fd.prototype.has_parameter(*base) { + define_texel_fetch_ptr(state, &base_sym, &sampler_sym, &offsets); + } else { + texel_fetches.push((*sampler, *base, offsets.clone())); + } + } + _ => panic!(), + } + } + } + } + + for st in &fd.body.statement_list { + show_statement(state, st); + } + + if state.return_declared { + show_indent(state); + if fd.prototype.ty == Type::new(hir::TypeKind::Void) { + state.write("return;\n"); + } else { + state.write("return ret;\n"); + } + } + state.outdent(); + + show_indent(state); + state.write("}\n"); + // println!("end {:?}", fd.prototype.name); + + state.return_type = None; + state.return_declared = false; + state.mask = None; +} + +pub fn show_compound_statement(state: &mut OutputState, cst: &hir::CompoundStatement) { + show_indent(state); + state.write("{\n"); + + state.indent(); + for st in &cst.statement_list { + show_statement(state, st); + } + state.outdent(); + + show_indent(state); + state.write("}\n"); +} + +pub fn show_statement(state: &mut OutputState, st: &hir::Statement) { + match *st { + hir::Statement::Compound(ref cst) => show_compound_statement(state, cst), + hir::Statement::Simple(ref sst) => show_simple_statement(state, sst), + } +} + +pub fn show_simple_statement(state: &mut OutputState, sst: &hir::SimpleStatement) { + match *sst { + hir::SimpleStatement::Declaration(ref d) => show_declaration(state, d), + hir::SimpleStatement::Expression(ref e) => show_expression_statement(state, e), + hir::SimpleStatement::Selection(ref s) => show_selection_statement(state, s), + hir::SimpleStatement::Switch(ref s) => show_switch_statement(state, s), + hir::SimpleStatement::Iteration(ref i) => show_iteration_statement(state, i), + hir::SimpleStatement::Jump(ref j) => show_jump_statement(state, j), + } +} + +pub fn show_indent(state: &OutputState) { + for _ in 0 .. state.indent { + state.write(" "); + } +} + +pub fn show_expression_statement(state: &mut OutputState, est: &hir::ExprStatement) { + show_indent(state); + + if let Some(ref e) = *est { + show_hir_expr_inner(state, e, true); + } + + state.write(";\n"); +} + +pub fn show_selection_statement(state: &mut OutputState, sst: &hir::SelectionStatement) { + show_indent(state); + + if state.output_cxx && + (state.return_declared || expr_run_class(state, &sst.cond) != hir::RunClass::Scalar) + { + let (cond_index, mask) = if state.mask.is_none() || sst.else_stmt.is_some() { + let cond = sst.cond.clone(); + state.cond_index += 1; + let cond_index = state.cond_index; + write!(state, "auto _c{}_ = ", cond_index); + show_hir_expr(state, &cond); + state.write(";\n"); + ( + cond_index, + Box::new(hir::Expr { + kind: hir::ExprKind::Cond(cond_index, cond), + ty: hir::Type::new(hir::TypeKind::Bool), + }), + ) + } else { + (0, sst.cond.clone()) + }; + + let previous = mem::replace(&mut state.mask, None); + state.mask = Some(match previous.clone() { + Some(e) => { + let cond = Box::new(hir::Expr { + kind: hir::ExprKind::Binary(syntax::BinaryOp::BitAnd, e, mask.clone()), + ty: hir::Type::new(hir::TypeKind::Bool), + }); + state.cond_index += 1; + let nested_cond_index = state.cond_index; + show_indent(state); + write!(state, "auto _c{}_ = ", nested_cond_index); + show_hir_expr(state, &cond); + state.write(";\n"); + Box::new(hir::Expr { + kind: hir::ExprKind::Cond(nested_cond_index, cond), + ty: hir::Type::new(hir::TypeKind::Bool), + }) + } + None => mask.clone(), + }); + + show_statement(state, &sst.body); + state.mask = previous; + + if let Some(rest) = &sst.else_stmt { + // invert the condition + let inverted_cond = Box::new(hir::Expr { + kind: hir::ExprKind::Unary(UnaryOp::Complement, mask), + ty: hir::Type::new(hir::TypeKind::Bool), + }); + let previous = mem::replace(&mut state.mask, None); + state.mask = Some(match previous.clone() { + Some(e) => { + let cond = Box::new(hir::Expr { + kind: hir::ExprKind::Binary(syntax::BinaryOp::BitAnd, e, inverted_cond), + ty: hir::Type::new(hir::TypeKind::Bool), + }); + show_indent(state); + write!(state, "_c{}_ = ", cond_index); + show_hir_expr(state, &cond); + state.write(";\n"); + Box::new(hir::Expr { + kind: hir::ExprKind::Cond(cond_index, cond), + ty: hir::Type::new(hir::TypeKind::Bool), + }) + } + None => inverted_cond, + }); + + show_statement(state, rest); + state.mask = previous; + } + } else { + state.write("if ("); + show_hir_expr(state, &sst.cond); + state.write(") {\n"); + + state.indent(); + show_statement(state, &sst.body); + state.outdent(); + + show_indent(state); + if let Some(rest) = &sst.else_stmt { + state.write("} else "); + show_statement(state, rest); + } else { + state.write("}\n"); + } + } +} + +fn case_stmts_to_if_stmts(stmts: &[Statement], last: bool) -> (Option>, bool) { + // Look for jump statements and remove them + // We currently are pretty strict on the form that the statement + // list needs to be in. This can be loosened as needed. + let mut fallthrough = false; + let cstmt = match &stmts[..] { + [hir::Statement::Compound(c)] => match c.statement_list.split_last() { + Some((hir::Statement::Simple(s), rest)) => match **s { + hir::SimpleStatement::Jump(hir::JumpStatement::Break) => hir::CompoundStatement { + statement_list: rest.to_owned(), + }, + _ => panic!("fall through not supported"), + }, + _ => panic!("empty compound"), + }, + [hir::Statement::Simple(s)] => { + match **s { + hir::SimpleStatement::Jump(hir::JumpStatement::Break) => hir::CompoundStatement { + statement_list: Vec::new(), + }, + _ => { + if last { + // we don't need a break at the end + hir::CompoundStatement { + statement_list: vec![hir::Statement::Simple(s.clone())], + } + } else { + panic!("fall through not supported {:?}", s) + } + } + } + } + [] => return (None, true), + stmts => match stmts.split_last() { + Some((hir::Statement::Simple(s), rest)) => match **s { + hir::SimpleStatement::Jump(hir::JumpStatement::Break) => hir::CompoundStatement { + statement_list: rest.to_owned(), + }, + _ => { + if !last { + fallthrough = true; + } + hir::CompoundStatement { + statement_list: stmts.to_owned(), + } + } + }, + _ => panic!("unexpected empty"), + }, + }; + let stmts = Box::new(hir::Statement::Compound(Box::new(cstmt))); + (Some(stmts), fallthrough) +} + +fn build_selection<'a, I: Iterator>( + head: &Box, + case: &hir::Case, + mut cases: I, + default: Option<&hir::Case>, + previous_condition: Option>, + previous_stmts: Option>, +) -> hir::SelectionStatement { + let cond = match &case.label { + hir::CaseLabel::Case(e) => Some(Box::new(hir::Expr { + kind: hir::ExprKind::Binary(syntax::BinaryOp::Equal, head.clone(), e.clone()), + ty: hir::Type::new(hir::TypeKind::Bool), + })), + hir::CaseLabel::Def => None, + }; + + // if we have two conditions join them + let cond = match (&previous_condition, &cond) { + (Some(prev), Some(cond)) => Some(Box::new(hir::Expr { + kind: hir::ExprKind::Binary(syntax::BinaryOp::Or, prev.clone(), cond.clone()), + ty: hir::Type::new(hir::TypeKind::Bool), + })), + (_, cond) => cond.clone(), + }; + + /* + + // find the next case that's not a default + let next_case = loop { + match cases.next() { + Some(hir::Case { label: hir::CaseLabel::Def, ..}) => { }, + case => break case, + } + };*/ + + let (cond, body, else_stmt) = match (cond, cases.next()) { + (None, Some(next_case)) => { + assert!(previous_stmts.is_none()); + // default so just move on to the next + return build_selection(head, next_case, cases, default, None, None); + } + (Some(cond), Some(next_case)) => { + assert!(previous_stmts.is_none()); + let (stmts, fallthrough) = case_stmts_to_if_stmts(&case.stmts, false); + if !fallthrough && stmts.is_some() { + ( + cond, + stmts.unwrap(), + Some(Box::new(hir::Statement::Simple(Box::new( + hir::SimpleStatement::Selection(build_selection( + head, next_case, cases, default, None, None, + )), + )))), + ) + } else { + // empty so fall through to the next + return build_selection(head, next_case, cases, default, Some(cond), stmts); + } + } + (Some(cond), None) => { + // non-default last + assert!(previous_stmts.is_none()); + let (stmts, _) = case_stmts_to_if_stmts(&case.stmts, default.is_none()); + let stmts = stmts.expect("empty case labels unsupported at the end"); + // add the default case at the end if we have one + ( + cond, + stmts, + match default { + Some(default) => { + let (default_stmts, fallthrough) = + case_stmts_to_if_stmts(&default.stmts, true); + assert!(!fallthrough); + Some(default_stmts.expect("empty default unsupported")) + } + None => None, + }, + ) + } + (None, None) => { + // default, last + + assert!(default.is_some()); + + let (stmts, fallthrough) = case_stmts_to_if_stmts(&case.stmts, true); + let stmts = stmts.expect("empty default unsupported"); + assert!(!fallthrough); + + match previous_stmts { + Some(previous_stmts) => { + let cond = previous_condition.expect("must have previous condition"); + (cond, previous_stmts, Some(stmts)) + } + None => { + let cond = Box::new(hir::Expr { + kind: hir::ExprKind::BoolConst(true), + ty: hir::Type::new(hir::TypeKind::Bool), + }); + (cond, stmts, None) + } + } + } + }; + + hir::SelectionStatement { + cond, + body, + else_stmt, + } +} + +pub fn lower_switch_to_ifs(sst: &hir::SwitchStatement) -> hir::SelectionStatement { + let default = sst.cases.iter().find(|x| x.label == hir::CaseLabel::Def); + let mut cases = sst.cases.iter(); + let r = build_selection(&sst.head, cases.next().unwrap(), cases, default, None, None); + r +} + +fn is_declaration(stmt: &hir::Statement) -> bool { + if let hir::Statement::Simple(s) = stmt { + if let hir::SimpleStatement::Declaration(..) = **s { + return true; + } + } + return false; +} + +pub fn show_switch_statement(state: &mut OutputState, sst: &hir::SwitchStatement) { + if state.output_cxx && expr_run_class(state, &sst.head) != hir::RunClass::Scalar { + // XXX: when lowering switches we end up with a mask that has + // a bunch of mutually exclusive conditions. + // It would be nice if we could fold them together. + let ifs = lower_switch_to_ifs(sst); + return show_selection_statement(state, &ifs); + } + + show_indent(state); + state.write("switch ("); + show_hir_expr(state, &sst.head); + state.write(") {\n"); + state.indent(); + + for case in &sst.cases { + show_case_label(state, &case.label); + state.indent(); + + let has_declaration = case.stmts.iter().any(|x| is_declaration(x)); + // glsl allows declarations in switch statements while C requires them to be + // in a compound statement. If we have a declaration wrap the statements in an block. + // This will break some glsl shaders but keeps the saner ones working + if has_declaration { + show_indent(state); + state.write("{\n"); + state.indent(); + } + for st in &case.stmts { + show_statement(state, st); + } + + if has_declaration { + show_indent(state); + state.write("}\n"); + state.outdent(); + } + + state.outdent(); + } + state.outdent(); + show_indent(state); + state.write("}\n"); +} + +pub fn show_case_label(state: &mut OutputState, cl: &hir::CaseLabel) { + show_indent(state); + match *cl { + hir::CaseLabel::Case(ref e) => { + state.write("case "); + show_hir_expr(state, e); + state.write(":\n"); + } + hir::CaseLabel::Def => { + state.write("default:\n"); + } + } +} + +pub fn show_iteration_statement(state: &mut OutputState, ist: &hir::IterationStatement) { + show_indent(state); + match *ist { + hir::IterationStatement::While(ref cond, ref body) => { + state.write("while ("); + show_condition(state, cond); + state.write(") "); + show_statement(state, body); + } + hir::IterationStatement::DoWhile(ref body, ref cond) => { + state.write("do "); + show_statement(state, body); + state.write(" while ("); + show_hir_expr(state, cond); + state.write(")\n"); + } + hir::IterationStatement::For(ref init, ref rest, ref body) => { + state.write("for ("); + show_for_init_statement(state, init); + show_for_rest_statement(state, rest); + state.write(") "); + show_statement(state, body); + } + } +} + +pub fn show_condition(state: &mut OutputState, c: &hir::Condition) { + match *c { + hir::Condition::Expr(ref e) => show_hir_expr(state, e), + /*hir::Condition::Assignment(ref ty, ref name, ref initializer) => { + show_type(state, ty); + state.write(" "); + show_identifier(f, name); + state.write(" = "); + show_initializer(state, initializer); + }*/ + } +} + +pub fn show_for_init_statement(state: &mut OutputState, i: &hir::ForInitStatement) { + match *i { + hir::ForInitStatement::Expression(ref expr) => { + if let Some(ref e) = *expr { + show_hir_expr(state, e); + } + } + hir::ForInitStatement::Declaration(ref d) => { + show_declaration(state, d); + } + } +} + +pub fn show_for_rest_statement(state: &mut OutputState, r: &hir::ForRestStatement) { + if let Some(ref cond) = r.condition { + show_condition(state, cond); + } + + state.write("; "); + + if let Some(ref e) = r.post_expr { + show_hir_expr(state, e); + } +} + +fn use_return_mask(state: &OutputState) -> bool { + if let Some(mask) = &state.mask { + mask.kind != hir::ExprKind::CondMask + } else { + false + } +} + +pub fn show_jump_statement(state: &mut OutputState, j: &hir::JumpStatement) { + show_indent(state); + match *j { + hir::JumpStatement::Continue => { + state.write("continue;\n"); + } + hir::JumpStatement::Break => { + state.write("break;\n"); + } + hir::JumpStatement::Discard => { + if state.output_cxx { + state.uses_discard = true; + if let Some(mask) = &state.mask { + state.write("isPixelDiscarded |= ("); + show_hir_expr(state, mask); + state.write(")"); + if state.return_declared { + state.write("&ret_mask"); + } + state.write(";\n"); + } else { + state.write("isPixelDiscarded = true;\n"); + } + } else { + state.write("discard;\n"); + } + } + hir::JumpStatement::Return(ref e) => { + if let Some(e) = e { + if state.output_cxx { + if use_return_mask(state) { + // We cast any conditions by `ret_mask_type` so that scalars nicely + // convert to -1. i.e. I32 &= bool will give the wrong result. while I32 &= I32(bool) works + let ret_mask_type = if state.return_vector { + "I32" + } else { + "int32_t" + }; + if state.return_declared { + // XXX: the cloning here is bad + write!(state, "ret = if_then_else(ret_mask & {}(", ret_mask_type); + show_hir_expr(state, &state.mask.clone().unwrap()); + state.write("), "); + show_hir_expr(state, e); + state.write(", ret);\n"); + } else { + state.write("ret = "); + show_hir_expr(state, e); + state.write(";\n"); + } + + show_indent(state); + + if state.return_declared { + write!(state, "ret_mask &= ~{}(", ret_mask_type); + } else { + write!(state, "ret_mask = ~{}(", ret_mask_type); + } + show_hir_expr(state, &state.mask.clone().unwrap()); + state.write(");\n"); + state.return_declared = true; + } else { + if state.return_declared { + state.write("ret = if_then_else(ret_mask, "); + show_hir_expr(state, e); + state.write(", ret);\n"); + } else { + state.write("return "); + show_hir_expr(state, e); + state.write(";\n"); + } + } + } else { + state.write("return "); + show_hir_expr(state, e); + state.write(";\n"); + } + } else { + if state.output_cxx { + if use_return_mask(state) { + show_indent(state); + let ret_mask_type = if state.return_vector { + "I32" + } else { + "int32_t" + }; + if state.return_declared { + write!(state, "ret_mask &= ~{}(", ret_mask_type); + } else { + write!(state, "ret_mask = ~{}(", ret_mask_type); + } + show_hir_expr(state, &state.mask.clone().unwrap()); + state.write(");\n"); + state.return_declared = true; + } else { + state.write("return;\n"); + } + } else { + state.write("return;\n"); + } + } + } + } +} + +pub fn show_path(state: &OutputState, path: &syntax::Path) { + match path { + syntax::Path::Absolute(s) => { + let _ = write!(state, "<{}>", s); + } + syntax::Path::Relative(s) => { + let _ = write!(state, "\"{}\"", s); + } + } +} + +pub fn show_preprocessor(state: &OutputState, pp: &syntax::Preprocessor) { + match *pp { + syntax::Preprocessor::Define(ref pd) => show_preprocessor_define(state, pd), + syntax::Preprocessor::Else => show_preprocessor_else(state), + syntax::Preprocessor::ElseIf(ref pei) => show_preprocessor_elseif(state, pei), + syntax::Preprocessor::EndIf => show_preprocessor_endif(state), + syntax::Preprocessor::Error(ref pe) => show_preprocessor_error(state, pe), + syntax::Preprocessor::If(ref pi) => show_preprocessor_if(state, pi), + syntax::Preprocessor::IfDef(ref pid) => show_preprocessor_ifdef(state, pid), + syntax::Preprocessor::IfNDef(ref pind) => show_preprocessor_ifndef(state, pind), + syntax::Preprocessor::Include(ref pi) => show_preprocessor_include(state, pi), + syntax::Preprocessor::Line(ref pl) => show_preprocessor_line(state, pl), + syntax::Preprocessor::Pragma(ref pp) => show_preprocessor_pragma(state, pp), + syntax::Preprocessor::Undef(ref pu) => show_preprocessor_undef(state, pu), + syntax::Preprocessor::Version(ref pv) => show_preprocessor_version(state, pv), + syntax::Preprocessor::Extension(ref pe) => show_preprocessor_extension(state, pe), + } +} + +pub fn show_preprocessor_define(state: &OutputState, pd: &syntax::PreprocessorDefine) { + match *pd { + syntax::PreprocessorDefine::ObjectLike { + ref ident, + ref value, + } => { + let _ = write!(state, "#define {} {}\n", ident, value); + } + + syntax::PreprocessorDefine::FunctionLike { + ref ident, + ref args, + ref value, + } => { + let _ = write!(state, "#define {}(", ident); + + if !args.is_empty() { + let _ = write!(state, "{}", &args[0]); + + for arg in &args[1 .. args.len()] { + let _ = write!(state, ", {}", arg); + } + } + + let _ = write!(state, ") {}\n", value); + } + } +} + +pub fn show_preprocessor_else(state: &OutputState) { + state.write("#else\n"); +} + +pub fn show_preprocessor_elseif(state: &OutputState, pei: &syntax::PreprocessorElseIf) { + let _ = write!(state, "#elseif {}\n", pei.condition); +} + +pub fn show_preprocessor_error(state: &OutputState, pe: &syntax::PreprocessorError) { + let _ = writeln!(state, "#error {}", pe.message); +} + +pub fn show_preprocessor_endif(state: &OutputState) { + state.write("#endif\n"); +} + +pub fn show_preprocessor_if(state: &OutputState, pi: &syntax::PreprocessorIf) { + let _ = write!(state, "#if {}\n", pi.condition); +} + +pub fn show_preprocessor_ifdef(state: &OutputState, pid: &syntax::PreprocessorIfDef) { + state.write("#ifdef "); + show_identifier(state, &pid.ident); + state.write("\n"); +} + +pub fn show_preprocessor_ifndef(state: &OutputState, pind: &syntax::PreprocessorIfNDef) { + state.write("#ifndef "); + show_identifier(state, &pind.ident); + state.write("\n"); +} + +pub fn show_preprocessor_include(state: &OutputState, pi: &syntax::PreprocessorInclude) { + state.write("#include "); + show_path(state, &pi.path); + state.write("\n"); +} + +pub fn show_preprocessor_line(state: &OutputState, pl: &syntax::PreprocessorLine) { + let _ = write!(state, "#line {}", pl.line); + if let Some(source_string_number) = pl.source_string_number { + let _ = write!(state, " {}", source_string_number); + } + state.write("\n"); +} + +pub fn show_preprocessor_pragma(state: &OutputState, pp: &syntax::PreprocessorPragma) { + let _ = writeln!(state, "#pragma {}", pp.command); +} + +pub fn show_preprocessor_undef(state: &OutputState, pud: &syntax::PreprocessorUndef) { + state.write("#undef "); + show_identifier(state, &pud.name); + state.write("\n"); +} + +pub fn show_preprocessor_version(state: &OutputState, pv: &syntax::PreprocessorVersion) { + let _ = write!(state, "#version {}", pv.version); + + if let Some(ref profile) = pv.profile { + match *profile { + syntax::PreprocessorVersionProfile::Core => { + state.write(" core"); + } + syntax::PreprocessorVersionProfile::Compatibility => { + state.write(" compatibility"); + } + syntax::PreprocessorVersionProfile::ES => { + state.write(" es"); + } + } + } + + state.write("\n"); +} + +pub fn show_preprocessor_extension(state: &OutputState, pe: &syntax::PreprocessorExtension) { + state.write("#extension "); + + match pe.name { + syntax::PreprocessorExtensionName::All => { + state.write("all"); + } + syntax::PreprocessorExtensionName::Specific(ref n) => { + state.write(n); + } + } + + if let Some(ref behavior) = pe.behavior { + match *behavior { + syntax::PreprocessorExtensionBehavior::Require => { + state.write(" : require"); + } + syntax::PreprocessorExtensionBehavior::Enable => { + state.write(" : enable"); + } + syntax::PreprocessorExtensionBehavior::Warn => { + state.write(" : warn"); + } + syntax::PreprocessorExtensionBehavior::Disable => { + state.write(" : disable"); + } + } + } + + state.write("\n"); +} + +pub fn show_external_declaration(state: &mut OutputState, ed: &hir::ExternalDeclaration) { + match *ed { + hir::ExternalDeclaration::Preprocessor(ref pp) => { + if !state.output_cxx { + show_preprocessor(state, pp) + } + } + hir::ExternalDeclaration::FunctionDefinition(ref fd) => { + if !state.output_cxx { + show_function_definition(state, fd, !0) + } + } + hir::ExternalDeclaration::Declaration(ref d) => show_declaration(state, d), + } +} + +pub fn show_cxx_function_definition(state: &mut OutputState, name: hir::SymRef, vector_mask: u32) { + if let Some((ref fd, run_class)) = state.hir.function_definition(name) { + state.vector_mask = vector_mask; + state.return_vector = (vector_mask & (1 << 31)) != 0 + || match run_class { + hir::RunClass::Scalar => false, + hir::RunClass::Dependent(mask) => (mask & vector_mask) != 0, + _ => true, + }; + match state.functions.get(&(name, vector_mask)) { + Some(true) => {} + Some(false) => { + show_function_prototype(state, &fd.prototype); + state.functions.insert((name, vector_mask), true); + } + None => { + state.functions.insert((name, vector_mask), false); + let buffer = state.push_buffer(); + show_function_definition(state, fd, vector_mask); + for (name, vector_mask) in state.deps.replace(Vec::new()) { + show_cxx_function_definition(state, name, vector_mask); + } + state.flush_buffer(); + state.pop_buffer(buffer); + state.functions.insert((name, vector_mask), true); + } + } + } +} + +pub fn show_translation_unit(state: &mut OutputState, tu: &hir::TranslationUnit) { + state.flush_buffer(); + + for ed in &(tu.0).0 { + show_external_declaration(state, ed); + state.flush_buffer(); + } + if state.output_cxx { + if let Some(name) = state.hir.lookup("main") { + show_cxx_function_definition(state, name, 0); + state.flush_buffer(); + } + } +} + +fn write_abi(state: &mut OutputState) { + match state.kind { + ShaderKind::Fragment => { + state.write("static bool use_discard(Self*) { return "); + state.write(if state.uses_discard { "true" } else { "false" }); + state.write("; }\n"); + state.write("static void run(Self *self) {\n"); + if state.uses_discard { + state.write(" self->isPixelDiscarded = false;\n"); + } + state.write(" self->main();\n"); + state.write(" self->step_interp_inputs();\n"); + state.write("}\n"); + state.write("static void skip(Self* self, int chunks) {\n"); + state.write(" self->step_interp_inputs();\n"); + state.write(" while (--chunks > 0) self->step_interp_inputs();\n"); + state.write("}\n"); + if state.has_draw_span_rgba8 { + state.write( + "static void draw_span_RGBA8(Self* self, uint32_t* buf, int len) {\ + dispatch_draw_span(self, buf, len); }\n"); + } + if state.has_draw_span_r8 { + state.write( + "static void draw_span_R8(Self* self, uint8_t* buf, int len) {\ + dispatch_draw_span(self, buf, len); }\n"); + } + + write!(state, "{}_frag() {{\n", state.name); + } + ShaderKind::Vertex => { + state.write( + "static void run(Self* self, char* flats, char* interps, size_t interp_stride) {\n", + ); + state.write(" self->main();\n"); + state.write(" self->store_flat_outputs(flats);\n"); + state.write(" self->store_interp_outputs(interps, interp_stride);\n"); + state.write("}\n"); + + write!(state, "{}_vert() {{\n", state.name); + } + } + state.write(" set_uniform_1i_func = (SetUniform1iFunc)&set_uniform_1i;\n"); + state.write(" set_uniform_4fv_func = (SetUniform4fvFunc)&set_uniform_4fv;\n"); + state.write(" set_uniform_matrix4fv_func = (SetUniformMatrix4fvFunc)&set_uniform_matrix4fv;\n"); + match state.kind { + ShaderKind::Fragment => { + state.write(" init_batch_func = (InitBatchFunc)&bind_textures;\n"); + state.write(" init_primitive_func = (InitPrimitiveFunc)&read_flat_inputs;\n"); + state.write(" init_span_func = (InitSpanFunc)&read_interp_inputs;\n"); + state.write(" run_func = (RunFunc)&run;\n"); + state.write(" skip_func = (SkipFunc)&skip;\n"); + state.write(" use_discard_func = (UseDiscardFunc)&use_discard;\n"); + if state.has_draw_span_rgba8 { + state.write(" draw_span_RGBA8_func = (DrawSpanRGBA8Func)&draw_span_RGBA8;\n"); + } + if state.has_draw_span_r8 { + state.write(" draw_span_R8_func = (DrawSpanR8Func)&draw_span_R8;\n"); + } + } + ShaderKind::Vertex => { + state.write(" init_batch_func = (InitBatchFunc)&bind_textures;\n"); + state.write(" load_attribs_func = (LoadAttribsFunc)&load_attribs;\n"); + state.write(" run_func = (RunFunc)&run;\n"); + } + } + state.write("}\n"); +} + +pub fn define_global_consts(state: &mut OutputState, tu: &hir::TranslationUnit, part_name: &str) { + for i in tu { + match i { + hir::ExternalDeclaration::Declaration(hir::Declaration::InitDeclaratorList(ref d)) => { + let sym = state.hir.sym(d.head.name); + match &sym.decl { + hir::SymDecl::Global(hir::StorageClass::Const, ..) => { + let is_scalar = state.is_scalar.replace( + symbol_run_class(&sym.decl, state.vector_mask) == hir::RunClass::Scalar, + ); + if let Some(ref _array) = d.head.ty.array_sizes { + show_type(state, &d.head.ty); + } else { + if let Some(ty_def) = d.head.ty_def { + show_sym_decl(state, &ty_def); + } else { + show_type(state, &d.head.ty); + } + } + write!(state, " constexpr {}::{};\n", part_name, sym.name); + state.is_scalar.set(is_scalar); + } + _ => {} + } + } + _ => {} + } + } +} diff --git a/glsl-to-cxx/src/main.rs b/glsl-to-cxx/src/main.rs new file mode 100644 index 0000000000..e40262c84e --- /dev/null +++ b/glsl-to-cxx/src/main.rs @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use glsl_to_cxx::translate; +fn main() { + println!("{}", translate(&mut std::env::args())); +} diff --git a/servo-tidy.toml b/servo-tidy.toml index 84ec80db86..b45489f8d5 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -9,7 +9,6 @@ packages = [ "core-graphics", "core-text", "gl_generator", - "lazy_static", "percent-encoding", "rand", "rand_core", diff --git a/swgl/Cargo.toml b/swgl/Cargo.toml new file mode 100644 index 0000000000..55df40ccba --- /dev/null +++ b/swgl/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "swgl" +version = "0.1.0" +license = "MPL-2.0" +authors = ["The Mozilla Project Developers"] +build = "build.rs" +description = "Software OpenGL implementation for WebRender." + +[build-dependencies] +cc = "1.0.46" +glsl-to-cxx = { path = "../glsl-to-cxx" } + +[dependencies] +gleam = "0.10.0" diff --git a/swgl/README.md b/swgl/README.md new file mode 100644 index 0000000000..158a415793 --- /dev/null +++ b/swgl/README.md @@ -0,0 +1,4 @@ +swgl +======== + +Software OpenGL implementation for WebRender diff --git a/swgl/build.rs b/swgl/build.rs new file mode 100644 index 0000000000..4766d952f7 --- /dev/null +++ b/swgl/build.rs @@ -0,0 +1,180 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate cc; +extern crate glsl_to_cxx; + +use std::collections::HashSet; +use std::fmt::Write; + +fn write_load_shader(shaders: &[&str]) { + let mut load_shader = String::new(); + for s in shaders { + let _ = write!(load_shader, "#include \"{}.h\"\n", s); + } + load_shader.push_str("ProgramLoader load_shader(const char* name) {\n"); + for s in shaders { + let _ = write!(load_shader, " if (!strcmp(name, \"{}\")) {{ return {}_program::loader; }}\n", s, s); + } + load_shader.push_str(" return nullptr;\n}\n"); + std::fs::write(std::env::var("OUT_DIR").unwrap() + "/load_shader.h", load_shader).unwrap(); +} + +fn process_imports(shader_dir: &str, shader: &str, included: &mut HashSet, output: &mut String) { + if !included.insert(shader.into()) { + return; + } + println!("cargo:rerun-if-changed={}/{}.glsl", shader_dir, shader); + let source = std::fs::read_to_string(format!("{}/{}.glsl", shader_dir, shader)).unwrap(); + for line in source.lines() { + if line.starts_with("#include ") { + let imports = line["#include ".len() ..].split(','); + for import in imports { + process_imports(shader_dir, import, included, output); + } + } else if line.starts_with("#version ") || line.starts_with("#extension ") { + // ignore + } else { + output.push_str(line); + output.push('\n'); + } + } +} + +fn translate_shader(shader: &str, shader_dir: &str) { + let mut imported = String::new(); + imported.push_str("#define SWGL 1\n"); + imported.push_str("#define WR_MAX_VERTEX_TEXTURE_WIDTH 1024U\n"); + let basename = if let Some(feature_start) = shader.find(char::is_uppercase) { + let feature_end = shader.rfind(char::is_uppercase).unwrap(); + let features = shader[feature_start .. feature_end + 1].split('.'); + for feature in features { + let _ = write!(imported, "#define WR_FEATURE_{}\n", feature); + } + &shader[0..feature_start] + } else { + shader + }; + + process_imports(shader_dir, basename, &mut HashSet::new(), &mut imported); + + let out_dir = std::env::var("OUT_DIR").unwrap(); + let imp_name = format!("{}/{}.c", out_dir, shader); + std::fs::write(&imp_name, imported).unwrap(); + + let mut build = cc::Build::new(); + if build.get_compiler().is_like_msvc() { + build.flag("/EP"); + } else { + build.flag("-xc").flag("-P"); + } + build.file(&imp_name); + let vs = build.clone() + .define("WR_VERTEX_SHADER", Some("1")) + .expand(); + let fs = build.clone() + .define("WR_FRAGMENT_SHADER", Some("1")) + .expand(); + let vs_name = format!("{}/{}.vert", out_dir, shader); + let fs_name = format!("{}/{}.frag", out_dir, shader); + std::fs::write(&vs_name, vs).unwrap(); + std::fs::write(&fs_name, fs).unwrap(); + + let mut args = vec![ + "glsl_to_cxx".to_string(), + vs_name, + fs_name, + ]; + let frag_include = format!("{}/{}.frag.h", shader_dir, shader); + if std::path::Path::new(&frag_include).exists() { + println!("cargo:rerun-if-changed={}/{}.frag.h", shader_dir, shader); + args.push(frag_include); + } + let result = glsl_to_cxx::translate(&mut args.into_iter()); + std::fs::write(format!("{}/{}.h", out_dir, shader), result).unwrap(); +} + +const WR_SHADERS: &'static [&'static str] = &[ + "brush_blendALPHA_PASS", + "brush_blend", + "brush_imageALPHA_PASS", + "brush_image", + "brush_imageREPETITION_ANTIALIASING_ALPHA_PASS", + "brush_imageREPETITION_ANTIALIASING", + "brush_linear_gradientALPHA_PASS", + "brush_linear_gradientDITHERING_ALPHA_PASS", + "brush_linear_gradientDITHERING", + "brush_linear_gradient", + "brush_mix_blendALPHA_PASS", + "brush_mix_blend", + "brush_opacityALPHA_PASS", + "brush_radial_gradientALPHA_PASS", + "brush_radial_gradientDITHERING_ALPHA_PASS", + "brush_radial_gradientDITHERING", + "brush_radial_gradient", + "brush_solidALPHA_PASS", + "brush_solid", + "brush_yuv_image", + "brush_yuv_imageTEXTURE_2D_YUV_NV12", + "brush_yuv_imageYUV", + "brush_yuv_imageYUV_ALPHA_PASS", + "brush_yuv_imageYUV_INTERLEAVED", + "brush_yuv_imageYUV_NV12_ALPHA_PASS", + "brush_yuv_imageYUV_NV12", + "brush_yuv_imageYUV_PLANAR", + "composite", + "compositeYUV", + "cs_blurALPHA_TARGET", + "cs_blurCOLOR_TARGET", + "cs_border_segment", + "cs_border_solid", + "cs_clip_box_shadow", + "cs_clip_image", + "cs_clip_rectangleFAST_PATH", + "cs_clip_rectangle", + "cs_gradient", + "cs_line_decoration", + "cs_scale", + "cs_svg_filter", + "debug_color", + "debug_font", + "ps_split_composite", + "ps_text_runDUAL_SOURCE_BLENDING", + "ps_text_runGLYPH_TRANSFORM", + "ps_text_runDUAL_SOURCE_BLENDING_GLYPH_TRANSFORM", + "ps_text_run", +]; + +fn main() { + let shader_dir = match std::env::var("MOZ_SRC") { + Ok(dir) => dir + "/gfx/wr/webrender/res", + Err(_) => std::env::var("CARGO_MANIFEST_DIR").unwrap() + "/../webrender/res", + }; + + for shader in WR_SHADERS { + translate_shader(shader, &shader_dir); + } + + write_load_shader(WR_SHADERS); + + println!("cargo:rerun-if-changed=src/gl_defs.h"); + println!("cargo:rerun-if-changed=src/glsl.h"); + println!("cargo:rerun-if-changed=src/program.h"); + println!("cargo:rerun-if-changed=src/texture.h"); + println!("cargo:rerun-if-changed=src/vector_type.h"); + println!("cargo:rerun-if-changed=src/gl.cc"); + cc::Build::new() + .cpp(true) + .file("src/gl.cc") + .flag("-std=c++14") + .flag("-UMOZILLA_CONFIG_H") + .flag("-fno-exceptions") + .flag("-fno-rtti") + .flag("-fno-math-errno") + .define("_GLIBCXX_USE_CXX11_ABI", Some("0")) + .include(shader_dir) + .include("src") + .include(std::env::var("OUT_DIR").unwrap()) + .compile("gl_cc"); +} diff --git a/swgl/src/gl.cc b/swgl/src/gl.cc new file mode 100644 index 0000000000..07d9044f8b --- /dev/null +++ b/swgl/src/gl.cc @@ -0,0 +1,3120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include +#include +#include + +#ifdef __MACH__ +# include +# include +#else +# include +#endif + +#ifdef NDEBUG +# define debugf(...) +#else +# define debugf(...) printf(__VA_ARGS__) +#endif + +#ifdef _WIN32 +# define ALWAYS_INLINE __forceinline +#else +# define ALWAYS_INLINE __attribute__((always_inline)) inline +#endif + +#define UNREACHABLE __builtin_unreachable() + +#define UNUSED __attribute__((unused)) + +#ifdef MOZILLA_CLIENT +# define IMPLICIT __attribute__((annotate("moz_implicit"))) +#else +# define IMPLICIT +#endif + +#include "gl_defs.h" +#include "glsl.h" +#include "program.h" + +using namespace glsl; + +struct IntRect { + int x; + int y; + int width; + int height; +}; + +struct VertexAttrib { + size_t size = 0; // in bytes + GLenum type = 0; + bool normalized = false; + GLsizei stride = 0; + GLuint offset = 0; + bool enabled = false; + GLuint divisor = 0; + int vertex_array = 0; + int vertex_buffer = 0; + char* buf = nullptr; // XXX: this can easily dangle + size_t buf_size = 0; // this will let us bounds check +}; + +static int bytes_for_internal_format(GLenum internal_format) { + switch (internal_format) { + case GL_RGBA32F: + return 4 * 4; + case GL_RGBA32I: + return 4 * 4; + case GL_RGBA8: + case GL_BGRA8: + case GL_RGBA: + return 4; + case GL_R8: + case GL_RED: + return 1; + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT16: + return 2; + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32: + return 4; + default: + debugf("internal format: %x\n", internal_format); + assert(0); + return 0; + } +} + +static inline int aligned_stride(int row_bytes) { return (row_bytes + 3) & ~3; } + +static TextureFormat gl_format_to_texture_format(int type) { + switch (type) { + case GL_RGBA32F: + return TextureFormat::RGBA32F; + case GL_RGBA32I: + return TextureFormat::RGBA32I; + case GL_RGBA8: + return TextureFormat::RGBA8; + case GL_R8: + return TextureFormat::R8; + default: + assert(0); + return TextureFormat::RGBA8; + } +} + +struct Query { + uint64_t value = 0; +}; + +struct Buffer { + char* buf = nullptr; + size_t size = 0; + + bool allocate(size_t new_size) { + if (new_size != size) { + char* new_buf = (char*)realloc(buf, new_size); + assert(new_buf); + if (new_buf) { + buf = new_buf; + size = new_size; + return true; + } + cleanup(); + } + return false; + } + + void cleanup() { + if (buf) { + free(buf); + buf = nullptr; + size = 0; + } + } + + ~Buffer() { cleanup(); } +}; + +struct Framebuffer { + GLuint color_attachment = 0; + GLint layer = 0; + GLuint depth_attachment = 0; +}; + +struct Renderbuffer { + GLuint texture = 0; + + void on_erase(); +}; + +TextureFilter gl_filter_to_texture_filter(int type) { + switch (type) { + case GL_NEAREST: + return TextureFilter::NEAREST; + case GL_NEAREST_MIPMAP_LINEAR: + return TextureFilter::NEAREST; + case GL_NEAREST_MIPMAP_NEAREST: + return TextureFilter::NEAREST; + case GL_LINEAR: + return TextureFilter::LINEAR; + case GL_LINEAR_MIPMAP_LINEAR: + return TextureFilter::LINEAR; + case GL_LINEAR_MIPMAP_NEAREST: + return TextureFilter::LINEAR; + default: + assert(0); + return TextureFilter::NEAREST; + } +} + +struct Texture { + int levels = 0; + GLenum internal_format = 0; + int width = 0; + int height = 0; + int depth = 0; + char* buf = nullptr; + size_t buf_size = 0; + GLenum min_filter = GL_NEAREST; + GLenum mag_filter = GL_LINEAR; + + enum FLAGS { + SHOULD_FREE = 1 << 1, + }; + int flags = SHOULD_FREE; + bool should_free() const { return bool(flags & SHOULD_FREE); } + + void set_flag(int flag, bool val) { + if (val) { + flags |= flag; + } else { + flags &= ~flag; + } + } + void set_should_free(bool val) { set_flag(SHOULD_FREE, val); } + + int delay_clear = 0; + uint32_t clear_val = 0; + uint32_t* cleared_rows = nullptr; + + void enable_delayed_clear(uint32_t val) { + delay_clear = height; + clear_val = val; + if (!cleared_rows) { + cleared_rows = new uint32_t[(height + 31) / 32]; + } + memset(cleared_rows, 0, ((height + 31) / 32) * sizeof(uint32_t)); + if (height & 31) { + cleared_rows[height / 32] = ~0U << (height & 31); + } + } + + void disable_delayed_clear() { + if (cleared_rows) { + delete[] cleared_rows; + cleared_rows = nullptr; + delay_clear = 0; + } + } + + int bpp() const { return bytes_for_internal_format(internal_format); } + + size_t stride(int b = 0, int min_width = 0) const { + return aligned_stride((b ? b : bpp()) * max(width, min_width)); + } + + size_t layer_stride(int b = 0, int min_width = 0, int min_height = 0) const { + return stride(b ? b : bpp(), min_width) * max(height, min_height); + } + + bool allocate(bool force = false, int min_width = 0, int min_height = 0) { + if ((!buf || force) && should_free()) { + size_t size = + layer_stride(bpp(), min_width, min_height) * max(depth, 1) * levels; + if (!buf || size > buf_size) { + char* new_buf = (char*)realloc(buf, size + sizeof(Float)); + assert(new_buf); + if (new_buf) { + buf = new_buf; + buf_size = size; + return true; + } + cleanup(); + } + } + return false; + } + + void cleanup() { + if (buf && should_free()) { + free(buf); + buf = nullptr; + buf_size = 0; + } + disable_delayed_clear(); + } + + ~Texture() { cleanup(); } +}; + +#define MAX_ATTRIBS 16 +#define NULL_ATTRIB 15 +struct VertexArray { + VertexAttrib attribs[MAX_ATTRIBS]; + int max_attrib = -1; + + void validate(); +}; + +struct Shader { + GLenum type = 0; + ProgramLoader loader = nullptr; +}; + +struct Program { + ProgramImpl* impl = nullptr; + VertexShaderImpl* vert_impl = nullptr; + FragmentShaderImpl* frag_impl = nullptr; + bool deleted = false; + + ~Program() { + delete impl; + delete vert_impl; + delete frag_impl; + } +}; + +// for GL defines to fully expand +#define CONCAT_KEY(prefix, x, y, z, w, ...) prefix##x##y##z##w +#define BLEND_KEY(...) CONCAT_KEY(BLEND_, __VA_ARGS__, 0, 0) +#define FOR_EACH_BLEND_KEY(macro) \ + macro(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE) \ + macro(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, 0, 0) \ + macro(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, 0, 0) \ + macro(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE) \ + macro(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA, 0, 0) macro( \ + GL_ZERO, GL_SRC_COLOR, 0, 0) macro(GL_ONE, GL_ONE, 0, 0) \ + macro(GL_ONE, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA) \ + macro(GL_ONE, GL_ZERO, 0, 0) macro( \ + GL_ONE_MINUS_DST_ALPHA, GL_ONE, GL_ZERO, GL_ONE) \ + macro(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR, \ + 0, 0) \ + macro(GL_ONE, GL_ONE_MINUS_SRC1_COLOR, 0, 0) + +#define DEFINE_BLEND_KEY(...) BLEND_KEY(__VA_ARGS__), +enum BlendKey : uint8_t { + BLEND_KEY_NONE = 0, + FOR_EACH_BLEND_KEY(DEFINE_BLEND_KEY) +}; + +const size_t MAX_TEXTURE_UNITS = 16; + +template +static inline bool unlink(T& binding, T n) { + if (binding == n) { + binding = 0; + return true; + } + return false; +} + +template +struct ObjectStore { + O** objects = nullptr; + size_t size = 0; + // reserve object 0 as null + size_t first_free = 1; + O invalid; + + ~ObjectStore() { + if (objects) { + for (size_t i = 0; i < size; i++) delete objects[i]; + free(objects); + } + } + + bool grow(size_t i) { + size_t new_size = size ? size : 8; + while (new_size <= i) new_size += new_size / 2; + O** new_objects = (O**)realloc(objects, new_size * sizeof(O*)); + assert(new_objects); + if (!new_objects) return false; + while (size < new_size) new_objects[size++] = nullptr; + objects = new_objects; + return true; + } + + void insert(size_t i, const O& o) { + if (i >= size && !grow(i)) return; + if (!objects[i]) objects[i] = new O(o); + } + + size_t next_free() { + size_t i = first_free; + while (i < size && objects[i]) i++; + first_free = i; + return i; + } + + size_t insert(const O& o = O()) { + size_t i = next_free(); + insert(i, o); + return i; + } + + O& operator[](size_t i) { + insert(i, O()); + return i < size ? *objects[i] : invalid; + } + + O* find(size_t i) const { return i < size ? objects[i] : nullptr; } + + template void on_erase(T* o, ...) {} + template void on_erase(T* o, decltype(&T::on_erase)) { + o->on_erase(); + } + + bool erase(size_t i) { + if (i < size && objects[i]) { + on_erase(objects[i], nullptr); + delete objects[i]; + objects[i] = nullptr; + if (i < first_free) first_free = i; + return true; + } + return false; + } + + O** begin() const { return objects; } + O** end() const { return &objects[size]; } +}; + +struct Context { + ObjectStore queries; + ObjectStore buffers; + ObjectStore textures; + ObjectStore vertex_arrays; + ObjectStore framebuffers; + ObjectStore renderbuffers; + ObjectStore shaders; + ObjectStore programs; + + IntRect viewport = {0, 0, 0, 0}; + + bool blend = false; + GLenum blendfunc_srgb = GL_ONE; + GLenum blendfunc_drgb = GL_ZERO; + GLenum blendfunc_sa = GL_ONE; + GLenum blendfunc_da = GL_ZERO; + GLenum blend_equation = GL_FUNC_ADD; + V8 blendcolor = 0; + BlendKey blend_key = BLEND_KEY_NONE; + + bool depthtest = false; + bool depthmask = true; + GLenum depthfunc = GL_LESS; + + bool scissortest = false; + IntRect scissor = {0, 0, 0, 0}; + + uint32_t clearcolor = 0; + GLdouble cleardepth = 1; + + int unpack_row_length = 0; + + int shaded_rows = 0; + int shaded_pixels = 0; + + struct TextureUnit { + GLuint texture_2d_binding = 0; + GLuint texture_3d_binding = 0; + GLuint texture_2d_array_binding = 0; + + void unlink(GLuint n) { + ::unlink(texture_2d_binding, n); + ::unlink(texture_3d_binding, n); + ::unlink(texture_2d_array_binding, n); + } + }; + TextureUnit texture_units[MAX_TEXTURE_UNITS]; + int active_texture_unit = 0; + + GLuint current_program = 0; + + GLuint current_vertex_array = 0; + bool validate_vertex_array = true; + + GLuint pixel_pack_buffer_binding = 0; + GLuint pixel_unpack_buffer_binding = 0; + GLuint array_buffer_binding = 0; + GLuint element_array_buffer_binding = 0; + GLuint time_elapsed_query = 0; + GLuint samples_passed_query = 0; + GLuint renderbuffer_binding = 0; + GLuint draw_framebuffer_binding = 0; + GLuint read_framebuffer_binding = 0; + GLuint unknown_binding = 0; + + GLuint& get_binding(GLenum name) { + switch (name) { + case GL_PIXEL_PACK_BUFFER: + return pixel_pack_buffer_binding; + case GL_PIXEL_UNPACK_BUFFER: + return pixel_unpack_buffer_binding; + case GL_ARRAY_BUFFER: + return array_buffer_binding; + case GL_ELEMENT_ARRAY_BUFFER: + return element_array_buffer_binding; + case GL_TEXTURE_2D: + return texture_units[active_texture_unit].texture_2d_binding; + case GL_TEXTURE_2D_ARRAY: + return texture_units[active_texture_unit].texture_2d_array_binding; + case GL_TEXTURE_3D: + return texture_units[active_texture_unit].texture_3d_binding; + case GL_TIME_ELAPSED: + return time_elapsed_query; + case GL_SAMPLES_PASSED: + return samples_passed_query; + case GL_RENDERBUFFER: + return renderbuffer_binding; + case GL_DRAW_FRAMEBUFFER: + return draw_framebuffer_binding; + case GL_READ_FRAMEBUFFER: + return read_framebuffer_binding; + default: + debugf("unknown binding %x\n", name); + assert(false); + return unknown_binding; + } + } +}; +static Context* ctx = nullptr; +static ProgramImpl* program_impl = nullptr; +static VertexShaderImpl* vertex_shader = nullptr; +static FragmentShaderImpl* fragment_shader = nullptr; +static BlendKey blend_key = BLEND_KEY_NONE; + +static void prepare_texture(Texture& t, const IntRect* skip = nullptr); + +template +static inline void init_depth(S* s, Texture& t) { + s->depth = t.depth; + s->height_stride = s->stride * t.height; +} + +template +static inline void init_filter(S* s, Texture& t) { + s->filter = gl_filter_to_texture_filter(t.mag_filter); +} + +template +static inline void init_sampler(S* s, Texture& t) { + prepare_texture(t); + s->width = t.width; + s->height = t.height; + int bpp = t.bpp(); + s->stride = t.stride(bpp); + if (bpp >= 4) s->stride /= 4; + // Use uint32_t* for easier sampling, but need to cast to uint8_t* for formats + // with bpp < 4. + s->buf = (uint32_t*)t.buf; + s->format = gl_format_to_texture_format(t.internal_format); +} + +template +S* lookup_sampler(S* s, int texture) { + Texture& t = ctx->textures[ctx->texture_units[texture].texture_2d_binding]; + if (!t.buf) { + *s = S(); + } else { + init_sampler(s, t); + init_filter(s, t); + } + return s; +} + +template +S* lookup_isampler(S* s, int texture) { + Texture& t = ctx->textures[ctx->texture_units[texture].texture_2d_binding]; + if (!t.buf) { + *s = S(); + } else { + init_sampler(s, t); + } + return s; +} + +template +S* lookup_sampler_array(S* s, int texture) { + Texture& t = + ctx->textures[ctx->texture_units[texture].texture_2d_array_binding]; + if (!t.buf) { + *s = S(); + } else { + init_sampler(s, t); + init_depth(s, t); + init_filter(s, t); + } + return s; +} + +int bytes_per_type(GLenum type) { + switch (type) { + case GL_INT: + return 4; + case GL_FLOAT: + return 4; + case GL_UNSIGNED_SHORT: + return 2; + case GL_UNSIGNED_BYTE: + return 1; + default: + assert(0); + return 0; + } +} + +template +static inline S load_attrib_scalar(const char* src, size_t size, GLenum type, + bool normalized) { + if (sizeof(S) <= size) { + return *reinterpret_cast(src); + } + S scalar = {0}; + if (type == GL_UNSIGNED_SHORT) { + if (normalized) { + for (size_t i = 0; i < size / sizeof(uint16_t); i++) { + typename ElementType::ty x = + reinterpret_cast(src)[i]; + put_nth_component(scalar, i, x * (1.0f / 0xFFFF)); + } + } else { + for (size_t i = 0; i < size / sizeof(uint16_t); i++) { + typename ElementType::ty x = + reinterpret_cast(src)[i]; + put_nth_component(scalar, i, x); + } + } + } else { + assert(sizeof(typename ElementType::ty) == bytes_per_type(type)); + memcpy(&scalar, src, size); + } + return scalar; +} + +template +void load_attrib(T& attrib, VertexAttrib& va, uint16_t* indices, int start, + int instance, int count) { + typedef decltype(force_scalar(attrib)) scalar_type; + if (!va.enabled) { + attrib = T(scalar_type{0}); + } else if (va.divisor == 1) { + char* src = (char*)va.buf + va.stride * instance + va.offset; + assert(src + va.size <= va.buf + va.buf_size); + attrib = T( + load_attrib_scalar(src, va.size, va.type, va.normalized)); + } else if (va.divisor == 0) { + if (!indices) return; + assert(sizeof(typename ElementType::ty) == bytes_per_type(va.type)); + assert(count == 3 || count == 4); + attrib = (T){ + load_attrib_scalar( + (char*)va.buf + va.stride * indices[start + 0] + va.offset, va.size, + va.type, va.normalized), + load_attrib_scalar( + (char*)va.buf + va.stride * indices[start + 1] + va.offset, va.size, + va.type, va.normalized), + load_attrib_scalar( + (char*)va.buf + va.stride * indices[start + 2] + va.offset, va.size, + va.type, va.normalized), + load_attrib_scalar( + (char*)va.buf + va.stride * indices[start + (count > 3 ? 3 : 2)] + + va.offset, + va.size, va.type, va.normalized), + }; + } else { + assert(false); + } +} + +template +void load_flat_attrib(T& attrib, VertexAttrib& va, uint16_t* indices, int start, + int instance, int count) { + typedef decltype(force_scalar(attrib)) scalar_type; + if (!va.enabled) { + attrib = T{0}; + return; + } + char* src = nullptr; + if (va.divisor == 1) { + src = (char*)va.buf + va.stride * instance + va.offset; + } else if (va.divisor == 0) { + if (!indices) return; + src = (char*)va.buf + va.stride * indices[start] + va.offset; + } else { + assert(false); + } + assert(src + va.size <= va.buf + va.buf_size); + attrib = + T(load_attrib_scalar(src, va.size, va.type, va.normalized)); +} + +void setup_program(GLuint program) { + if (!program) { + program_impl = nullptr; + vertex_shader = nullptr; + fragment_shader = nullptr; + return; + } + Program& p = ctx->programs[program]; + assert(p.impl); + assert(p.vert_impl); + assert(p.frag_impl); + program_impl = p.impl; + vertex_shader = p.vert_impl; + fragment_shader = p.frag_impl; +} + +extern ProgramLoader load_shader(const char* name); + +extern "C" { + +void UseProgram(GLuint program) { + if (ctx->current_program && program != ctx->current_program) { + auto* p = ctx->programs.find(ctx->current_program); + if (p && p->deleted) { + ctx->programs.erase(ctx->current_program); + } + } + ctx->current_program = program; + setup_program(program); +} + +void SetViewport(GLint x, GLint y, GLsizei width, GLsizei height) { + ctx->viewport.x = x; + ctx->viewport.y = y; + ctx->viewport.width = width; + ctx->viewport.height = height; +} + +void Enable(GLenum cap) { + switch (cap) { + case GL_BLEND: + ctx->blend = true; + blend_key = ctx->blend_key; + break; + case GL_DEPTH_TEST: + ctx->depthtest = true; + break; + case GL_SCISSOR_TEST: + ctx->scissortest = true; + break; + } +} + +void Disable(GLenum cap) { + switch (cap) { + case GL_BLEND: + ctx->blend = false; + blend_key = BLEND_KEY_NONE; + break; + case GL_DEPTH_TEST: + ctx->depthtest = false; + break; + case GL_SCISSOR_TEST: + ctx->scissortest = false; + break; + } +} + +GLenum GetError() { return GL_NO_ERROR; } + +static const char* const extensions[] = { + "GL_ARB_blend_func_extended", "GL_ARB_copy_image", + "GL_ARB_draw_instanced", "GL_ARB_explicit_attrib_location", + "GL_ARB_instanced_arrays", "GL_ARB_invalidate_subdata", + "GL_ARB_texture_storage", "GL_EXT_timer_query", +}; + +void GetIntegerv(GLenum pname, GLint* params) { + assert(params); + switch (pname) { + case GL_MAX_TEXTURE_UNITS: + case GL_MAX_TEXTURE_IMAGE_UNITS: + params[0] = MAX_TEXTURE_UNITS; + break; + case GL_MAX_TEXTURE_SIZE: + params[0] = 1 << 15; + break; + case GL_MAX_ARRAY_TEXTURE_LAYERS: + params[0] = 1 << 15; + break; + case GL_READ_FRAMEBUFFER_BINDING: + params[0] = ctx->read_framebuffer_binding; + break; + case GL_DRAW_FRAMEBUFFER_BINDING: + params[0] = ctx->draw_framebuffer_binding; + break; + case GL_PIXEL_PACK_BUFFER_BINDING: + params[0] = ctx->pixel_pack_buffer_binding; + break; + case GL_PIXEL_UNPACK_BUFFER_BINDING: + params[0] = ctx->pixel_unpack_buffer_binding; + break; + case GL_NUM_EXTENSIONS: + params[0] = sizeof(extensions) / sizeof(extensions[0]); + break; + default: + debugf("unhandled glGetIntegerv parameter %x\n", pname); + assert(false); + } +} + +void GetBooleanv(GLenum pname, GLboolean* params) { + assert(params); + switch (pname) { + case GL_DEPTH_WRITEMASK: + params[0] = ctx->depthmask; + break; + default: + debugf("unhandled glGetBooleanv parameter %x\n", pname); + assert(false); + } +} + +const char* GetString(GLenum name) { + switch (name) { + case GL_VENDOR: + return "Mozilla Gfx"; + case GL_RENDERER: + return "Software WebRender"; + case GL_VERSION: + return "3.2"; + default: + debugf("unhandled glGetString parameter %x\n", name); + assert(false); + return nullptr; + } +} + +const char* GetStringi(GLenum name, GLuint index) { + switch (name) { + case GL_EXTENSIONS: + if (index >= sizeof(extensions) / sizeof(extensions[0])) { + return nullptr; + } + return extensions[index]; + default: + debugf("unhandled glGetStringi parameter %x\n", name); + assert(false); + return nullptr; + } +} + +GLenum remap_blendfunc(GLenum rgb, GLenum a) { + switch (a) { + case GL_SRC_ALPHA: + if (rgb == GL_SRC_COLOR) a = GL_SRC_COLOR; + break; + case GL_ONE_MINUS_SRC_ALPHA: + if (rgb == GL_ONE_MINUS_SRC_COLOR) a = GL_ONE_MINUS_SRC_COLOR; + break; + case GL_DST_ALPHA: + if (rgb == GL_DST_COLOR) a = GL_DST_COLOR; + break; + case GL_ONE_MINUS_DST_ALPHA: + if (rgb == GL_ONE_MINUS_DST_COLOR) a = GL_ONE_MINUS_DST_COLOR; + break; + case GL_CONSTANT_ALPHA: + if (rgb == GL_CONSTANT_COLOR) a = GL_CONSTANT_COLOR; + break; + case GL_ONE_MINUS_CONSTANT_ALPHA: + if (rgb == GL_ONE_MINUS_CONSTANT_COLOR) a = GL_ONE_MINUS_CONSTANT_COLOR; + break; + case GL_SRC_COLOR: + if (rgb == GL_SRC_ALPHA) a = GL_SRC_ALPHA; + break; + case GL_ONE_MINUS_SRC_COLOR: + if (rgb == GL_ONE_MINUS_SRC_ALPHA) a = GL_ONE_MINUS_SRC_ALPHA; + break; + case GL_DST_COLOR: + if (rgb == GL_DST_ALPHA) a = GL_DST_ALPHA; + break; + case GL_ONE_MINUS_DST_COLOR: + if (rgb == GL_ONE_MINUS_DST_ALPHA) a = GL_ONE_MINUS_DST_ALPHA; + break; + case GL_CONSTANT_COLOR: + if (rgb == GL_CONSTANT_ALPHA) a = GL_CONSTANT_ALPHA; + break; + case GL_ONE_MINUS_CONSTANT_COLOR: + if (rgb == GL_ONE_MINUS_CONSTANT_ALPHA) a = GL_ONE_MINUS_CONSTANT_ALPHA; + break; + case GL_SRC1_ALPHA: + if (rgb == GL_SRC1_COLOR) a = GL_SRC1_COLOR; + break; + case GL_ONE_MINUS_SRC1_ALPHA: + if (rgb == GL_ONE_MINUS_SRC1_COLOR) a = GL_ONE_MINUS_SRC1_COLOR; + break; + case GL_SRC1_COLOR: + if (rgb == GL_SRC1_ALPHA) a = GL_SRC1_ALPHA; + break; + case GL_ONE_MINUS_SRC1_COLOR: + if (rgb == GL_ONE_MINUS_SRC1_ALPHA) a = GL_ONE_MINUS_SRC1_ALPHA; + break; + } + return a; +} + +void BlendFunc(GLenum srgb, GLenum drgb, GLenum sa, GLenum da) { + ctx->blendfunc_srgb = srgb; + ctx->blendfunc_drgb = drgb; + sa = remap_blendfunc(srgb, sa); + da = remap_blendfunc(drgb, da); + ctx->blendfunc_sa = sa; + ctx->blendfunc_da = da; + +#define HASH_BLEND_KEY(x, y, z, w) ((x << 4) | (y) | (z << 24) | (w << 20)) + int hash = HASH_BLEND_KEY(srgb, drgb, 0, 0); + if (srgb != sa || drgb != da) hash |= HASH_BLEND_KEY(0, 0, sa, da); + switch (hash) { +#define MAP_BLEND_KEY(...) \ + case HASH_BLEND_KEY(__VA_ARGS__): \ + ctx->blend_key = BLEND_KEY(__VA_ARGS__); \ + break; + FOR_EACH_BLEND_KEY(MAP_BLEND_KEY) + default: + debugf("blendfunc: %x, %x, separate: %x, %x\n", srgb, drgb, sa, da); + assert(false); + break; + } + + if (ctx->blend) { + blend_key = ctx->blend_key; + } +} + +void BlendColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + I32 c = roundto((Float){b, g, r, a}, 255.49f); + ctx->blendcolor = CONVERT(c, U16).xyzwxyzw; +} + +void BlendEquation(GLenum mode) { + assert(mode == GL_FUNC_ADD); + ctx->blend_equation = mode; +} + +void DepthMask(GLboolean flag) { ctx->depthmask = flag; } + +void DepthFunc(GLenum func) { + switch (func) { + case GL_LESS: + case GL_LEQUAL: + break; + default: + assert(false); + } + ctx->depthfunc = func; +} + +void SetScissor(GLint x, GLint y, GLsizei width, GLsizei height) { + ctx->scissor.x = x; + ctx->scissor.y = y; + ctx->scissor.width = width; + ctx->scissor.height = height; +} + +void ClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + I32 c = roundto((Float){b, g, r, a}, 255.49f); + ctx->clearcolor = bit_cast(CONVERT(c, U8)); +} + +void ClearDepth(GLdouble depth) { ctx->cleardepth = depth; } + +void ActiveTexture(GLenum texture) { + assert(texture >= GL_TEXTURE0); + assert(texture < GL_TEXTURE0 + MAX_TEXTURE_UNITS); + ctx->active_texture_unit = + clamp(int(texture - GL_TEXTURE0), 0, int(MAX_TEXTURE_UNITS - 1)); +} + +void GenQueries(GLsizei n, GLuint* result) { + for (int i = 0; i < n; i++) { + Query q; + result[i] = ctx->queries.insert(q); + } +} + +void DeleteQuery(GLuint n) { + if (n && ctx->queries.erase(n)) { + unlink(ctx->time_elapsed_query, n); + unlink(ctx->samples_passed_query, n); + } +} + +void GenBuffers(int n, GLuint* result) { + for (int i = 0; i < n; i++) { + Buffer b; + result[i] = ctx->buffers.insert(b); + } +} + +void DeleteBuffer(GLuint n) { + if (n && ctx->buffers.erase(n)) { + unlink(ctx->pixel_pack_buffer_binding, n); + unlink(ctx->pixel_unpack_buffer_binding, n); + unlink(ctx->array_buffer_binding, n); + unlink(ctx->element_array_buffer_binding, n); + } +} + +void GenVertexArrays(int n, GLuint* result) { + for (int i = 0; i < n; i++) { + VertexArray v; + result[i] = ctx->vertex_arrays.insert(v); + } +} + +void DeleteVertexArray(GLuint n) { + if (n && ctx->vertex_arrays.erase(n)) { + unlink(ctx->current_vertex_array, n); + } +} + +GLuint CreateShader(GLenum type) { + Shader s; + s.type = type; + return ctx->shaders.insert(s); +} + +void ShaderSourceByName(GLuint shader, char* name) { + Shader& s = ctx->shaders[shader]; + s.loader = load_shader(name); + if (!s.loader) { + debugf("unknown shader %s\n", name); + } +} + +void AttachShader(GLuint program, GLuint shader) { + Program& p = ctx->programs[program]; + Shader& s = ctx->shaders[shader]; + if (s.type == GL_VERTEX_SHADER) { + if (!p.impl && s.loader) p.impl = s.loader(); + } else if (s.type == GL_FRAGMENT_SHADER) { + if (!p.impl && s.loader) p.impl = s.loader(); + } else { + assert(0); + } +} + +void DeleteShader(GLuint n) { + if (n) ctx->shaders.erase(n); +} + +GLuint CreateProgram() { + Program p; + return ctx->programs.insert(p); +} + +void DeleteProgram(GLuint n) { + if (!n) return; + if (ctx->current_program == n) { + if (auto* p = ctx->programs.find(n)) { + p->deleted = true; + } + } else { + ctx->programs.erase(n); + } +} + +void LinkProgram(GLuint program) { + Program& p = ctx->programs[program]; + assert(p.impl); + if (!p.vert_impl) p.vert_impl = p.impl->get_vertex_shader(); + if (!p.frag_impl) p.frag_impl = p.impl->get_fragment_shader(); +} + +void BindAttribLocation(GLuint program, GLuint index, char* name) { + Program& p = ctx->programs[program]; + assert(p.impl); + p.impl->bind_attrib(name, index); +} + +GLint GetAttribLocation(GLuint program, char* name) { + Program& p = ctx->programs[program]; + assert(p.impl); + return p.impl->get_attrib(name); +} + +GLint GetUniformLocation(GLuint program, char* name) { + Program& p = ctx->programs[program]; + assert(p.impl); + GLint loc = p.impl->get_uniform(name); + // debugf("location: %d\n", loc); + return loc; +} + +static uint64_t get_time_value() { +#ifdef __MACH__ + return mach_absolute_time(); +#elif defined(_WIN32) + return uint64_t(clock()) * (1000000000ULL / CLOCKS_PER_SEC); +#else + return ({ + struct timespec tp; + clock_gettime(CLOCK_MONOTONIC, &tp); + tp.tv_sec * 1000000000ULL + tp.tv_nsec; + }); +#endif +} + +void BeginQuery(GLenum target, GLuint id) { + ctx->get_binding(target) = id; + Query& q = ctx->queries[id]; + switch (target) { + case GL_SAMPLES_PASSED: + q.value = 0; + break; + case GL_TIME_ELAPSED: + q.value = get_time_value(); + break; + default: + debugf("unknown query target %x for query %d\n", target, id); + assert(false); + } +} + +void EndQuery(GLenum target) { + Query& q = ctx->queries[ctx->get_binding(target)]; + switch (target) { + case GL_SAMPLES_PASSED: + break; + case GL_TIME_ELAPSED: + q.value = get_time_value() - q.value; + break; + default: + debugf("unknown query target %x\n", target); + assert(false); + } + ctx->get_binding(target) = 0; +} + +void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params) { + Query& q = ctx->queries[id]; + switch (pname) { + case GL_QUERY_RESULT: + assert(params); + params[0] = q.value; + break; + default: + assert(false); + } +} + +void BindVertexArray(GLuint vertex_array) { + if (vertex_array != ctx->current_vertex_array) { + ctx->validate_vertex_array = true; + } + ctx->current_vertex_array = vertex_array; +} + +void BindTexture(GLenum target, GLuint texture) { + ctx->get_binding(target) = texture; +} + +void BindBuffer(GLenum target, GLuint buffer) { + ctx->get_binding(target) = buffer; +} + +void BindFramebuffer(GLenum target, GLuint fb) { + if (target == GL_FRAMEBUFFER) { + ctx->read_framebuffer_binding = fb; + ctx->draw_framebuffer_binding = fb; + } else { + assert(target == GL_READ_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER); + ctx->get_binding(target) = fb; + } +} + +void BindRenderbuffer(GLenum target, GLuint rb) { + ctx->get_binding(target) = rb; +} + +void PixelStorei(GLenum name, GLint param) { + if (name == GL_UNPACK_ALIGNMENT) { + assert(param == 1); + } else if (name == GL_UNPACK_ROW_LENGTH) { + ctx->unpack_row_length = param; + } +} + +static GLenum remap_internal_format(GLenum format) { + switch (format) { + case GL_DEPTH_COMPONENT: + return GL_DEPTH_COMPONENT16; + case GL_RGBA: + return GL_RGBA8; + case GL_RED: + return GL_R8; + default: + return format; + } +} + +void TexStorage3D(GLenum target, GLint levels, GLenum internal_format, + GLsizei width, GLsizei height, GLsizei depth) { + Texture& t = ctx->textures[ctx->get_binding(target)]; + internal_format = remap_internal_format(internal_format); + bool changed = false; + if (t.width != width || t.height != height || t.depth != depth || + t.levels != levels || t.internal_format != internal_format) { + changed = true; + t.levels = levels; + t.internal_format = internal_format; + t.width = width; + t.height = height; + t.depth = depth; + } + t.disable_delayed_clear(); + t.allocate(changed); +} + +static void set_tex_storage(Texture& t, GLint levels, GLenum internal_format, + GLsizei width, GLsizei height, + bool should_free = true, void* buf = nullptr, + GLsizei min_width = 0, GLsizei min_height = 0) { + internal_format = remap_internal_format(internal_format); + bool changed = false; + if (t.width != width || t.height != height || t.levels != levels || + t.internal_format != internal_format) { + changed = true; + t.levels = levels; + t.internal_format = internal_format; + t.width = width; + t.height = height; + } + if (t.should_free() != should_free || buf != nullptr) { + if (t.should_free()) { + t.cleanup(); + } + t.set_should_free(should_free); + t.buf = (char*)buf; + t.buf_size = 0; + } + t.disable_delayed_clear(); + t.allocate(changed, min_width, min_height); +} + +void TexStorage2D(GLenum target, GLint levels, GLenum internal_format, + GLsizei width, GLsizei height) { + Texture& t = ctx->textures[ctx->get_binding(target)]; + set_tex_storage(t, levels, internal_format, width, height); +} + +GLenum internal_format_for_data(GLenum format, GLenum ty) { + if (format == GL_RED && ty == GL_UNSIGNED_BYTE) { + return GL_R8; + } else if ((format == GL_RGBA || format == GL_BGRA) && + ty == GL_UNSIGNED_BYTE) { + return GL_RGBA8; + } else if (format == GL_RGBA && ty == GL_FLOAT) { + return GL_RGBA32F; + } else if (format == GL_RGBA_INTEGER && ty == GL_INT) { + return GL_RGBA32I; + } else { + debugf("unknown internal format for format %x, type %x\n", format, ty); + assert(false); + return 0; + } +} + +static inline void copy_bgra8_to_rgba8(uint32_t* dest, uint32_t* src, + int width) { + for (; width >= 4; width -= 4, dest += 4, src += 4) { + U32 p = unaligned_load(src); + U32 rb = p & 0x00FF00FF; + unaligned_store(dest, (p & 0xFF00FF00) | (rb << 16) | (rb >> 16)); + } + for (; width > 0; width--, dest++, src++) { + uint32_t p = *src; + uint32_t rb = p & 0x00FF00FF; + *dest = (p & 0xFF00FF00) | (rb << 16) | (rb >> 16); + } +} + +static Buffer* get_pixel_pack_buffer() { + return ctx->pixel_pack_buffer_binding + ? &ctx->buffers[ctx->pixel_pack_buffer_binding] + : nullptr; +} + +static void* get_pixel_pack_buffer_data(void* data) { + if (Buffer* b = get_pixel_pack_buffer()) { + return b->buf ? b->buf + (size_t)data : nullptr; + } + return data; +} + +static Buffer* get_pixel_unpack_buffer() { + return ctx->pixel_unpack_buffer_binding + ? &ctx->buffers[ctx->pixel_unpack_buffer_binding] + : nullptr; +} + +static void* get_pixel_unpack_buffer_data(void* data) { + if (Buffer* b = get_pixel_unpack_buffer()) { + return b->buf ? b->buf + (size_t)data : nullptr; + } + return data; +} + +void TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, GLenum format, GLenum ty, + void* data) { + data = get_pixel_unpack_buffer_data(data); + if (!data) return; + Texture& t = ctx->textures[ctx->get_binding(target)]; + IntRect skip = {xoffset, yoffset, width, height}; + prepare_texture(t, &skip); + assert(xoffset + width <= t.width); + assert(yoffset + height <= t.height); + assert(ctx->unpack_row_length == 0 || ctx->unpack_row_length >= width); + GLsizei row_length = + ctx->unpack_row_length != 0 ? ctx->unpack_row_length : width; + assert(t.internal_format == internal_format_for_data(format, ty)); + int bpp = t.bpp(); + if (!bpp || !t.buf) return; + size_t dest_stride = t.stride(bpp); + char* dest = t.buf + yoffset * dest_stride + xoffset * bpp; + char* src = (char*)data; + for (int y = 0; y < height; y++) { + if (t.internal_format == GL_RGBA8 && format != GL_BGRA) { + copy_bgra8_to_rgba8((uint32_t*)dest, (uint32_t*)src, width); + } else { + memcpy(dest, src, width * bpp); + } + dest += dest_stride; + src += row_length * bpp; + } +} + +void TexImage2D(GLenum target, GLint level, GLint internal_format, + GLsizei width, GLsizei height, GLint border, GLenum format, + GLenum ty, void* data) { + assert(level == 0); + assert(border == 0); + TexStorage2D(target, 1, internal_format, width, height); + TexSubImage2D(target, 0, 0, 0, width, height, format, ty, data); +} + +void TexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, + GLenum format, GLenum ty, void* data) { + data = get_pixel_unpack_buffer_data(data); + if (!data) return; + Texture& t = ctx->textures[ctx->get_binding(target)]; + prepare_texture(t); + assert(ctx->unpack_row_length == 0 || ctx->unpack_row_length >= width); + GLsizei row_length = + ctx->unpack_row_length != 0 ? ctx->unpack_row_length : width; + if (format == GL_BGRA) { + assert(ty == GL_UNSIGNED_BYTE); + assert(t.internal_format == GL_RGBA8); + } else { + assert(t.internal_format == internal_format_for_data(format, ty)); + } + int bpp = t.bpp(); + if (!bpp || !t.buf) return; + char* src = (char*)data; + assert(xoffset + width <= t.width); + assert(yoffset + height <= t.height); + assert(zoffset + depth <= t.depth); + size_t dest_stride = t.stride(bpp); + for (int z = 0; z < depth; z++) { + char* dest = t.buf + ((zoffset + z) * t.height + yoffset) * dest_stride + + xoffset * bpp; + for (int y = 0; y < height; y++) { + if (t.internal_format == GL_RGBA8 && format != GL_BGRA) { + copy_bgra8_to_rgba8((uint32_t*)dest, (uint32_t*)src, width); + } else { + memcpy(dest, src, width * bpp); + } + dest += dest_stride; + src += row_length * bpp; + } + } +} + +void TexImage3D(GLenum target, GLint level, GLint internal_format, + GLsizei width, GLsizei height, GLsizei depth, GLint border, + GLenum format, GLenum ty, void* data) { + assert(level == 0); + assert(border == 0); + TexStorage3D(target, 1, internal_format, width, height, depth); + TexSubImage3D(target, 0, 0, 0, 0, width, height, depth, format, ty, data); +} + +void TexParameteri(GLenum target, GLenum pname, GLint param) { + Texture& t = ctx->textures[ctx->get_binding(target)]; + switch (pname) { + case GL_TEXTURE_WRAP_S: + assert(param == GL_CLAMP_TO_EDGE); + break; + case GL_TEXTURE_WRAP_T: + assert(param == GL_CLAMP_TO_EDGE); + break; + case GL_TEXTURE_MIN_FILTER: + assert(param == GL_NEAREST || param == GL_LINEAR); + t.min_filter = param; + break; + case GL_TEXTURE_MAG_FILTER: + assert(param == GL_NEAREST || param == GL_LINEAR); + t.mag_filter = param; + break; + default: + break; + } +} + +void GenTextures(int n, GLuint* result) { + for (int i = 0; i < n; i++) { + Texture t; + result[i] = ctx->textures.insert(t); + } +} + +void DeleteTexture(GLuint n) { + if (n && ctx->textures.erase(n)) { + for (size_t i = 0; i < MAX_TEXTURE_UNITS; i++) { + ctx->texture_units[i].unlink(n); + } + } +} + +void GenRenderbuffers(int n, GLuint* result) { + for (int i = 0; i < n; i++) { + Renderbuffer r; + result[i] = ctx->renderbuffers.insert(r); + } +} + +void Renderbuffer::on_erase() { + for (auto* fb : ctx->framebuffers) { + if (fb) { + if (unlink(fb->color_attachment, texture)) { + fb->layer = 0; + } + unlink(fb->depth_attachment, texture); + } + } + DeleteTexture(texture); +} + +void DeleteRenderbuffer(GLuint n) { + if (n && ctx->renderbuffers.erase(n)) { + unlink(ctx->renderbuffer_binding, n); + } +} + +void GenFramebuffers(int n, GLuint* result) { + for (int i = 0; i < n; i++) { + Framebuffer f; + result[i] = ctx->framebuffers.insert(f); + } +} + +void DeleteFramebuffer(GLuint n) { + if (n && ctx->framebuffers.erase(n)) { + unlink(ctx->read_framebuffer_binding, n); + unlink(ctx->draw_framebuffer_binding, n); + } +} + +void RenderbufferStorage(GLenum target, GLenum internal_format, GLsizei width, + GLsizei height) { + // Just refer a renderbuffer to a texture to simplify things for now... + Renderbuffer& r = ctx->renderbuffers[ctx->get_binding(target)]; + if (!r.texture) { + GenTextures(1, &r.texture); + } + switch (internal_format) { + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32: + // Force depth format to 16 bits... + internal_format = GL_DEPTH_COMPONENT16; + break; + } + set_tex_storage(ctx->textures[r.texture], 1, internal_format, width, height); +} + +void VertexAttribPointer(GLuint index, GLint size, GLenum type, bool normalized, + GLsizei stride, GLuint offset) { + // debugf("cva: %d\n", ctx->current_vertex_array); + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (index >= NULL_ATTRIB) { + assert(0); + return; + } + VertexAttrib& va = v.attribs[index]; + va.size = size * bytes_per_type(type); + va.type = type; + va.normalized = normalized; + va.stride = stride; + va.offset = offset; + // Buffer &vertex_buf = ctx->buffers[ctx->array_buffer_binding]; + va.vertex_buffer = ctx->array_buffer_binding; + va.vertex_array = ctx->current_vertex_array; + ctx->validate_vertex_array = true; +} + +void VertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, + GLuint offset) { + // debugf("cva: %d\n", ctx->current_vertex_array); + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (index >= NULL_ATTRIB) { + assert(0); + return; + } + VertexAttrib& va = v.attribs[index]; + va.size = size * bytes_per_type(type); + va.type = type; + va.normalized = false; + va.stride = stride; + va.offset = offset; + // Buffer &vertex_buf = ctx->buffers[ctx->array_buffer_binding]; + va.vertex_buffer = ctx->array_buffer_binding; + va.vertex_array = ctx->current_vertex_array; + ctx->validate_vertex_array = true; +} + +void EnableVertexAttribArray(GLuint index) { + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (index >= NULL_ATTRIB) { + assert(0); + return; + } + VertexAttrib& va = v.attribs[index]; + if (!va.enabled) { + ctx->validate_vertex_array = true; + } + va.enabled = true; + v.max_attrib = max(v.max_attrib, (int)index); +} + +void DisableVertexAttribArray(GLuint index) { + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (index >= NULL_ATTRIB) { + assert(0); + return; + } + VertexAttrib& va = v.attribs[index]; + if (va.enabled) { + ctx->validate_vertex_array = true; + } + va.enabled = false; +} + +void VertexAttribDivisor(GLuint index, GLuint divisor) { + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (index >= NULL_ATTRIB) { + assert(0); + return; + } + VertexAttrib& va = v.attribs[index]; + va.divisor = divisor; +} + +void BufferData(GLenum target, GLsizeiptr size, void* data, GLenum usage) { + Buffer& b = ctx->buffers[ctx->get_binding(target)]; + if (b.allocate(size)) { + ctx->validate_vertex_array = true; + } + if (data && b.buf && size <= b.size) { + memcpy(b.buf, data, size); + } +} + +void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, + void* data) { + Buffer& b = ctx->buffers[ctx->get_binding(target)]; + assert(offset + size <= b.size); + if (data && b.buf && offset + size <= b.size) { + memcpy(&b.buf[offset], data, size); + } +} + +void* MapBuffer(GLenum target, GLbitfield access) { + Buffer& b = ctx->buffers[ctx->get_binding(target)]; + return b.buf; +} + +void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, + GLbitfield access) { + Buffer& b = ctx->buffers[ctx->get_binding(target)]; + if (b.buf && offset >= 0 && length > 0 && offset + length <= b.size) { + return b.buf + offset; + } + return nullptr; +} + +GLboolean UnmapBuffer(GLenum target) { + Buffer& b = ctx->buffers[ctx->get_binding(target)]; + return b.buf != nullptr; +} + +void Uniform1i(GLint location, GLint V0) { + // debugf("tex: %d\n", (int)ctx->textures.size); + if (!program_impl->set_sampler(location, V0)) { + vertex_shader->set_uniform_1i(location, V0); + fragment_shader->set_uniform_1i(location, V0); + } +} +void Uniform4fv(GLint location, GLsizei count, const GLfloat* v) { + vertex_shader->set_uniform_4fv(location, v); + fragment_shader->set_uniform_4fv(location, v); +} +void UniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, + const GLfloat* value) { + vertex_shader->set_uniform_matrix4fv(location, value); + fragment_shader->set_uniform_matrix4fv(location, value); +} + +void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, + GLuint texture, GLint level) { + assert(target == GL_READ_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER); + Framebuffer& fb = ctx->framebuffers[ctx->get_binding(target)]; + if (attachment == GL_COLOR_ATTACHMENT0) { + fb.color_attachment = texture; + fb.layer = 0; + } else if (attachment == GL_DEPTH_ATTACHMENT) { + fb.depth_attachment = texture; + } else { + assert(0); + } +} + +void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, + GLint level, GLint layer) { + assert(level == 0); + assert(target == GL_READ_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER); + Framebuffer& fb = ctx->framebuffers[ctx->get_binding(target)]; + if (attachment == GL_COLOR_ATTACHMENT0) { + fb.color_attachment = texture; + fb.layer = layer; + } else if (attachment == GL_DEPTH_ATTACHMENT) { + assert(layer == 0); + fb.depth_attachment = texture; + } else { + assert(0); + } +} + +void FramebufferRenderbuffer(GLenum target, GLenum attachment, + GLenum renderbuffertarget, GLuint renderbuffer) { + assert(target == GL_READ_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER); + assert(renderbuffertarget == GL_RENDERBUFFER); + Framebuffer& fb = ctx->framebuffers[ctx->get_binding(target)]; + Renderbuffer& rb = ctx->renderbuffers[renderbuffer]; + if (attachment == GL_COLOR_ATTACHMENT0) { + fb.color_attachment = rb.texture; + fb.layer = 0; + } else if (attachment == GL_DEPTH_ATTACHMENT) { + fb.depth_attachment = rb.texture; + } else { + assert(0); + } +} + +} // extern "C" + +static inline Framebuffer* get_framebuffer(GLenum target) { + if (target == GL_FRAMEBUFFER) { + target = GL_DRAW_FRAMEBUFFER; + } + return ctx->framebuffers.find(ctx->get_binding(target)); +} + +template +static inline void fill_n(T* dst, size_t n, T val) { + for (T* end = &dst[n]; dst < end; dst++) *dst = val; +} + +#if USE_SSE2 +template <> +inline void fill_n(uint32_t* dst, size_t n, uint32_t val) { + __asm__ __volatile__("rep stosl\n" + : "+D"(dst), "+c"(n) + : "a"(val) + : "memory", "cc"); +} +#endif + +template +static void clear_buffer(Texture& t, T value, int x0, int x1, int y0, int y1, + int layer = 0, int skip_start = 0, int skip_end = 0) { + if (!t.buf) return; + skip_start = max(skip_start, x0); + skip_end = max(skip_end, skip_start); + size_t stride = t.stride(sizeof(T)); + if (x1 - x0 == t.width && y1 - y0 > 1 && skip_start >= skip_end) { + x1 += (stride / sizeof(T)) * (y1 - y0 - 1); + y1 = y0 + 1; + } + char* buf = t.buf + stride * (t.height * layer + y0) + x0 * sizeof(T); + uint32_t chunk = + sizeof(T) == 1 + ? uint32_t(value) * 0x01010101U + : (sizeof(T) == 2 ? uint32_t(value) | (uint32_t(value) << 16) + : value); + for (int y = y0; y < y1; y++) { + if (x0 < skip_start) { + fill_n((uint32_t*)buf, (skip_start - x0) / (4 / sizeof(T)), chunk); + if (sizeof(T) < 4) { + fill_n((T*)buf + ((skip_start - x0) & ~(4 / sizeof(T) - 1)), + (skip_start - x0) & (4 / sizeof(T) - 1), value); + } + } + if (skip_end < x1) { + T* skip_buf = (T*)buf + (skip_end - x0); + fill_n((uint32_t*)skip_buf, (x1 - skip_end) / (4 / sizeof(T)), chunk); + if (sizeof(T) < 4) { + fill_n(skip_buf + ((x1 - skip_end) & ~(4 / sizeof(T) - 1)), + (x1 - skip_end) & (4 / sizeof(T) - 1), value); + } + } + buf += stride; + } +} + +template +static inline void clear_buffer(Texture& t, T value, int layer = 0) { + int x0 = 0, y0 = 0, x1 = t.width, y1 = t.height; + if (ctx->scissortest) { + x0 = max(x0, ctx->scissor.x); + y0 = max(y0, ctx->scissor.y); + x1 = min(x1, ctx->scissor.x + ctx->scissor.width); + y1 = min(y1, ctx->scissor.y + ctx->scissor.height); + } + if (x1 - x0 > 0) { + clear_buffer(t, value, x0, x1, y0, y1, layer); + } +} + +template +static void force_clear(Texture& t, const IntRect* skip = nullptr) { + if (!t.delay_clear || !t.cleared_rows) { + return; + } + int y0 = 0; + int y1 = t.height; + int skip_start = 0; + int skip_end = 0; + if (skip) { + y0 = min(max(skip->y, 0), t.height); + y1 = min(max(skip->y + skip->height, y0), t.height); + skip_start = min(max(skip->x, 0), t.width); + skip_end = min(max(skip->x + skip->width, y0), t.width); + if (skip_start <= 0 && skip_end >= t.width && y0 <= 0 && y1 >= t.height) { + t.disable_delayed_clear(); + return; + } + } + int num_masks = (y1 + 31) / 32; + uint32_t* rows = t.cleared_rows; + for (int i = y0 / 32; i < num_masks; i++) { + uint32_t mask = rows[i]; + if (mask != ~0U) { + rows[i] = ~0U; + int start = i * 32; + while (mask) { + int count = __builtin_ctz(mask); + if (count > 0) { + clear_buffer(t, t.clear_val, 0, t.width, start, start + count, 0, + skip_start, skip_end); + t.delay_clear -= count; + start += count; + mask >>= count; + } + count = __builtin_ctz(mask + 1); + start += count; + mask >>= count; + } + int count = (i + 1) * 32 - start; + if (count > 0) { + clear_buffer(t, t.clear_val, 0, t.width, start, start + count, 0, + skip_start, skip_end); + t.delay_clear -= count; + } + } + } + if (t.delay_clear <= 0) t.disable_delayed_clear(); +} + +static void prepare_texture(Texture& t, const IntRect* skip) { + if (t.delay_clear) { + switch (t.internal_format) { + case GL_RGBA8: + force_clear(t, skip); + break; + case GL_R8: + force_clear(t, skip); + break; + case GL_DEPTH_COMPONENT16: + force_clear(t, skip); + break; + default: + assert(false); + break; + } + } +} + +extern "C" { + +void InitDefaultFramebuffer(int width, int height) { + Framebuffer& fb = ctx->framebuffers[0]; + if (!fb.color_attachment) { + GenTextures(1, &fb.color_attachment); + fb.layer = 0; + } + Texture& colortex = ctx->textures[fb.color_attachment]; + if (colortex.width != width || colortex.height != height) { + colortex.cleanup(); + set_tex_storage(colortex, 1, GL_RGBA8, width, height); + } + if (!fb.depth_attachment) { + GenTextures(1, &fb.depth_attachment); + } + Texture& depthtex = ctx->textures[fb.depth_attachment]; + if (depthtex.width != width || depthtex.height != height) { + depthtex.cleanup(); + set_tex_storage(depthtex, 1, GL_DEPTH_COMPONENT16, width, height); + } +} + +void* GetColorBuffer(GLuint fbo, GLboolean flush, int32_t* width, + int32_t* height) { + Framebuffer* fb = ctx->framebuffers.find(fbo); + if (!fb || !fb->color_attachment) { + return nullptr; + } + Texture& colortex = ctx->textures[fb->color_attachment]; + if (flush) { + prepare_texture(colortex); + } + *width = colortex.width; + *height = colortex.height; + return colortex.buf + ? colortex.buf + + (fb->layer ? fb->layer * colortex.layer_stride() : 0) + : nullptr; +} + +void SetTextureBuffer(GLuint texid, GLenum internal_format, GLsizei width, + GLsizei height, void* buf, GLsizei min_width, + GLsizei min_height) { + Texture& t = ctx->textures[texid]; + set_tex_storage(t, 1, internal_format, width, height, !buf, buf, min_width, + min_height); +} + +GLenum CheckFramebufferStatus(GLenum target) { + Framebuffer* fb = get_framebuffer(target); + if (!fb || !fb->color_attachment) { + return GL_FRAMEBUFFER_UNSUPPORTED; + } + return GL_FRAMEBUFFER_COMPLETE; +} + +static inline bool clear_requires_scissor(Texture& t) { + return ctx->scissortest && + (ctx->scissor.x > 0 || ctx->scissor.y > 0 || + ctx->scissor.width < t.width || ctx->scissor.height < t.height); +} + +void Clear(GLbitfield mask) { + Framebuffer& fb = *get_framebuffer(GL_DRAW_FRAMEBUFFER); + if ((mask & GL_COLOR_BUFFER_BIT) && fb.color_attachment) { + Texture& t = ctx->textures[fb.color_attachment]; + if (t.internal_format == GL_RGBA8) { + uint32_t color = ctx->clearcolor; + if (clear_requires_scissor(t)) { + force_clear(t, &ctx->scissor); + clear_buffer(t, color, fb.layer); + } else if (t.depth > 1) { + t.disable_delayed_clear(); + clear_buffer(t, color, fb.layer); + } else { + t.enable_delayed_clear(color); + } + } else if (t.internal_format == GL_R8) { + uint8_t color = uint8_t((ctx->clearcolor >> 16) & 0xFF); + if (clear_requires_scissor(t)) { + force_clear(t, &ctx->scissor); + clear_buffer(t, color, fb.layer); + } else if (t.depth > 1) { + t.disable_delayed_clear(); + clear_buffer(t, color, fb.layer); + } else { + t.enable_delayed_clear(color); + } + } else { + assert(false); + } + } + if ((mask & GL_DEPTH_BUFFER_BIT) && fb.depth_attachment) { + Texture& t = ctx->textures[fb.depth_attachment]; + assert(t.internal_format == GL_DEPTH_COMPONENT16); + uint16_t depth = uint16_t(0xFFFF * ctx->cleardepth) - 0x8000; + if (clear_requires_scissor(t)) { + force_clear(t, &ctx->scissor); + clear_buffer(t, depth); + } else { + t.enable_delayed_clear(depth); + } + } +} + +void InvalidateFramebuffer(GLenum target, GLsizei num_attachments, + const GLenum* attachments) { + Framebuffer* fb = get_framebuffer(target); + if (!fb || num_attachments <= 0 || !attachments) { + return; + } + for (GLsizei i = 0; i < num_attachments; i++) { + switch (attachments[i]) { + case GL_DEPTH_ATTACHMENT: { + Texture& t = ctx->textures[fb->depth_attachment]; + t.disable_delayed_clear(); + break; + } + case GL_COLOR_ATTACHMENT0: { + Texture& t = ctx->textures[fb->color_attachment]; + t.disable_delayed_clear(); + break; + } + } + } +} + +void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, + GLenum type, void* data) { + data = get_pixel_pack_buffer_data(data); + if (!data) return; + Framebuffer* fb = get_framebuffer(GL_READ_FRAMEBUFFER); + if (!fb) return; + assert(format == GL_RED || format == GL_RGBA || format == GL_RGBA_INTEGER || + format == GL_BGRA); + Texture& t = ctx->textures[fb->color_attachment]; + if (!t.buf) return; + prepare_texture(t); + // debugf("read pixels %d, %d, %d, %d from fb %d with format %x\n", x, y, + // width, height, ctx->read_framebuffer_binding, t.internal_format); + assert(x + width <= t.width); + assert(y + height <= t.height); + if (internal_format_for_data(format, type) != t.internal_format) { + debugf("mismatched format for read pixels: %x vs %x\n", t.internal_format, + internal_format_for_data(format, type)); + assert(false); + } + int bpp = t.bpp(); + char* dest = (char*)data; + size_t src_stride = t.stride(bpp); + char* src = t.buf + (t.height * fb->layer + y) * src_stride + x * bpp; + for (; height > 0; height--) { + if (t.internal_format == GL_RGBA8 && format != GL_BGRA) { + copy_bgra8_to_rgba8((uint32_t*)dest, (uint32_t*)src, width); + } else { + memcpy(dest, src, width * bpp); + } + dest += width * bpp; + src += src_stride; + } +} + +void CopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLevel, + GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, + GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, + GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, + GLsizei srcDepth) { + if (srcTarget == GL_RENDERBUFFER) { + Renderbuffer& rb = ctx->renderbuffers[srcName]; + srcName = rb.texture; + } + if (dstTarget == GL_RENDERBUFFER) { + Renderbuffer& rb = ctx->renderbuffers[dstName]; + dstName = rb.texture; + } + Texture& srctex = ctx->textures[srcName]; + if (!srctex.buf) return; + prepare_texture(srctex); + Texture& dsttex = ctx->textures[dstName]; + if (!dsttex.buf) return; + IntRect skip = {dstX, dstY, srcWidth, abs(srcHeight)}; + prepare_texture(dsttex, &skip); + assert(srctex.internal_format == dsttex.internal_format); + assert(srcX + srcWidth <= srctex.width); + assert(srcY + srcHeight <= srctex.height); + assert(srcZ + srcDepth <= max(srctex.depth, 1)); + assert(dstX + srcWidth <= dsttex.width); + assert(max(dstY, dstY + srcHeight) <= dsttex.height); + assert(dstZ + srcDepth <= max(dsttex.depth, 1)); + int bpp = srctex.bpp(); + int src_stride = srctex.stride(bpp); + int dest_stride = dsttex.stride(bpp); + for (int z = 0; z < srcDepth; z++) { + char* dest = dsttex.buf + + (dsttex.height * (dstZ + z) + dstY) * dest_stride + dstX * bpp; + char* src = srctex.buf + (srctex.height * (srcZ + z) + srcY) * src_stride + + srcX * bpp; + if (srcHeight < 0) { + for (int y = srcHeight; y < 0; y++) { + dest -= dest_stride; + memcpy(dest, src, srcWidth * bpp); + src += src_stride; + } + } else { + for (int y = 0; y < srcHeight; y++) { + memcpy(dest, src, srcWidth * bpp); + dest += dest_stride; + src += src_stride; + } + } + } +} + +void CopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint zoffset, GLint x, GLint y, GLsizei width, + GLsizei height) { + Framebuffer* fb = get_framebuffer(GL_READ_FRAMEBUFFER); + if (!fb) return; + CopyImageSubData(fb->color_attachment, GL_TEXTURE_3D, 0, x, y, fb->layer, + ctx->get_binding(target), GL_TEXTURE_3D, 0, xoffset, yoffset, + zoffset, width, height, 1); +} + +void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint x, GLint y, GLsizei width, GLsizei height) { + Framebuffer* fb = get_framebuffer(GL_READ_FRAMEBUFFER); + if (!fb) return; + CopyImageSubData(fb->color_attachment, GL_TEXTURE_2D_ARRAY, 0, x, y, + fb->layer, ctx->get_binding(target), GL_TEXTURE_2D_ARRAY, 0, + xoffset, yoffset, 0, width, height, 1); +} + +void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, + GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, + GLbitfield mask, GLenum filter) { + assert(mask == GL_COLOR_BUFFER_BIT); + Framebuffer* srcfb = get_framebuffer(GL_READ_FRAMEBUFFER); + if (!srcfb) return; + Framebuffer* dstfb = get_framebuffer(GL_DRAW_FRAMEBUFFER); + if (!dstfb) return; + int dstWidth = dstX1 - dstX0; + int dstHeight = dstY1 - dstY0; + assert(srcX1 - srcX0 == dstWidth && srcY1 - srcY0 == abs(dstHeight)); + CopyImageSubData(srcfb->color_attachment, GL_TEXTURE_2D_ARRAY, 0, srcX0, + srcY0, srcfb->layer, dstfb->color_attachment, + GL_TEXTURE_2D_ARRAY, 0, dstX0, dstY0, dstfb->layer, dstWidth, + dstHeight, 1); +} + +} // extern "C" + +using PackedRGBA8 = V16; +using WideRGBA8 = V16; +using HalfRGBA8 = V8; + +static inline WideRGBA8 unpack(PackedRGBA8 p) { return CONVERT(p, WideRGBA8); } + +static inline PackedRGBA8 pack(WideRGBA8 p) { +#if USE_SSE2 + return _mm_packus_epi16(lowHalf(p), highHalf(p)); +#elif USE_NEON + return vcombine_u8(vqmovn_u16(lowHalf(p)), vqmovn_u16(highHalf(p))); +#else + return CONVERT(p, PackedRGBA8); +#endif +} + +static inline HalfRGBA8 packRGBA8(I32 a, I32 b) { +#if USE_SSE2 + return _mm_packs_epi32(a, b); +#elif USE_NEON + return vcombine_u16(vqmovun_s32(a), vqmovun_s32(b)); +#else + return CONVERT(combine(a, b), HalfRGBA8); +#endif +} + +using PackedR8 = V4; +using WideR8 = V4; + +static inline WideR8 unpack(PackedR8 p) { return CONVERT(p, WideR8); } + +static inline WideR8 packR8(I32 a) { +#if USE_SSE2 + return lowHalf(bit_cast>(_mm_packs_epi32(a, a))); +#elif USE_NEON + return vqmovun_s32(a); +#else + return CONVERT(a, WideR8); +#endif +} + +static inline PackedR8 pack(WideR8 p) { +#if USE_SSE2 + auto m = expand(p); + auto r = bit_cast>(_mm_packus_epi16(m, m)); + return SHUFFLE(r, r, 0, 1, 2, 3); +#elif USE_NEON + return lowHalf(bit_cast>(vqmovn_u16(expand(p)))); +#else + return CONVERT(p, PackedR8); +#endif +} + +using ZMask4 = V4; +using ZMask8 = V8; + +static inline PackedRGBA8 unpack(ZMask4 mask, uint32_t*) { + return bit_cast(mask.xxyyzzww); +} + +static inline WideR8 unpack(ZMask4 mask, uint8_t*) { + return bit_cast(mask); +} + +#if USE_SSE2 +# define ZMASK_NONE_PASSED 0xFFFF +# define ZMASK_ALL_PASSED 0 +static inline uint32_t zmask_code(ZMask8 mask) { + return _mm_movemask_epi8(mask); +} +static inline uint32_t zmask_code(ZMask4 mask) { + return zmask_code(mask.xyzwxyzw); +} +#else +using ZMask4Code = V4; +using ZMask8Code = V8; +# define ZMASK_NONE_PASSED 0xFFFFFFFFU +# define ZMASK_ALL_PASSED 0 +static inline uint32_t zmask_code(ZMask4 mask) { + return bit_cast(CONVERT(mask, ZMask4Code)); +} +static inline uint32_t zmask_code(ZMask8 mask) { + return zmask_code( + ZMask4((U16(lowHalf(mask)) >> 12) | (U16(highHalf(mask)) << 4))); +} +#endif + +template +static ALWAYS_INLINE int check_depth8(uint16_t z, uint16_t* zbuf, + ZMask8& outmask) { + ZMask8 dest = unaligned_load(zbuf); + ZMask8 src = int16_t(z); + // Invert the depth test to check which pixels failed and should be discarded. + ZMask8 mask = FUNC == GL_LEQUAL ? + // GL_LEQUAL: Not(LessEqual) = Greater + ZMask8(src > dest) + : + // GL_LESS: Not(Less) = GreaterEqual + ZMask8(src >= dest); + switch (zmask_code(mask)) { + case ZMASK_NONE_PASSED: + return 0; + case ZMASK_ALL_PASSED: + if (MASK) { + unaligned_store(zbuf, src); + } + return -1; + default: + if (MASK) { + unaligned_store(zbuf, (mask & dest) | (~mask & src)); + } + outmask = mask; + return 1; + } +} + +template +static ALWAYS_INLINE bool check_depth4(uint16_t z, uint16_t* zbuf, + ZMask4& outmask, int span = 0) { + ZMask4 dest = unaligned_load(zbuf); + ZMask4 src = int16_t(z); + // Invert the depth test to check which pixels failed and should be discarded. + ZMask4 mask = ctx->depthfunc == GL_LEQUAL + ? + // GL_LEQUAL: Not(LessEqual) = Greater + ZMask4(src > dest) + : + // GL_LESS: Not(Less) = GreaterEqual + ZMask4(src >= dest); + if (!FULL_SPANS) { + mask |= ZMask4(span) < ZMask4{1, 2, 3, 4}; + } + if (zmask_code(mask) == ZMASK_NONE_PASSED) { + return false; + } + if (!DISCARD && ctx->depthmask) { + unaligned_store(zbuf, (mask & dest) | (~mask & src)); + } + outmask = mask; + return true; +} + +static inline ZMask4 packZMask4(Bool a) { +#if USE_SSE2 + return lowHalf(bit_cast(_mm_packs_epi32(a, a))); +#elif USE_NEON + return vqmovun_s32(a); +#else + return CONVERT(a, ZMask4); +#endif +} + +static ALWAYS_INLINE void discard_depth(uint16_t z, uint16_t* zbuf, + ZMask4 mask) { + if (ctx->depthmask) { + ZMask4 dest = unaligned_load(zbuf); + ZMask4 src = int16_t(z); + mask |= packZMask4(fragment_shader->isPixelDiscarded); + unaligned_store(zbuf, (mask & dest) | (~mask & src)); + } +} + +static inline WideRGBA8 pack_pixels_RGBA8(const vec4& v) { + ivec4 i = roundto(v, 255.49f); + HalfRGBA8 xz = packRGBA8(i.z, i.x); + HalfRGBA8 yw = packRGBA8(i.y, i.w); + HalfRGBA8 xy = zipLow(xz, yw); + HalfRGBA8 zw = zipHigh(xz, yw); + HalfRGBA8 lo = zip2Low(xy, zw); + HalfRGBA8 hi = zip2High(xy, zw); + return combine(lo, hi); +} + +static inline WideRGBA8 pack_pixels_RGBA8(const vec4_scalar& v) { + I32 i = roundto((Float){v.z, v.y, v.x, v.w}, 255.49f); + HalfRGBA8 c = packRGBA8(i, i); + return combine(c, c); +} + +static inline WideRGBA8 pack_pixels_RGBA8() { + return pack_pixels_RGBA8(fragment_shader->gl_FragColor); +} + +template +static inline PackedRGBA8 pack_span(uint32_t*, const V& v) { + return pack(pack_pixels_RGBA8(v)); +} + +static inline PackedRGBA8 pack_span(uint32_t*) { + return pack(pack_pixels_RGBA8()); +} + +// (x*y + x) >> 8, cheap approximation of (x*y) / 255 +template +static inline T muldiv255(T x, T y) { + return (x * y + x) >> 8; +} +static inline WideRGBA8 alphas(WideRGBA8 c) { + return SHUFFLE(c, c, 3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15); +} + +static inline WideRGBA8 blend_pixels_RGBA8(PackedRGBA8 pdst, WideRGBA8 src) { + WideRGBA8 dst = unpack(pdst); + const WideRGBA8 RGB_MASK = {0xFFFF, 0xFFFF, 0xFFFF, 0, 0xFFFF, 0xFFFF, + 0xFFFF, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0, + 0xFFFF, 0xFFFF, 0xFFFF, 0}; + const WideRGBA8 ALPHA_MASK = {0, 0, 0, 0xFFFF, 0, 0, 0, 0xFFFF, + 0, 0, 0, 0xFFFF, 0, 0, 0, 0xFFFF}; + switch (blend_key) { + case BLEND_KEY_NONE: + return src; + case BLEND_KEY(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE): + return dst + muldiv255((src - dst) | ALPHA_MASK, alphas(src)); + case BLEND_KEY(GL_ONE, GL_ONE_MINUS_SRC_ALPHA): + return src + dst - muldiv255(dst, alphas(src)); + case BLEND_KEY(GL_ZERO, GL_ONE_MINUS_SRC_COLOR): + return dst - muldiv255(dst, src); + case BLEND_KEY(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE): + return dst - (muldiv255(dst, src) & RGB_MASK); + case BLEND_KEY(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA): + return dst - muldiv255(dst, alphas(src)); + case BLEND_KEY(GL_ZERO, GL_SRC_COLOR): + return muldiv255(src, dst); + case BLEND_KEY(GL_ONE, GL_ONE): + return src + dst; + case BLEND_KEY(GL_ONE, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA): + return src + dst - (muldiv255(dst, src) & ALPHA_MASK); + case BLEND_KEY(GL_ONE, GL_ZERO): + return src; + case BLEND_KEY(GL_ONE_MINUS_DST_ALPHA, GL_ONE, GL_ZERO, GL_ONE): + return dst + ((src - muldiv255(src, alphas(src))) & RGB_MASK); + case BLEND_KEY(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR): + return dst + + muldiv255(combine(ctx->blendcolor, ctx->blendcolor) - dst, src); + case BLEND_KEY(GL_ONE, GL_ONE_MINUS_SRC1_COLOR): { + WideRGBA8 secondary = + pack_pixels_RGBA8(fragment_shader->gl_SecondaryFragColor); + return src + dst - muldiv255(dst, secondary); + } + default: + UNREACHABLE; + // return src; + } +} + +template +static inline void commit_output(uint32_t* buf, PackedRGBA8 mask) { + fragment_shader->run(); + PackedRGBA8 dst = unaligned_load(buf); + WideRGBA8 r = pack_pixels_RGBA8(); + if (blend_key) r = blend_pixels_RGBA8(dst, r); + if (DISCARD) mask |= bit_cast(fragment_shader->isPixelDiscarded); + unaligned_store(buf, (mask & dst) | (~mask & pack(r))); +} + +template +static inline void commit_output(uint32_t* buf) { + commit_output(buf, 0); +} + +template <> +inline void commit_output(uint32_t* buf) { + fragment_shader->run(); + WideRGBA8 r = pack_pixels_RGBA8(); + if (blend_key) r = blend_pixels_RGBA8(unaligned_load(buf), r); + unaligned_store(buf, pack(r)); +} + +static inline void commit_span(uint32_t* buf, PackedRGBA8 r) { + if (blend_key) + r = pack(blend_pixels_RGBA8(unaligned_load(buf), unpack(r))); + unaligned_store(buf, r); +} + +UNUSED static inline void commit_solid_span(uint32_t* buf, PackedRGBA8 r, + int len) { + if (blend_key) { + auto src = unpack(r); + for (uint32_t* end = &buf[len]; buf < end; buf += 4) { + unaligned_store( + buf, pack(blend_pixels_RGBA8(unaligned_load(buf), src))); + } + } else { + fill_n(buf, len, bit_cast(r).x); + } +} + +UNUSED static inline void commit_texture_span(uint32_t* buf, uint32_t* src, + int len) { + if (blend_key) { + for (uint32_t* end = &buf[len]; buf < end; buf += 4, src += 4) { + PackedRGBA8 r = unaligned_load(src); + unaligned_store(buf, pack(blend_pixels_RGBA8( + unaligned_load(buf), unpack(r)))); + } + } else { + memcpy(buf, src, len * sizeof(uint32_t)); + } +} + +template +static inline void commit_output(uint32_t* buf, uint16_t z, uint16_t* zbuf) { + ZMask4 zmask; + if (check_depth4(z, zbuf, zmask)) { + commit_output(buf, unpack(zmask, buf)); + if (DISCARD) { + discard_depth(z, zbuf, zmask); + } + } else { + fragment_shader->skip(); + } +} + +template +static inline void commit_output(uint32_t* buf, uint16_t z, uint16_t* zbuf, + int span) { + ZMask4 zmask; + if (check_depth4(z, zbuf, zmask, span)) { + commit_output(buf, unpack(zmask, buf)); + if (DISCARD) { + discard_depth(z, zbuf, zmask); + } + } +} + +static inline PackedRGBA8 span_mask_RGBA8(int span) { + return bit_cast(I32(span) < I32{1, 2, 3, 4}); +} + +template +static inline void commit_output(uint32_t* buf, int span) { + commit_output(buf, span_mask_RGBA8(span)); +} + +static inline WideR8 pack_pixels_R8(Float c) { + return packR8(roundto(c, 255.49f)); +} + +static inline WideR8 pack_pixels_R8() { + return pack_pixels_R8(fragment_shader->gl_FragColor.x); +} + +template +static inline PackedR8 pack_span(uint8_t*, C c) { + return pack(pack_pixels_R8(c)); +} + +static inline PackedR8 pack_span(uint8_t*) { return pack(pack_pixels_R8()); } + +static inline WideR8 blend_pixels_R8(WideR8 dst, WideR8 src) { + switch (blend_key) { + case BLEND_KEY_NONE: + return src; + case BLEND_KEY(GL_ZERO, GL_SRC_COLOR): + return muldiv255(src, dst); + case BLEND_KEY(GL_ONE, GL_ONE): + return src + dst; + case BLEND_KEY(GL_ONE, GL_ZERO): + return src; + default: + UNREACHABLE; + // return src; + } +} + +template +static inline void commit_output(uint8_t* buf, WideR8 mask) { + fragment_shader->run(); + WideR8 dst = unpack(unaligned_load(buf)); + WideR8 r = pack_pixels_R8(); + if (blend_key) r = blend_pixels_R8(dst, r); + if (DISCARD) mask |= packR8(fragment_shader->isPixelDiscarded); + unaligned_store(buf, pack((mask & dst) | (~mask & r))); +} + +template +static inline void commit_output(uint8_t* buf) { + commit_output(buf, 0); +} + +template <> +inline void commit_output(uint8_t* buf) { + fragment_shader->run(); + WideR8 r = pack_pixels_R8(); + if (blend_key) r = blend_pixels_R8(unpack(unaligned_load(buf)), r); + unaligned_store(buf, pack(r)); +} + +static inline void commit_span(uint8_t* buf, PackedR8 r) { + if (blend_key) + r = pack(blend_pixels_R8(unpack(unaligned_load(buf)), unpack(r))); + unaligned_store(buf, r); +} + +UNUSED static inline void commit_solid_span(uint8_t* buf, PackedR8 r, int len) { + if (blend_key) { + auto src = unpack(r); + for (uint8_t* end = &buf[len]; buf < end; buf += 4) { + unaligned_store(buf, pack(blend_pixels_R8( + unpack(unaligned_load(buf)), src))); + } + } else { + fill_n((uint32_t*)buf, len / 4, bit_cast(r)); + } +} + +template +static inline void commit_output(uint8_t* buf, uint16_t z, uint16_t* zbuf) { + ZMask4 zmask; + if (check_depth4(z, zbuf, zmask)) { + commit_output(buf, unpack(zmask, buf)); + if (DISCARD) { + discard_depth(z, zbuf, zmask); + } + } else { + fragment_shader->skip(); + } +} + +template +static inline void commit_output(uint8_t* buf, uint16_t z, uint16_t* zbuf, + int span) { + ZMask4 zmask; + if (check_depth4(z, zbuf, zmask, span)) { + commit_output(buf, unpack(zmask, buf)); + if (DISCARD) { + discard_depth(z, zbuf, zmask); + } + } +} + +static inline WideR8 span_mask_R8(int span) { + return bit_cast(WideR8(span) < WideR8{1, 2, 3, 4}); +} + +template +static inline void commit_output(uint8_t* buf, int span) { + commit_output(buf, span_mask_R8(span)); +} + +static const size_t MAX_FLATS = 64; +typedef float Flats[MAX_FLATS]; + +static const size_t MAX_INTERPOLANTS = 16; +typedef VectorType Interpolants; + +template +static ALWAYS_INLINE void dispatch_draw_span(S* shader, P* buf, int len) { + int drawn = shader->draw_span(buf, len); + if (drawn) shader->step_interp_inputs(drawn >> 2); + for (buf += drawn; drawn < len; drawn += 4, buf += 4) { + S::run(shader); + commit_span(buf, pack_span(buf)); + } +} + +#include "texture.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#include "load_shader.h" +#pragma GCC diagnostic pop + +template +static inline void draw_depth_span(uint16_t z, P* buf, uint16_t* depth, + int span) { + int skip = 0; + if (fragment_shader->has_draw_span(buf)) { + int len = 0; + do { + ZMask8 zmask; + switch (check_depth8(z, depth, zmask)) { + case 0: + if (len) { + fragment_shader->draw_span(buf - len, len); + len = 0; + } + skip += 2; + break; + case -1: + if (skip) { + fragment_shader->skip(skip); + skip = 0; + } + len += 8; + break; + default: + if (len) { + fragment_shader->draw_span(buf - len, len); + len = 0; + } else if (skip) { + fragment_shader->skip(skip); + skip = 0; + } + commit_output(buf, unpack(lowHalf(zmask), buf)); + commit_output(buf + 4, unpack(highHalf(zmask), buf)); + break; + } + buf += 8; + depth += 8; + span -= 8; + } while (span >= 8); + if (len) { + fragment_shader->draw_span(buf - len, len); + } + } else { + do { + ZMask8 zmask; + switch (check_depth8(z, depth, zmask)) { + case 0: + skip += 2; + break; + case -1: + if (skip) { + fragment_shader->skip(skip); + skip = 0; + } + commit_output(buf); + commit_output(buf + 4); + break; + default: + if (skip) { + fragment_shader->skip(skip); + skip = 0; + } + commit_output(buf, unpack(lowHalf(zmask), buf)); + commit_output(buf + 4, unpack(highHalf(zmask), buf)); + break; + } + buf += 8; + depth += 8; + span -= 8; + } while (span >= 8); + } + if (skip) { + fragment_shader->skip(skip); + } +} + +typedef vec2_scalar Point; + +template +static inline void draw_quad_spans(int nump, Point p[4], uint16_t z, + Interpolants interp_outs[4], + Texture& colortex, int layer, + Texture& depthtex, float fx0, float fy0, + float fx1, float fy1) { + Point l0, r0, l1, r1; + int l0i, r0i, l1i, r1i; + { + int top = nump > 3 && p[3].y < p[2].y + ? (p[0].y < p[1].y ? (p[0].y < p[3].y ? 0 : 3) + : (p[1].y < p[3].y ? 1 : 3)) + : (p[0].y < p[1].y ? (p[0].y < p[2].y ? 0 : 2) + : (p[1].y < p[2].y ? 1 : 2)); +#define NEXT_POINT(idx) \ + ({ \ + int cur = (idx) + 1; \ + cur < nump ? cur : 0; \ + }) +#define PREV_POINT(idx) \ + ({ \ + int cur = (idx)-1; \ + cur >= 0 ? cur : nump - 1; \ + }) + int next = NEXT_POINT(top); + int prev = PREV_POINT(top); + if (p[top].y == p[next].y) { + l0i = next; + l1i = NEXT_POINT(next); + r0i = top; + r1i = prev; + } else if (p[top].y == p[prev].y) { + l0i = top; + l1i = next; + r0i = prev; + r1i = PREV_POINT(prev); + } else { + l0i = r0i = top; + l1i = next; + r1i = prev; + } + l0 = p[l0i]; + r0 = p[r0i]; + l1 = p[l1i]; + r1 = p[r1i]; + // debugf("l0: %d(%f,%f), r0: %d(%f,%f) -> l1: %d(%f,%f), r1: + // %d(%f,%f)\n", l0i, l0.x, l0.y, r0i, r0.x, r0.y, l1i, l1.x, l1.y, r1i, + // r1.x, r1.y); + } + + float lx = l0.x; + float lk = 1.0f / (l1.y - l0.y); + float lm = (l1.x - l0.x) * lk; + float rx = r0.x; + float rk = 1.0f / (r1.y - r0.y); + float rm = (r1.x - r0.x) * rk; + assert(l0.y == r0.y); + float y = floor(max(l0.y, fy0) + 0.5f) + 0.5f; + lx += (y - l0.y) * lm; + rx += (y - r0.y) * rm; + Interpolants lo = interp_outs[l0i]; + Interpolants lom = (interp_outs[l1i] - lo) * lk; + lo = lo + lom * (y - l0.y); + Interpolants ro = interp_outs[r0i]; + Interpolants rom = (interp_outs[r1i] - ro) * rk; + ro = ro + rom * (y - r0.y); + P* fbuf = (P*)colortex.buf + (layer * colortex.height + int(y)) * + colortex.stride(sizeof(P)) / sizeof(P); + uint16_t* fdepth = + (uint16_t*)depthtex.buf + + int(y) * depthtex.stride(sizeof(uint16_t)) / sizeof(uint16_t); + while (y < fy1) { + if (y > l1.y) { + l0i = l1i; + l0 = l1; + l1i = NEXT_POINT(l1i); + l1 = p[l1i]; + if (l1.y <= l0.y) break; + lk = 1.0f / (l1.y - l0.y); + lm = (l1.x - l0.x) * lk; + lx = l0.x + (y - l0.y) * lm; + lo = interp_outs[l0i]; + lom = (interp_outs[l1i] - lo) * lk; + lo += lom * (y - l0.y); + } + if (y > r1.y) { + r0i = r1i; + r0 = r1; + r1i = PREV_POINT(r1i); + r1 = p[r1i]; + if (r1.y <= r0.y) break; + rk = 1.0f / (r1.y - r0.y); + rm = (r1.x - r0.x) * rk; + rx = r0.x + (y - r0.y) * rm; + ro = interp_outs[r0i]; + rom = (interp_outs[r1i] - ro) * rk; + ro += rom * (y - r0.y); + } + int startx = int(max(min(lx, rx), fx0) + 0.5f); + int endx = int(min(max(lx, rx), fx1) + 0.5f); + int span = endx - startx; + if (span > 0) { + ctx->shaded_rows++; + ctx->shaded_pixels += span; + P* buf = fbuf + startx; + uint16_t* depth = fdepth + startx; + bool use_depth = depthtex.buf != nullptr; + bool use_discard = fragment_shader->use_discard(); + if (depthtex.delay_clear) { + int yi = int(y); + uint32_t& mask = depthtex.cleared_rows[yi / 32]; + if ((mask & (1 << (yi & 31))) == 0) { + switch (ctx->depthfunc) { + case GL_LESS: + if (int16_t(z) < int16_t(depthtex.clear_val)) + break; + else + goto next_span; + case GL_LEQUAL: + if (int16_t(z) <= int16_t(depthtex.clear_val)) + break; + else + goto next_span; + } + if (ctx->depthmask) { + mask |= 1 << (yi & 31); + depthtex.delay_clear--; + if (use_discard) { + clear_buffer(depthtex, depthtex.clear_val, 0, + depthtex.width, yi, yi + 1); + } else { + if (startx > 0 || endx < depthtex.width) { + clear_buffer(depthtex, depthtex.clear_val, 0, + depthtex.width, yi, yi + 1, 0, startx, + endx); + } + clear_buffer(depthtex, z, startx, endx, yi, yi + 1); + use_depth = false; + } + } else { + use_depth = false; + } + } + } + if (colortex.delay_clear) { + int yi = int(y); + uint32_t& mask = colortex.cleared_rows[yi / 32]; + if ((mask & (1 << (yi & 31))) == 0) { + mask |= 1 << (yi & 31); + colortex.delay_clear--; + if (use_depth || blend_key || use_discard) { + clear_buffer

(colortex, colortex.clear_val, 0, colortex.width, yi, + yi + 1, layer); + } else if (startx > 0 || endx < colortex.width) { + clear_buffer

(colortex, colortex.clear_val, 0, colortex.width, yi, + yi + 1, layer, startx, endx); + } + } + } + fragment_shader->gl_FragCoordXY.x = init_interp(startx + 0.5f, 1); + fragment_shader->gl_FragCoordXY.y = y; + { + Interpolants step = (ro - lo) * (1.0f / (rx - lx)); + Interpolants o = lo + step * (startx + 0.5f - lx); + fragment_shader->init_span(&o, &step, 4.0f); + } + if (!use_discard) { + if (use_depth) { + if (span >= 8) { + if (ctx->depthfunc == GL_LEQUAL) { + if (ctx->depthmask) + draw_depth_span(z, buf, depth, span); + else + draw_depth_span(z, buf, depth, span); + } else { + if (ctx->depthmask) + draw_depth_span(z, buf, depth, span); + else + draw_depth_span(z, buf, depth, span); + } + buf += span & ~7; + depth += span & ~7; + span &= 7; + } + for (; span >= 4; span -= 4, buf += 4, depth += 4) { + commit_output(buf, z, depth); + } + if (span > 0) { + commit_output(buf, z, depth, span); + } + } else { + if (span >= 4) { + if (fragment_shader->has_draw_span(buf)) { + int len = span & ~3; + fragment_shader->draw_span(buf, len); + buf += len; + span &= 3; + } else { + do { + commit_output(buf); + buf += 4; + span -= 4; + } while (span >= 4); + } + } + if (span > 0) { + commit_output(buf, span); + } + } + } else { + if (use_depth) { + for (; span >= 4; span -= 4, buf += 4, depth += 4) { + commit_output(buf, z, depth); + } + if (span > 0) { + commit_output(buf, z, depth, span); + } + } else { + for (; span >= 4; span -= 4, buf += 4, depth += 4) { + commit_output(buf); + } + if (span > 0) { + commit_output(buf, span); + } + } + } + } + next_span: + lx += lm; + rx += rm; + y++; + lo += lom; + ro += rom; + fbuf += colortex.stride(sizeof(P)) / sizeof(P); + fdepth += depthtex.stride(sizeof(uint16_t)) / sizeof(uint16_t); + } +} + +static void draw_quad(int nump, Texture& colortex, int layer, + Texture& depthtex) { + Flats flat_outs; + Interpolants interp_outs[4] = {0}; + vertex_shader->run((char*)flat_outs, (char*)interp_outs, + sizeof(Interpolants)); + Float w = 1.0f / vertex_shader->gl_Position.w; + vec3 clip = vertex_shader->gl_Position.sel(X, Y, Z) * w; + vec3 screen = (clip + 1) * vec3(ctx->viewport.width / 2, + ctx->viewport.height / 2, 0.5f) + + vec3(ctx->viewport.x, ctx->viewport.y, 0); + Point p[4] = {{screen.x.x, screen.y.x}, + {screen.x.y, screen.y.y}, + {screen.x.z, screen.y.z}, + {screen.x.w, screen.y.w}}; + + auto top_left = min(min(p[0], p[1]), p[2]); + auto bot_right = max(max(p[0], p[1]), p[2]); + if (nump > 3) { + top_left = min(top_left, p[3]); + bot_right = max(bot_right, p[3]); + } + // debugf("bbox: %f %f %f %f\n", top_left.x, top_left.y, bot_right.x, + // bot_right.y); + + float fx0 = 0; + float fy0 = 0; + float fx1 = colortex.width; + float fy1 = colortex.height; + if (ctx->scissortest) { + fx0 = max(fx0, float(ctx->scissor.x)); + fy0 = max(fy0, float(ctx->scissor.y)); + fx1 = min(fx1, float(ctx->scissor.x + ctx->scissor.width)); + fy1 = min(fy1, float(ctx->scissor.y + ctx->scissor.height)); + } + + if (top_left.x >= fx1 || top_left.y >= fy1 || bot_right.x <= fx0 || + bot_right.y <= fy0) { + return; + } + + // SSE2 does not support unsigned comparison, so bias Z to be negative. + uint16_t z = uint16_t(0xFFFF * screen.z.x) - 0x8000; + fragment_shader->gl_FragCoordZW.x = screen.z.x; + fragment_shader->gl_FragCoordZW.y = w.x; + + fragment_shader->init_primitive(flat_outs); + + if (colortex.internal_format == GL_RGBA8) { + draw_quad_spans(nump, p, z, interp_outs, colortex, layer, + depthtex, fx0, fy0, fx1, fy1); + } else if (colortex.internal_format == GL_R8) { + draw_quad_spans(nump, p, z, interp_outs, colortex, layer, depthtex, + fx0, fy0, fx1, fy1); + } else { + assert(false); + } +} + +void VertexArray::validate() { + int last_enabled = -1; + for (int i = 0; i <= max_attrib; i++) { + if (attribs[i].enabled) { + VertexAttrib& attr = attribs[i]; + // VertexArray &v = ctx->vertex_arrays[attr.vertex_array]; + Buffer& vertex_buf = ctx->buffers[attr.vertex_buffer]; + attr.buf = vertex_buf.buf; + attr.buf_size = vertex_buf.size; + // debugf("%d %x %d %d %d %d\n", i, attr.type, attr.size, attr.stride, + // attr.offset, attr.divisor); + last_enabled = i; + } + } + max_attrib = last_enabled; +} + +extern "C" { + +void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, + void* indicesptr, GLsizei instancecount) { + assert(mode == GL_TRIANGLES || mode == GL_QUADS); + assert(type == GL_UNSIGNED_SHORT || type == GL_UNSIGNED_INT); + assert(count == 6); + assert(indicesptr == nullptr); + if (count <= 0 || instancecount <= 0 || indicesptr) { + return; + } + + Framebuffer& fb = *get_framebuffer(GL_DRAW_FRAMEBUFFER); + Texture& colortex = ctx->textures[fb.color_attachment]; + if (!colortex.buf) { + return; + } + assert(colortex.internal_format == GL_RGBA8 || + colortex.internal_format == GL_R8); + Texture& depthtex = ctx->textures[ctx->depthtest ? fb.depth_attachment : 0]; + if (depthtex.buf) { + assert(depthtex.internal_format == GL_DEPTH_COMPONENT16); + assert(colortex.width == depthtex.width && + colortex.height == depthtex.height); + } + + Buffer& indices_buf = ctx->buffers[ctx->element_array_buffer_binding]; + if (!indices_buf.buf) { + return; + } + + // debugf("current_vertex_array %d\n", ctx->current_vertex_array); + // debugf("indices size: %d\n", indices_buf.size); + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (ctx->validate_vertex_array) { + ctx->validate_vertex_array = false; + v.validate(); + } + +#ifndef NDEBUG + // uint64_t start = get_time_value(); +#endif + + ctx->shaded_rows = 0; + ctx->shaded_pixels = 0; + + uint16_t* indices = (uint16_t*)indices_buf.buf; + if (type == GL_UNSIGNED_INT) { + assert(indices_buf.size == size_t(count) * 4); + indices = new uint16_t[count]; + for (GLsizei i = 0; i < count; i++) { + uint32_t val = ((uint32_t*)indices_buf.buf)[i]; + assert(val <= 0xFFFFU); + indices[i] = val; + } + } else if (type == GL_UNSIGNED_SHORT) { + assert(indices_buf.size == size_t(count) * 2); + } else { + assert(0); + } + + vertex_shader->init_batch(program_impl); + fragment_shader->init_batch(program_impl); + if (count == 6 && indices[3] == indices[2] && indices[4] == indices[1]) { + uint16_t quad_indices[4] = {indices[0], indices[1], indices[5], indices[2]}; + vertex_shader->load_attribs(program_impl, v.attribs, quad_indices, 0, 0, 4); + // debugf("emulate quad %d %d %d %d\n", indices[0], indices[1], indices[5], + // indices[2]); + draw_quad(4, colortex, fb.layer, depthtex); + for (GLsizei instance = 1; instance < instancecount; instance++) { + vertex_shader->load_attribs(program_impl, v.attribs, nullptr, 0, instance, + 4); + draw_quad(4, colortex, fb.layer, depthtex); + } + } else { + for (GLsizei instance = 0; instance < instancecount; instance++) { + if (mode == GL_QUADS) + for (GLsizei i = 0; i + 4 <= count; i += 4) { + vertex_shader->load_attribs(program_impl, v.attribs, indices, i, + instance, 4); + // debugf("native quad %d %d %d %d\n", indices[i], indices[i+1], + // indices[i+2], indices[i+3]); + draw_quad(4, colortex, fb.layer, depthtex); + } + else + for (GLsizei i = 0; i + 3 <= count; i += 3) { + if (i + 6 <= count && indices[i + 3] == indices[i + 2] && + indices[i + 4] == indices[i + 1]) { + uint16_t quad_indices[4] = {indices[i], indices[i + 1], + indices[i + 5], indices[i + 2]}; + vertex_shader->load_attribs(program_impl, v.attribs, quad_indices, + 0, instance, 4); + // debugf("emulate quad %d %d %d %d\n", indices[i], indices[i+1], + // indices[i+5], indices[i+2]); + draw_quad(4, colortex, fb.layer, depthtex); + i += 3; + } else { + vertex_shader->load_attribs(program_impl, v.attribs, indices, i, + instance, 3); + // debugf("triangle %d %d %d %d\n", indices[i], indices[i+1], + // indices[i+2]); + draw_quad(3, colortex, fb.layer, depthtex); + } + } + } + } + + if (indices != (uint16_t*)indices_buf.buf) { + delete[] indices; + } + + if (ctx->samples_passed_query) { + Query& q = ctx->queries[ctx->samples_passed_query]; + q.value += ctx->shaded_pixels; + } + +#ifndef NDEBUG + // uint64_t end = get_time_value(); + // debugf("draw(%d): %fms for %d pixels in %d rows (avg %f pixels/row, %f + // ns/pixel)\n", instancecount, double(end - start)/(1000.*1000.), + // ctx->shaded_pixels, ctx->shaded_rows, + // double(ctx->shaded_pixels)/ctx->shaded_rows, double(end - + // start)/max(ctx->shaded_pixels, 1)); +#endif +} + +void Finish() {} + +void MakeCurrent(void* ctx_ptr) { + ctx = (Context*)ctx_ptr; + if (ctx) { + setup_program(ctx->current_program); + blend_key = ctx->blend ? ctx->blend_key : BLEND_KEY_NONE; + } else { + setup_program(0); + blend_key = BLEND_KEY_NONE; + } +} + +void* CreateContext() { return new Context; } + +void DestroyContext(void* ctx_ptr) { + if (!ctx_ptr) { + return; + } + if (ctx == ctx_ptr) { + MakeCurrent(nullptr); + } + delete (Context*)ctx_ptr; +} + +void Composite(GLuint srcId, GLint srcX, GLint srcY, GLsizei srcWidth, + GLsizei srcHeight, GLint dstX, GLint dstY, GLboolean opaque, + GLboolean flip) { + Framebuffer& fb = ctx->framebuffers[0]; + if (!fb.color_attachment) { + return; + } + Texture& srctex = ctx->textures[srcId]; + if (!srctex.buf) return; + prepare_texture(srctex); + Texture& dsttex = ctx->textures[fb.color_attachment]; + if (!dsttex.buf) return; + assert(srctex.bpp() == 4); + const int bpp = 4; + size_t src_stride = srctex.stride(bpp); + size_t dest_stride = dsttex.stride(bpp); + if (srcY < 0) { + dstY -= srcY; + srcHeight += srcY; + srcY = 0; + } + if (dstY < 0) { + srcY -= dstY; + srcHeight += dstY; + dstY = 0; + } + if (srcY + srcHeight > srctex.height) { + srcHeight = srctex.height - srcY; + } + if (dstY + srcHeight > dsttex.height) { + srcHeight = dsttex.height - dstY; + } + IntRect skip = {dstX, dstY, srcWidth, srcHeight}; + prepare_texture(dsttex, &skip); + char* dest = dsttex.buf + + (flip ? dsttex.height - 1 - dstY : dstY) * dest_stride + + dstX * bpp; + char* src = srctex.buf + srcY * src_stride + srcX * bpp; + if (flip) { + dest_stride = -dest_stride; + } + if (opaque) { + for (int y = 0; y < srcHeight; y++) { + memcpy(dest, src, srcWidth * bpp); + dest += dest_stride; + src += src_stride; + } + } else { + for (int y = 0; y < srcHeight; y++) { + char* end = src + srcWidth * bpp; + while (src + 4 * bpp <= end) { + WideRGBA8 srcpx = unpack(unaligned_load(src)); + WideRGBA8 dstpx = unpack(unaligned_load(dest)); + PackedRGBA8 r = pack(srcpx + dstpx - muldiv255(dstpx, alphas(srcpx))); + unaligned_store(dest, r); + src += 4 * bpp; + dest += 4 * bpp; + } + if (src < end) { + WideRGBA8 srcpx = unpack(unaligned_load(src)); + WideRGBA8 dstpx = unpack(unaligned_load(dest)); + U32 r = bit_cast( + pack(srcpx + dstpx - muldiv255(dstpx, alphas(srcpx)))); + unaligned_store(dest, r.x); + if (src + bpp < end) { + unaligned_store(dest + bpp, r.y); + if (src + 2 * bpp < end) { + unaligned_store(dest + 2 * bpp, r.z); + } + } + dest += end - src; + src = end; + } + dest += dest_stride - srcWidth * bpp; + src += src_stride - srcWidth * bpp; + } + } +} + +} // extern "C" diff --git a/swgl/src/gl_defs.h b/swgl/src/gl_defs.h new file mode 100644 index 0000000000..3cd774e49b --- /dev/null +++ b/swgl/src/gl_defs.h @@ -0,0 +1,175 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +typedef int8_t GLbyte; +typedef uint8_t GLubyte; +typedef int16_t GLshort; +typedef uint16_t GLushort; +typedef int32_t GLint; +typedef uint32_t GLuint; +typedef int64_t GLint64; +typedef uint64_t GLuint64; + +typedef float GLfloat; +typedef double GLdouble; + +typedef uint32_t GLenum; +typedef int32_t GLboolean; +typedef uint32_t GLbitfield; + +typedef int32_t GLsizei; +typedef size_t GLsizeiptr; +typedef intptr_t GLintptr; + +#define GL_NO_ERROR 0 + +#define GL_RGBA32F 0x8814 +#define GL_RGBA8 0x8058 +#define GL_R8 0x8229 +#define GL_RGBA32I 0x8D82 +#define GL_BGRA8 0x93A1 + +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 + +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_RGBA_INTEGER 0x8D99 +#define GL_BGRA 0x80E1 + +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 + +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 + +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER 0x8D41 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_STENCIL_BUFFER_BIT 0x00000400 + +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_ALIGNMENT 0x0CF5 + +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_TIME_ELAPSED 0x88BF +#define GL_SAMPLES_PASSED 0x8914 + +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_3D 0x806F +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF + +#define GL_VERTEX_SHADER 0x8B31 +#define GL_FRAGMENT_SHADER 0x8B30 + +#define GL_BLEND 0x0BE2 +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_SRC1_ALPHA 0x8589 +#define GL_SRC1_COLOR 0x88F9 +#define GL_ONE_MINUS_SRC1_COLOR 0x88FA +#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB + +#define GL_FUNC_ADD 0x8006 + +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 + +#define GL_SCISSOR_TEST 0x0C11 + +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_NUM_EXTENSIONS 0x821D + +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 diff --git a/swgl/src/glsl.h b/swgl/src/glsl.h new file mode 100644 index 0000000000..cc94aa3d2d --- /dev/null +++ b/swgl/src/glsl.h @@ -0,0 +1,3053 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Some of this is copied from Skia and is governed by a BSD-style license +// Every function in this file should be marked static and inline using SI. +#define SI ALWAYS_INLINE static + +#include "vector_type.h" + +namespace glsl { + +float make_float(float n) { return n; } + +float make_float(int32_t n) { return float(n); } + +float make_float(uint32_t n) { return float(n); } + +float make_float(bool n) { return float(n); } + +template +Float make_float(T v) { + return CONVERT(v, Float); +} + +int32_t make_int(uint32_t n) { return n; } + +int32_t make_int(int32_t n) { return n; } + +int32_t make_int(float n) { return int32_t(n); } + +int32_t make_int(bool n) { return int32_t(n); } + +template +I32 make_int(T v) { + return CONVERT(v, I32); +} + +uint32_t make_uint(uint32_t n) { return n; } + +uint32_t make_uint(int32_t n) { return n; } + +uint32_t make_uint(float n) { return uint32_t(n); } + +uint32_t make_uint(bool n) { return uint32_t(n); } + +template +U32 make_uint(T v) { + return CONVERT(v, U32); +} + +template +T force_scalar(T n) { + return n; +} + +float force_scalar(Float f) { return f[0]; } + +int32_t force_scalar(I32 i) { return i[0]; } + +struct vec4; +struct ivec2; + +SI int32_t if_then_else(int32_t c, int32_t t, int32_t e) { return c ? t : e; } + +SI float if_then_else(int32_t c, float t, float e) { return c ? t : e; } + +SI Float if_then_else(I32 c, float t, float e) { + return Float((c & I32(Float(t))) | (~c & I32(Float(e)))); +} + +SI I32 if_then_else(I32 c, int32_t t, int32_t e) { + return (c & I32(t)) | (~c & I32(e)); +} + +SI Float if_then_else(I32 c, Float t, Float e) { + return Float((c & I32(t)) | (~c & I32(e))); +} + +SI Float if_then_else(int32_t c, Float t, Float e) { return c ? t : e; } + +SI Bool if_then_else(I32 c, Bool t, Bool e) { return (c & t) | (~c & e); } + +SI Bool if_then_else(int32_t c, Bool t, Bool e) { return c ? t : e; } + +SI int32_t min(int32_t a, int32_t b) { return a < b ? a : b; } +SI int32_t max(int32_t a, int32_t b) { return a > b ? a : b; } + +SI int32_t clamp(int32_t a, int32_t minVal, int32_t maxVal) { + return min(max(a, minVal), maxVal); +} + +SI float min(float a, float b) { return a < b ? a : b; } +SI float max(float a, float b) { return a > b ? a : b; } + +SI float clamp(float a, float minVal, float maxVal) { + return min(max(a, minVal), maxVal); +} + +SI Float min(Float a, Float b) { +#if USE_SSE2 + return _mm_min_ps(a, b); +#elif USE_NEON + return vminq_f32(a, b); +#else + return if_then_else(a < b, a, b); +#endif +} + +SI Float max(Float a, Float b) { +#if USE_SSE2 + return _mm_max_ps(a, b); +#elif USE_NEON + return vmaxq_f32(a, b); +#else + return if_then_else(a > b, a, b); +#endif +} + +SI Float clamp(Float a, Float minVal, Float maxVal) { + return min(max(a, minVal), maxVal); +} + +#define sqrt __glsl_sqrt + +SI float sqrt(float x) { return sqrtf(x); } + +SI Float sqrt(Float v) { +#if USE_SSE2 + return _mm_sqrt_ps(v); +#elif USE_NEON + Float e = vrsqrteq_f32(v); + e *= vrsqrtsq_f32(v, e * e); + e *= vrsqrtsq_f32(v, e * e); + return v * e; +#else + return (Float){sqrtf(v.x), sqrtf(v.y), sqrtf(v.z), sqrtf(v.w)}; +#endif +} + +SI float inversesqrt(float x) { return 1.0f / sqrtf(x); } + +SI Float inversesqrt(Float v) { +#if USE_SSE2 + return _mm_rsqrt_ps(v); +#elif USE_NEON + Float e = vrsqrteq_f32(v); + return vrsqrtsq_f32(v, e * e) * e; +#else + return 1.0f / sqrt(v); +#endif +} + +SI float step(float edge, float x) { return float(x >= edge); } + +SI Float step(Float edge, Float x) { + return if_then_else(x < edge, Float(0), Float(1)); +} + +/* +enum RGBA { + R, + G, + B, + A +};*/ + +enum XYZW { + X = 0, + Y = 1, + Z = 2, + W = 3, + R = 0, + G = 1, + B = 2, + A = 3, +}; + +struct bvec2_scalar { + bool x; + bool y; + + bvec2_scalar() : bvec2_scalar(false) {} + constexpr bvec2_scalar(bool a) : x(a), y(a) {} + constexpr bvec2_scalar(bool x, bool y) : x(x), y(y) {} +}; + +struct bvec2 { + bvec2() : bvec2(0) {} + bvec2(Bool a) : x(a), y(a) {} + bvec2(Bool x, Bool y) : x(x), y(y) {} + Bool& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + Bool sel(XYZW c1) { return select(c1); } + + bvec2 operator~() { return bvec2(~x, ~y); } + + Bool x; + Bool y; +}; + +bvec2_scalar make_bvec2(bool n) { return bvec2_scalar{n, n}; } + +bvec2_scalar make_bvec2(bool x, bool y) { return bvec2_scalar{x, y}; } + +template +bvec2 make_bvec2(const N& n) { + return bvec2(n); +} + +template +bvec2 make_bvec2(const X& x, const Y& y) { + return bvec2(x, y); +} + +struct vec4_scalar; + +struct vec2_scalar { + typedef struct vec2 vector_type; + typedef float element_type; + + float x; + float y; + + constexpr vec2_scalar() : vec2_scalar(0.0f) {} + constexpr vec2_scalar(float a) : x(a), y(a) {} + constexpr vec2_scalar(int a) : x(a), y(a) {} + constexpr vec2_scalar(float x, float y) : x(x), y(y) {} + + float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + float& sel(XYZW c1) { return select(c1); } + vec2_scalar sel(XYZW c1, XYZW c2) { + return vec2_scalar(select(c1), select(c2)); + } + vec4_scalar sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4); + + friend bool operator==(const vec2_scalar& l, const vec2_scalar& r) { + return l.x == r.x && l.y == r.y; + } + + friend bool operator!=(const vec2_scalar& l, const vec2_scalar& r) { + return l.x != r.x || l.y != r.y; + } + + friend vec2_scalar operator*(float a, vec2_scalar b) { + return vec2_scalar(a * b.x, a * b.y); + } + friend vec2_scalar operator*(vec2_scalar a, float b) { + return vec2_scalar(a.x * b, a.y * b); + } + friend vec2_scalar operator*(vec2_scalar a, vec2_scalar b) { + return vec2_scalar(a.x * b.x, a.y * b.y); + } + friend vec2_scalar operator/(vec2_scalar a, vec2_scalar b) { + return vec2_scalar(a.x / b.x, a.y / b.y); + } + + friend vec2_scalar operator-(vec2_scalar a, vec2_scalar b) { + return vec2_scalar(a.x - b.x, a.y - b.y); + } + friend vec2_scalar operator+(vec2_scalar a, vec2_scalar b) { + return vec2_scalar(a.x + b.x, a.y + b.y); + } + friend vec2_scalar operator+(vec2_scalar a, float b) { + return vec2_scalar(a.x + b, a.y + b); + } + + vec2_scalar operator-() { return vec2_scalar(-x, -y); } + + vec2_scalar operator+=(vec2_scalar a) { + x += a.x; + y += a.y; + return *this; + } + + vec2_scalar operator-=(vec2_scalar a) { + x -= a.x; + y -= a.y; + return *this; + } +}; + +struct vec2_scalar_ref { + vec2_scalar_ref(float& x, float& y) : x(x), y(y) {} + float& x; + float& y; + + float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + float& sel(XYZW c1) { return select(c1); } + + vec2_scalar_ref& operator=(const vec2_scalar& a) { + x = a.x; + y = a.y; + return *this; + } + vec2_scalar_ref& operator*=(vec2_scalar a) { + x *= a.x; + y *= a.y; + return *this; + } + operator vec2_scalar() const { return vec2_scalar{x, y}; } +}; + +struct vec2 { + typedef struct vec2 vector_type; + typedef float element_type; + + constexpr vec2() : vec2(Float(0.0f)) {} + constexpr vec2(Float a) : x(a), y(a) {} + vec2(Float x, Float y) : x(x), y(y) {} + constexpr vec2(vec2_scalar s) : x(s.x), y(s.y) {} + constexpr vec2(vec2_scalar s0, vec2_scalar s1, vec2_scalar s2, vec2_scalar s3) + : x(Float{s0.x, s1.x, s2.x, s3.x}), y(Float{s0.y, s1.y, s2.y, s3.y}) {} + vec2(ivec2 a); + Float x; + Float y; + + Float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + Float& sel(XYZW c1) { return select(c1); } + vec2 sel(XYZW c1, XYZW c2) { return vec2(select(c1), select(c2)); } + + vec4 sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4); + + vec2 operator*=(Float a) { + x *= a; + y *= a; + return *this; + } + vec2 operator*=(vec2 a) { + x *= a.x; + y *= a.y; + return *this; + } + + vec2 operator/=(Float a) { + x /= a; + y /= a; + return *this; + } + vec2 operator/=(vec2 a) { + x /= a.x; + y /= a.y; + return *this; + } + + vec2 operator+=(vec2 a) { + x += a.x; + y += a.y; + return *this; + } + vec2 operator-=(vec2 a) { + x -= a.x; + y -= a.y; + return *this; + } + vec2 operator-=(Float a) { + x -= a; + y -= a; + return *this; + } + + vec2 operator-() { return vec2(-x, -y); } + + friend I32 operator==(const vec2& l, const vec2& r) { + return l.x == r.x && l.y == r.y; + } + + friend I32 operator!=(const vec2& l, const vec2& r) { + return l.x != r.x || l.y != r.y; + } + + friend vec2 operator*(vec2 a, Float b) { return vec2(a.x * b, a.y * b); } + friend vec2 operator*(vec2 a, vec2 b) { return vec2(a.x * b.x, a.y * b.y); } + friend vec2 operator*(Float a, vec2 b) { return vec2(a * b.x, a * b.y); } + + friend vec2 operator/(vec2 a, vec2 b) { return vec2(a.x / b.x, a.y / b.y); } + friend vec2 operator/(vec2 a, Float b) { return vec2(a.x / b, a.y / b); } + + friend vec2 operator-(vec2 a, vec2 b) { return vec2(a.x - b.x, a.y - b.y); } + friend vec2 operator-(vec2 a, Float b) { return vec2(a.x - b, a.y - b); } + friend vec2 operator-(Float a, vec2 b) { return vec2(a - b.x, a - b.y); } + friend vec2 operator+(vec2 a, vec2 b) { return vec2(a.x + b.x, a.y + b.y); } + friend vec2 operator+(vec2 a, Float b) { return vec2(a.x + b, a.y + b); } + friend vec2 operator+(Float a, vec2 b) { return vec2(a + b.x, a + b.y); } +}; + +vec2_scalar force_scalar(const vec2& v) { + return vec2_scalar{force_scalar(v.x), force_scalar(v.y)}; +} + +vec2_scalar make_vec2(float n) { return vec2_scalar{n, n}; } + +vec2_scalar make_vec2(float x, float y) { return vec2_scalar{x, y}; } + +template +vec2 make_vec2(const N& n) { + return vec2(n); +} + +template +vec2 make_vec2(const X& x, const Y& y) { + return vec2(x, y); +} + +vec2 operator*(vec2_scalar a, Float b) { return vec2(a.x * b, a.y * b); } + +vec2 operator*(Float a, vec2_scalar b) { return vec2(a * b.x, a * b.y); } + +SI vec2 min(vec2 a, vec2 b) { return vec2(min(a.x, b.x), min(a.y, b.y)); } + +SI vec2_scalar min(vec2_scalar a, vec2_scalar b) { + return vec2_scalar{min(a.x, b.x), min(a.y, b.y)}; +} + +SI vec2 if_then_else(I32 c, vec2 t, vec2 e) { + return vec2(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y)); +} + +SI vec2 if_then_else(int32_t c, vec2 t, vec2 e) { return c ? t : e; } + +vec2 step(vec2 edge, vec2 x) { + return vec2(step(edge.x, x.x), step(edge.y, x.y)); +} + +vec2 max(vec2 a, vec2 b) { return vec2(max(a.x, b.x), max(a.y, b.y)); } +vec2 max(vec2 a, Float b) { return vec2(max(a.x, b), max(a.y, b)); } + +SI vec2_scalar max(vec2_scalar a, vec2_scalar b) { + return vec2_scalar{max(a.x, b.x), max(a.y, b.y)}; +} +SI vec2_scalar max(vec2_scalar a, float b) { + return vec2_scalar{max(a.x, b), max(a.y, b)}; +} + +Float length(vec2 a) { return sqrt(a.x * a.x + a.y * a.y); } + +float length(vec2_scalar a) { return hypotf(a.x, a.y); } + +SI Float distance(vec2 a, vec2 b) { return length(a - b); } + +SI vec2 normalize(vec2 a) { return a / length(a); } + +#define abs __glsl_abs + +int32_t abs(int32_t a) { return a < 0 ? -a : a; } + +float abs(float a) { return fabsf(a); } + +Float abs(Float v) { +#if USE_NEON + return vabsq_f32(v); +#else + return Float(I32(v) & I32(0.0f - v)); +#endif +} + +Float cast(U32 v) { return CONVERT((I32)v, Float); } +Float cast(I32 v) { return CONVERT((I32)v, Float); } +I32 cast(Float v) { return CONVERT(v, I32); } + +#define floor __glsl_floor + +float floor(float a) { return floorf(a); } + +Float floor(Float v) { + Float roundtrip = cast(cast(v)); + return roundtrip - if_then_else(roundtrip > v, Float(1), Float(0)); +} + +vec2 floor(vec2 v) { return vec2(floor(v.x), floor(v.y)); } + +vec2_scalar floor(vec2_scalar v) { + return vec2_scalar{floorf(v.x), floorf(v.y)}; +} + +#define ceil __glsl_ceil + +float ceil(float a) { return ceilf(a); } + +Float ceil(Float v) { + Float roundtrip = cast(cast(v)); + return roundtrip + if_then_else(roundtrip < v, Float(1), Float(0)); +} + +SI int32_t roundto(float v, float scale) { return int32_t(v * scale + 0.5f); } + +SI I32 roundto(Float v, Float scale) { +#if USE_SSE2 + return _mm_cvtps_epi32(v * scale); +#else + return cast(v * scale + 0.5f); +#endif +} + +#define round __glsl_round + +float round(float a) { return roundf(a); } + +float fract(float a) { return a - floor(a); } + +Float round(Float v) { return floor(v + 0.5f); } + +Float fract(Float v) { return v - floor(v); } + +// X derivatives can be approximated by dFdx(x) = x[1] - x[0]. +// Y derivatives are not easily available since we operate in terms of X spans +// only. To work around, assume dFdy(p.x) = dFdx(p.y), which only holds for +// uniform scaling, and thus abs(dFdx(p.x)) + abs(dFdy(p.x)) = abs(dFdx(p.x)) + +// abs(dFdx(p.y)) which mirrors abs(dFdx(p.y)) + abs(dFdy(p.y)) = abs(dFdx(p.y)) +// + abs(dFdx(p.x)). +vec2 fwidth(vec2 p) { + Float d = abs(SHUFFLE(p.x, p.y, 1, 1, 5, 5) - SHUFFLE(p.x, p.y, 0, 0, 4, 4)); + return vec2(d.xyxy + d.zwzw); +} + +// See +// http://www.machinedlearnings.com/2011/06/fast-approximate-logarithm-exponential.html. +Float approx_log2(Float x) { + // e - 127 is a fair approximation of log2(x) in its own right... + Float e = cast(bit_cast(x)) * (1.0f / (1 << 23)); + + // ... but using the mantissa to refine its error is _much_ better. + Float m = bit_cast((bit_cast(x) & 0x007fffff) | 0x3f000000); + return e - 124.225514990f - 1.498030302f * m - + 1.725879990f / (0.3520887068f + m); +} +Float approx_pow2(Float x) { + Float f = fract(x); + return bit_cast( + roundto(1.0f * (1 << 23), x + 121.274057500f - 1.490129070f * f + + 27.728023300f / (4.84252568f - f))); +} + +// From skia +Float pow(Float x, Float y) { + return if_then_else((x == 0) | (x == 1), x, approx_pow2(approx_log2(x) * y)); +} + +Float exp(Float y) { + float x = 2.718281828459045235360287471352; + return approx_pow2(log2f(x) * y); +} + +struct ivec4; + +struct ivec2_scalar { + typedef int32_t element_type; + + int32_t x; + int32_t y; + + ivec2_scalar() : ivec2_scalar(0) {} + constexpr ivec2_scalar(int32_t a) : x(a), y(a) {} + constexpr ivec2_scalar(int32_t x, int32_t y) : x(x), y(y) {} + + int32_t& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + int32_t& sel(XYZW c1) { return select(c1); } + ivec2_scalar sel(XYZW c1, XYZW c2) { + return ivec2_scalar{select(c1), select(c2)}; + } + + ivec2_scalar& operator+=(ivec2_scalar a) { + x += a.x; + y += a.y; + return *this; + } + ivec2_scalar& operator+=(int n) { + x += n; + y += n; + return *this; + } + + ivec2_scalar& operator>>=(int shift) { + x >>= shift; + y >>= shift; + return *this; + } + + friend ivec2_scalar operator&(ivec2_scalar a, int b) { + return ivec2_scalar{a.x & b, a.y & b}; + } + + friend ivec2_scalar operator+(ivec2_scalar a, ivec2_scalar b) { + return ivec2_scalar{a.x + b.x, a.y + b.y}; + } +}; + +struct ivec2 { + typedef int32_t element_type; + + ivec2() : ivec2(I32(0)) {} + ivec2(I32 a) : x(a), y(a) {} + ivec2(I32 x, I32 y) : x(x), y(y) {} + ivec2(vec2 a) : x(cast(a.x)), y(cast(a.y)) {} + ivec2(U32 x, U32 y) : x(CONVERT(x, I32)), y(CONVERT(y, I32)) {} + constexpr ivec2(ivec2_scalar s) : x(s.x), y(s.y) {} + constexpr ivec2(ivec2_scalar s0, ivec2_scalar s1, ivec2_scalar s2, + ivec2_scalar s3) + : x(I32{s0.x, s1.x, s2.x, s3.x}), y(I32{s0.y, s1.y, s2.y, s3.y}) {} + I32 x; + I32 y; + + I32& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + I32& sel(XYZW c1) { return select(c1); } + + ivec2 sel(XYZW c1, XYZW c2) { return ivec2(select(c1), select(c2)); } + + ivec4 sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4); + + ivec2& operator*=(I32 a) { + x *= a; + y *= a; + return *this; + } + ivec2& operator+=(ivec2 a) { + x += a.x; + y += a.y; + return *this; + } + ivec2& operator>>=(int shift) { + x >>= shift; + y >>= shift; + return *this; + } + + friend ivec2 operator*(ivec2 a, I32 b) { return ivec2(a.x * b, a.y * b); } + friend ivec2 operator&(ivec2 a, ivec2 b) { + return ivec2(a.x & b.x, a.y & b.y); + } + friend ivec2 operator&(ivec2 a, I32 b) { return ivec2(a.x & b, a.y & b); } + friend ivec2 operator+(ivec2 a, ivec2 b) { + return ivec2(a.x + b.x, a.y + b.y); + } +}; + +vec2::vec2(ivec2 a) : x(cast(a.x)), y(cast(a.y)) {} + +ivec2_scalar make_ivec2(int32_t n) { return ivec2_scalar{n, n}; } + +ivec2_scalar make_ivec2(uint32_t n) { + return ivec2_scalar{int32_t(n), int32_t(n)}; +} + +ivec2_scalar make_ivec2(int32_t x, int32_t y) { return ivec2_scalar{x, y}; } + +ivec2_scalar make_ivec2(uint32_t x, uint32_t y) { + return ivec2_scalar{int32_t(x), int32_t(y)}; +} + +vec2_scalar make_vec2(const ivec2_scalar& v) { + return vec2_scalar{float(v.x), float(v.y)}; +} + +ivec2_scalar make_ivec2(const vec2_scalar& v) { + return ivec2_scalar{int32_t(v.x), int32_t(v.y)}; +} + +template +ivec2 make_ivec2(const N& n) { + return ivec2(n); +} + +template +ivec2 make_ivec2(const X& x, const Y& y) { + return ivec2(x, y); +} + +ivec2_scalar force_scalar(const ivec2& v) { + return ivec2_scalar{force_scalar(v.x), force_scalar(v.y)}; +} + +struct ivec3_scalar { + int32_t x; + int32_t y; + int32_t z; + + ivec3_scalar() : ivec3_scalar(0) {} + constexpr ivec3_scalar(int32_t a) : x(a), y(a), z(a) {} + constexpr ivec3_scalar(int32_t x, int32_t y, int32_t z) : x(x), y(y), z(z) {} + + int32_t& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + default: + UNREACHABLE; + } + } + int32_t& sel(XYZW c1) { return select(c1); } + ivec2_scalar sel(XYZW c1, XYZW c2) { + return ivec2_scalar{select(c1), select(c2)}; + } +}; + +struct ivec3 { + ivec3() : ivec3(0) {} + ivec3(I32 a) : x(a), y(a), z(a) {} + ivec3(I32 x, I32 y, I32 z) : x(x), y(y), z(z) {} + ivec3(ivec2 a, I32 b) : x(a.x), y(a.y), z(b) {} + ivec3(vec2 a, Float b) : x(cast(a.x)), y(cast(a.y)), z(cast(b)) {} + I32 x; + I32 y; + I32 z; + + friend ivec3 operator+(ivec3 a, ivec3 b) { + return ivec3(a.x + b.x, a.y + b.y, a.z + b.z); + } +}; + +vec2_scalar make_vec2(ivec3_scalar s) { + return vec2_scalar{float(s.x), float(s.y)}; +} + +ivec3_scalar make_ivec3(int32_t n) { return ivec3_scalar{n, n, n}; } + +ivec3_scalar make_ivec3(const ivec2_scalar& v, int32_t z) { + return ivec3_scalar{v.x, v.y, z}; +} + +ivec3_scalar make_ivec3(int32_t x, int32_t y, int32_t z) { + return ivec3_scalar{x, y, z}; +} + +template +ivec3 make_ivec3(const N& n) { + return ivec3(n); +} + +template +ivec3 make_ivec3(const X& x, const Y& y) { + return ivec3(x, y); +} + +template +ivec3 make_ivec3(const X& x, const Y& y, const Z& z) { + return ivec3(x, y, z); +} + +struct ivec4_scalar { + typedef int32_t element_type; + + int32_t x; + int32_t y; + int32_t z; + int32_t w; + + ivec4_scalar() : ivec4_scalar(0) {} + constexpr ivec4_scalar(int32_t a) : x(a), y(a), z(a), w(a) {} + constexpr ivec4_scalar(int32_t x, int32_t y, int32_t z, int32_t w) + : x(x), y(y), z(z), w(w) {} + + int32_t& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + case W: + return w; + default: + UNREACHABLE; + } + } + int32_t& sel(XYZW c1) { return select(c1); } + ivec2_scalar sel(XYZW c1, XYZW c2) { + return ivec2_scalar{select(c1), select(c2)}; + } + + friend ivec4_scalar operator&(int32_t a, ivec4_scalar b) { + return ivec4_scalar{a & b.x, a & b.y, a & b.z, a & b.w}; + } +}; + +struct ivec4 { + typedef int32_t element_type; + + ivec4() : ivec4(I32(0)) {} + ivec4(I32 a) : x(a), y(a), z(a), w(a) {} + ivec4(I32 x, I32 y, I32 z, I32 w) : x(x), y(y), z(z), w(w) {} + ivec4(ivec2 a, I32 b, I32 c) : x(a.x), y(a.y), z(b), w(c) {} + constexpr ivec4(ivec4_scalar s) : x(s.x), y(s.y), z(s.z), w(s.w) {} + constexpr ivec4(ivec4_scalar s0, ivec4_scalar s1, ivec4_scalar s2, + ivec4_scalar s3) + : x(I32{s0.x, s1.x, s2.x, s3.x}), + y(I32{s0.y, s1.y, s2.y, s3.y}), + z(I32{s0.z, s1.z, s2.z, s3.z}), + w(I32{s0.w, s1.w, s2.w, s3.w}) {} + + I32& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + case W: + return w; + default: + UNREACHABLE; + } + } + I32 sel(XYZW c1) { return select(c1); } + + ivec2 sel(XYZW c1, XYZW c2) { return ivec2(select(c1), select(c2)); } + + ivec3 sel(XYZW c1, XYZW c2, XYZW c3) { + return ivec3(select(c1), select(c2), select(c3)); + } + + friend ivec4 operator&(I32 a, ivec4 b) { + return ivec4(a & b.x, a & b.y, a & b.z, a & b.w); + } + + I32 x; + I32 y; + I32 z; + I32 w; +}; + +ivec4_scalar force_scalar(const ivec4& v) { + return ivec4_scalar{force_scalar(v.x), force_scalar(v.y), force_scalar(v.z), + force_scalar(v.w)}; +} + +ivec4_scalar make_ivec4(int32_t n) { return ivec4_scalar{n, n, n, n}; } + +ivec4_scalar make_ivec4(const ivec2_scalar& xy, int32_t z, int32_t w) { + return ivec4_scalar{xy.x, xy.y, z, w}; +} + +ivec4_scalar make_ivec4(int32_t x, int32_t y, int32_t z, int32_t w) { + return ivec4_scalar{x, y, z, w}; +} + +template +ivec4 make_ivec4(const N& n) { + return ivec4(n); +} + +template +ivec4 make_ivec4(const X& x, const Y& y, const Z& z) { + return ivec4(x, y, z); +} + +template +ivec4 make_ivec4(const X& x, const Y& y, const Z& z, const W& w) { + return ivec4(x, y, z, w); +} + +SI ivec2 if_then_else(I32 c, ivec2 t, ivec2 e) { + return ivec2(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y)); +} + +SI ivec2 if_then_else(int32_t c, ivec2 t, ivec2 e) { return c ? t : e; } + +SI ivec4 if_then_else(I32 c, ivec4 t, ivec4 e) { + return ivec4(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y), + if_then_else(c, t.z, e.z), if_then_else(c, t.w, e.w)); +} + +SI ivec4 if_then_else(int32_t c, ivec4 t, ivec4 e) { return c ? t : e; } + +ivec4 operator&(I32 a, ivec4_scalar b) { + return ivec4(a & b.x, a & b.y, a & b.z, a & b.w); +} + +struct bvec3_scalar { + bool x; + bool y; + bool z; + + bvec3_scalar() : bvec3_scalar(false) {} + constexpr bvec3_scalar(bool a) : x(a), y(a), z(a) {} + constexpr bvec3_scalar(bool x, bool y, bool z) : x(x), y(y), z(z) {} +}; + +struct bvec3 { + bvec3() : bvec3(0) {} + bvec3(Bool a) : x(a), y(a), z(a) {} + bvec3(Bool x, Bool y, Bool z) : x(x), y(y), z(z) {} + Bool& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + default: + UNREACHABLE; + } + } + Bool sel(XYZW c1) { return select(c1); } + + Bool x; + Bool y; + Bool z; +}; + +struct bvec4_scalar { + bool x; + bool y; + bool z; + bool w; + + bvec4_scalar() : bvec4_scalar(false) {} + constexpr bvec4_scalar(bool a) : x(a), y(a), z(a), w(a) {} + constexpr bvec4_scalar(bool x, bool y, bool z, bool w) + : x(x), y(y), z(z), w(w) {} +}; + +struct bvec4 { + bvec4() : bvec4(0) {} + bvec4(Bool a) : x(a), y(a), z(a), w(a) {} + bvec4(Bool x, Bool y, Bool z, Bool w) : x(x), y(y), z(z), w(w) {} + bvec4(bvec2 x, bvec2 y) : x(x.x), y(x.y), z(y.x), w(y.y) {} + Bool& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + case W: + return w; + } + } + Bool sel(XYZW c1) { return select(c1); } + + Bool x; + Bool y; + Bool z; + Bool w; +}; + +bvec4_scalar make_bvec4(bool n) { return bvec4_scalar{n, n, n, n}; } + +bvec4_scalar make_bvec4(bool x, bool y, bool z, bool w) { + return bvec4_scalar{x, y, z, w}; +} + +template +bvec4 make_bvec4(const N& n) { + return bvec4(n); +} + +template +bvec4 make_bvec4(const X& x, const Y& y) { + return bvec4(x, y); +} + +template +bvec4 make_bvec4(const X& x, const Y& y, const Z& z, const W& w) { + return bvec4(x, y, z, w); +} + +struct vec2_ref { + vec2_ref(Float& x, Float& y) : x(x), y(y) {} + Float& x; + Float& y; + + Float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + Float& sel(XYZW c1) { return select(c1); } + + vec2_ref& operator=(const vec2& a) { + x = a.x; + y = a.y; + return *this; + } + + vec2_ref& operator/=(Float a) { + x /= a; + y /= a; + return *this; + } + + vec2_ref& operator/=(vec2 a) { + x /= a.x; + y /= a.y; + return *this; + } + + vec2_ref& operator+=(vec2 a) { + x += a.x; + y += a.y; + return *this; + } + vec2_ref& operator-=(vec2 a) { + x -= a.x; + y -= a.y; + return *this; + } + vec2_ref& operator*=(vec2 a) { + x *= a.x; + y *= a.y; + return *this; + } +}; + +struct vec3_scalar { + typedef struct vec3 vector_type; + typedef float element_type; + + float x; + float y; + float z; + + constexpr vec3_scalar() : vec3_scalar(0.0f) {} + constexpr vec3_scalar(float a) : x(a), y(a), z(a) {} + constexpr vec3_scalar(float x, float y, float z) : x(x), y(y), z(z) {} + + float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + default: + UNREACHABLE; + } + } + float& sel(XYZW c1) { return select(c1); } + vec2_scalar sel(XYZW c1, XYZW c2) { + return vec2_scalar(select(c1), select(c2)); + } + vec3_scalar sel(XYZW c1, XYZW c2, XYZW c3) { + return vec3_scalar(select(c1), select(c2), select(c3)); + } + vec2_scalar_ref lsel(XYZW c1, XYZW c2) { + return vec2_scalar_ref(select(c1), select(c2)); + } + + friend vec3_scalar operator*(vec3_scalar a, float b) { + return vec3_scalar{a.x * b, a.y * b, a.z * b}; + } + + friend vec3_scalar operator-(vec3_scalar a, vec3_scalar b) { + return vec3_scalar{a.x - b.x, a.y - b.y, a.z - b.z}; + } + friend vec3_scalar operator+(vec3_scalar a, vec3_scalar b) { + return vec3_scalar{a.x + b.x, a.y + b.y, a.z + b.z}; + } + + friend vec3_scalar operator/(vec3_scalar a, float b) { + return vec3_scalar{a.x / b, a.y / b, a.z / b}; + } + + friend bool operator==(const vec3_scalar& l, const vec3_scalar& r) { + return l.x == r.x && l.y == r.y && l.z == r.z; + } +}; + +struct vec3_scalar_ref { + vec3_scalar_ref(float& x, float& y, float& z) : x(x), y(y), z(z) {} + float& x; + float& y; + float& z; + + float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + default: + UNREACHABLE; + } + } + float& sel(XYZW c1) { return select(c1); } + + vec3_scalar_ref& operator=(const vec3_scalar& a) { + x = a.x; + y = a.y; + z = a.z; + return *this; + } + + operator vec3_scalar() const { return vec3_scalar{x, y, z}; } +}; + +struct vec3 { + typedef struct vec3 vector_type; + typedef float element_type; + + constexpr vec3() : vec3(Float(0.0f)) {} + constexpr vec3(Float a) : x(a), y(a), z(a) {} + constexpr vec3(Float x, Float y, Float z) : x(x), y(y), z(z) {} + vec3(vec2 a, Float z) : x(a.x), y(a.y), z(z) {} + constexpr vec3(vec3_scalar s) : x(s.x), y(s.y), z(s.z) {} + constexpr vec3(vec3_scalar s0, vec3_scalar s1, vec3_scalar s2, vec3_scalar s3) + : x(Float{s0.x, s1.x, s2.x, s3.x}), + y(Float{s0.y, s1.y, s2.y, s3.y}), + z(Float{s0.z, s1.z, s2.z, s3.z}) {} + Float x; + Float y; + Float z; + + Float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + default: + UNREACHABLE; + } + } + Float& sel(XYZW c1) { return select(c1); } + + vec2 sel(XYZW c1, XYZW c2) { return vec2(select(c1), select(c2)); } + + vec3 sel(XYZW c1, XYZW c2, XYZW c3) { + return vec3(select(c1), select(c2), select(c3)); + } + + vec2_ref lsel(XYZW c1, XYZW c2) { return vec2_ref(select(c1), select(c2)); } + + friend vec3 operator*(vec3 a, Float b) { + return vec3(a.x * b, a.y * b, a.z * b); + } + friend vec3 operator*(vec3 a, vec3 b) { + return vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + friend vec3 operator*(Float a, vec3 b) { + return vec3(a * b.x, a * b.y, a * b.z); + } + + friend vec3 operator/(vec3 a, Float b) { + return vec3(a.x / b, a.y / b, a.z / b); + } + + friend I32 operator==(const vec3& l, const vec3& r) { + return l.x == r.x && l.y == r.y && l.z == r.z; + } + + friend vec3 operator-(vec3 a, Float b) { + return vec3(a.x - b, a.y - b, a.z - b); + } + friend vec3 operator-(vec3 a, vec3 b) { + return vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + friend vec3 operator+(vec3 a, Float b) { + return vec3(a.x + b, a.y + b, a.z + b); + } + friend vec3 operator+(vec3 a, vec3 b) { + return vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + + vec3 operator+=(vec3_scalar a) { + x += a.x; + y += a.y; + z += a.z; + return *this; + } +}; + +vec3_scalar force_scalar(const vec3& v) { + return vec3_scalar{force_scalar(v.x), force_scalar(v.y), force_scalar(v.z)}; +} + +vec3_scalar make_vec3(float n) { return vec3_scalar{n, n, n}; } + +vec3_scalar make_vec3(const vec2_scalar& v, float z) { + return vec3_scalar{v.x, v.y, z}; +} + +vec3_scalar make_vec3(float x, float y, float z) { + return vec3_scalar{x, y, z}; +} + +vec3_scalar make_vec3(int32_t x, int32_t y, float z) { + return vec3_scalar{float(x), float(y), z}; +} + +template +vec3 make_vec3(const N& n) { + return vec3(n); +} + +template +vec3 make_vec3(const X& x, const Y& y) { + return vec3(x, y); +} + +template +vec3 make_vec3(const X& x, const Y& y, const Z& z) { + return vec3(x, y, z); +} + +SI vec3 if_then_else(I32 c, vec3 t, vec3 e) { + return vec3(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y), + if_then_else(c, t.z, e.z)); +} + +SI vec3 if_then_else(int32_t c, vec3 t, vec3 e) { return c ? t : e; } + +SI vec3 if_then_else(ivec3 c, vec3 t, vec3 e) { + return vec3(if_then_else(c.x, t.x, e.x), if_then_else(c.y, t.y, e.y), + if_then_else(c.z, t.z, e.z)); +} + +vec3 step(vec3 edge, vec3 x) { + return vec3(step(edge.x, x.x), step(edge.y, x.y), step(edge.z, x.z)); +} + +SI vec3 min(vec3 a, vec3 b) { + return vec3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} +SI vec3 max(vec3 a, vec3 b) { + return vec3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +SI vec3_scalar max(vec3_scalar a, vec3_scalar b) { + return vec3_scalar{max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)}; +} + +vec3 pow(vec3 x, vec3 y) { + return vec3(pow(x.x, y.x), pow(x.y, y.y), pow(x.z, y.z)); +} + +struct vec3_ref { + vec3_ref(Float& x, Float& y, Float& z) : x(x), y(y), z(z) {} + Float& x; + Float& y; + Float& z; + vec3_ref& operator=(const vec3& a) { + x = a.x; + y = a.y; + z = a.z; + return *this; + } + + vec3_ref& operator/=(Float a) { + x /= a; + y /= a; + z /= a; + return *this; + } + + vec3_ref& operator*=(Float a) { + x *= a; + y *= a; + z *= a; + return *this; + } +}; + +struct vec4_scalar { + typedef struct vec4 vector_type; + typedef float element_type; + + float x; + float y; + float z; + float w; + + constexpr vec4_scalar() : vec4_scalar(0.0f) {} + constexpr vec4_scalar(float a) : x(a), y(a), z(a), w(a) {} + constexpr vec4_scalar(float x, float y, float z, float w) + : x(x), y(y), z(z), w(w) {} + + float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + case W: + return w; + default: + UNREACHABLE; + } + } + float& sel(XYZW c1) { return select(c1); } + vec2_scalar sel(XYZW c1, XYZW c2) { + return vec2_scalar{select(c1), select(c2)}; + } + vec3_scalar sel(XYZW c1, XYZW c2, XYZW c3) { + return vec3_scalar{select(c1), select(c2), select(c3)}; + } + vec2_scalar_ref lsel(XYZW c1, XYZW c2) { + return vec2_scalar_ref(select(c1), select(c2)); + } + vec3_scalar_ref lsel(XYZW c1, XYZW c2, XYZW c3) { + return vec3_scalar_ref(select(c1), select(c2), select(c3)); + } + + friend vec4_scalar operator*(vec4_scalar a, vec4_scalar b) { + return vec4_scalar{a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w}; + } + friend vec4_scalar operator*(vec4_scalar a, float b) { + return vec4_scalar{a.x * b, a.y * b, a.z * b, a.w * b}; + } + vec4_scalar& operator*=(float a) { + x *= a; + y *= a; + z *= a; + w *= a; + return *this; + } + + friend vec4_scalar operator-(vec4_scalar a, vec4_scalar b) { + return vec4_scalar{a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w}; + } + friend vec4_scalar operator+(vec4_scalar a, vec4_scalar b) { + return vec4_scalar{a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w}; + } + + friend vec4_scalar operator/(vec4_scalar a, vec4_scalar b) { + return vec4_scalar{a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w}; + } + vec4_scalar& operator/=(vec4_scalar a) { + x /= a.x; + y /= a.y; + z /= a.z; + w /= a.w; + return *this; + } +}; + +vec4_scalar vec2_scalar::sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4) { + return vec4_scalar{select(c1), select(c2), select(c3), select(c4)}; +} + +struct vec4 { + typedef struct vec4 vector_type; + typedef float element_type; + + constexpr vec4() : vec4(Float(0.0f)) {} + constexpr vec4(Float a) : x(a), y(a), z(a), w(a) {} + vec4(Float x, Float y, Float z, Float w) : x(x), y(y), z(z), w(w) {} + vec4(vec3 xyz, Float w) : x(xyz.x), y(xyz.y), z(xyz.z), w(w) {} + vec4(vec2 xy, vec2 zw) : x(xy.x), y(xy.y), z(zw.x), w(zw.y) {} + vec4(vec2 xy, Float z, Float w) : x(xy.x), y(xy.y), z(z), w(w) {} + vec4(Float x, Float y, vec2 zw) : x(x), y(y), z(zw.x), w(zw.y) {} + constexpr vec4(vec4_scalar s) : x(s.x), y(s.y), z(s.z), w(s.w) {} + constexpr vec4(vec4_scalar s0, vec4_scalar s1, vec4_scalar s2, vec4_scalar s3) + : x(Float{s0.x, s1.x, s2.x, s3.x}), + y(Float{s0.y, s1.y, s2.y, s3.y}), + z(Float{s0.z, s1.z, s2.z, s3.z}), + w(Float{s0.w, s1.w, s2.w, s3.w}) {} + Float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + case W: + return w; + default: + UNREACHABLE; + } + } + Float& sel(XYZW c1) { return select(c1); } + + vec2 sel(XYZW c1, XYZW c2) { return vec2(select(c1), select(c2)); } + + vec3 sel(XYZW c1, XYZW c2, XYZW c3) { + return vec3(select(c1), select(c2), select(c3)); + } + vec3_ref lsel(XYZW c1, XYZW c2, XYZW c3) { + return vec3_ref(select(c1), select(c2), select(c3)); + } + + vec2_ref lsel(XYZW c1, XYZW c2) { return vec2_ref(select(c1), select(c2)); } + + Float& operator[](int index) { + switch (index) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + case 3: + return w; + default: + UNREACHABLE; + } + } + + // glsl supports non-const indexing of vecs. + // hlsl doesn't. The code it generates is probably not wonderful. + Float operator[](I32 index) { + float sel_x; + switch (index.x) { + case 0: + sel_x = x.x; + break; + case 1: + sel_x = y.x; + break; + case 2: + sel_x = z.x; + break; + case 3: + sel_x = w.x; + break; + } + float sel_y; + switch (index.y) { + case 0: + sel_y = x.y; + break; + case 1: + sel_y = y.y; + break; + case 2: + sel_y = z.y; + break; + case 3: + sel_y = w.y; + break; + } + float sel_z; + switch (index.z) { + case 0: + sel_z = x.z; + break; + case 1: + sel_z = y.z; + break; + case 2: + sel_z = z.z; + break; + case 3: + sel_z = w.z; + break; + } + float sel_w; + switch (index.w) { + case 0: + sel_w = x.w; + break; + case 1: + sel_w = y.w; + break; + case 2: + sel_w = z.w; + break; + case 3: + sel_w = w.w; + break; + } + Float ret = {sel_x, sel_y, sel_z, sel_w}; + return ret; + } + + friend vec4 operator/(vec4 a, Float b) { + return vec4(a.x / b, a.y / b, a.z / b, a.w / b); + } + friend vec4 operator/(vec4 a, vec4 b) { + return vec4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); + } + + friend vec4 operator*(vec4 a, Float b) { + return vec4(a.x * b, a.y * b, a.z * b, a.w * b); + } + + friend vec4 operator*(Float b, vec4 a) { + return vec4(a.x * b, a.y * b, a.z * b, a.w * b); + } + friend vec4 operator*(vec4 a, vec4 b) { + return vec4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); + } + + friend vec4 operator-(vec4 a, vec4 b) { + return vec4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); + } + friend vec4 operator+(vec4 a, vec4 b) { + return vec4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); + } + vec4& operator+=(vec4 a) { + x += a.x; + y += a.y; + z += a.z; + w += a.w; + return *this; + } + vec4& operator/=(vec4 a) { + x /= a.x; + y /= a.y; + z /= a.z; + w /= a.w; + return *this; + } + vec4& operator*=(Float a) { + x *= a; + y *= a; + z *= a; + w *= a; + return *this; + } + + Float x; + Float y; + Float z; + Float w; +}; + +vec4_scalar force_scalar(const vec4& v) { + return vec4_scalar{force_scalar(v.x), force_scalar(v.y), force_scalar(v.z), + force_scalar(v.w)}; +} + +vec4_scalar make_vec4(float n) { return vec4_scalar{n, n, n, n}; } + +vec4_scalar make_vec4(const vec2_scalar& v, float z, float w) { + return vec4_scalar{v.x, v.y, z, w}; +} + +vec4_scalar make_vec4(const vec2_scalar& a, const vec2_scalar& b) { + return vec4_scalar{a.x, a.y, b.x, b.y}; +} + +vec4_scalar make_vec4(const vec3_scalar& v, float w) { + return vec4_scalar{v.x, v.y, v.z, w}; +} + +vec4_scalar make_vec4(float x, float y, float z, float w) { + return vec4_scalar{x, y, z, w}; +} + +vec4_scalar make_vec4(float x, float y, const vec2_scalar& v) { + return vec4_scalar{x, y, v.x, v.y}; +} + +template +vec4 make_vec4(const N& n) { + return vec4(n); +} + +template +vec4 make_vec4(const X& x, const Y& y) { + return vec4(x, y); +} + +template +vec4 make_vec4(const X& x, const Y& y, const Z& z) { + return vec4(x, y, z); +} + +template +vec4 make_vec4(const X& x, const Y& y, const Z& z, const W& w) { + return vec4(x, y, z, w); +} + +SI ivec4 roundto(vec4 v, Float scale) { + return ivec4(roundto(v.x, scale), roundto(v.y, scale), roundto(v.z, scale), + roundto(v.w, scale)); +} + +vec4 operator*(vec4_scalar a, Float b) { + return vec4(a.x * b, a.y * b, a.z * b, a.w * b); +} + +SI vec4 if_then_else(I32 c, vec4 t, vec4 e) { + return vec4(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y), + if_then_else(c, t.z, e.z), if_then_else(c, t.w, e.w)); +} + +SI vec4 if_then_else(int32_t c, vec4 t, vec4 e) { return c ? t : e; } + +SI vec2 clamp(vec2 a, vec2 minVal, vec2 maxVal) { + return vec2(clamp(a.x, minVal.x, maxVal.x), clamp(a.y, minVal.y, maxVal.y)); +} + +SI vec2_scalar clamp(vec2_scalar a, vec2_scalar minVal, vec2_scalar maxVal) { + return vec2_scalar{clamp(a.x, minVal.x, maxVal.x), + clamp(a.y, minVal.y, maxVal.y)}; +} + +SI I32 clamp(I32 a, I32 minVal, I32 maxVal) { + a = if_then_else(a < minVal, minVal, a); + return if_then_else(a > maxVal, maxVal, a); +} + +SI vec3 clamp(vec3 a, vec3 minVal, vec3 maxVal) { + return vec3(clamp(a.x, minVal.x, maxVal.x), clamp(a.y, minVal.y, maxVal.y), + clamp(a.z, minVal.z, maxVal.z)); +} + +SI vec4 clamp(vec4 a, vec4 minVal, vec4 maxVal) { + return vec4(clamp(a.x, minVal.x, maxVal.x), clamp(a.y, minVal.y, maxVal.y), + clamp(a.z, minVal.z, maxVal.z), clamp(a.w, minVal.w, maxVal.w)); +} +template +auto lessThanEqual(T x, T y) -> decltype(x <= y) { + return x <= y; +} + +template +auto lessThan(T x, T y) -> decltype(x < y) { + return x < y; +} + +SI bvec3 lessThanEqual(vec3 x, vec3 y) { + return bvec3(lessThanEqual(x.x, y.x), lessThanEqual(x.y, y.y), + lessThanEqual(x.z, y.z)); +} + +SI bvec2 lessThanEqual(vec2 x, vec2 y) { + return bvec2(lessThanEqual(x.x, y.x), lessThanEqual(x.y, y.y)); +} + +SI bvec2_scalar lessThanEqual(vec2_scalar x, vec2_scalar y) { + return bvec2_scalar{lessThanEqual(x.x, y.x), lessThanEqual(x.y, y.y)}; +} + +SI bvec4 lessThanEqual(vec4 x, vec4 y) { + return bvec4(lessThanEqual(x.x, y.x), lessThanEqual(x.y, y.y), + lessThanEqual(x.z, y.z), lessThanEqual(x.w, y.w)); +} + +SI bvec4_scalar lessThanEqual(vec4_scalar x, vec4_scalar y) { + return bvec4_scalar{lessThanEqual(x.x, y.x), lessThanEqual(x.y, y.y), + lessThanEqual(x.z, y.z), lessThanEqual(x.w, y.w)}; +} + +SI bvec2 lessThan(vec2 x, vec2 y) { + return bvec2(lessThan(x.x, y.x), lessThan(x.y, y.y)); +} + +template +auto greaterThan(T x, T y) -> decltype(x > y) { + return x > y; +} + +bvec2 greaterThan(vec2 x, vec2 y) { + return bvec2(greaterThan(x.x, y.x), greaterThan(x.y, y.y)); +} + +template +auto greaterThanEqual(T x, T y) -> decltype(x >= y) { + return x >= y; +} + +bvec4 greaterThanEqual(vec4 x, vec4 y) { + return bvec4(greaterThanEqual(x.x, y.x), greaterThanEqual(x.y, y.y), + greaterThanEqual(x.z, y.z), greaterThanEqual(x.w, y.w)); +} + +enum TextureFormat { RGBA32F, RGBA32I, RGBA8, R8 }; + +enum TextureFilter { NEAREST, LINEAR }; + +struct samplerCommon { + uint32_t* buf = nullptr; + uint32_t stride = 0; // in dwords + uint32_t height = 0; + uint32_t width = 0; + TextureFormat format = TextureFormat::RGBA8; +}; + +struct samplerDepth { + int depth = 0; + uint32_t height_stride = 0; // in dwords +}; + +struct samplerFilter { + TextureFilter filter = TextureFilter::NEAREST; +}; + +struct sampler2DArray_impl : samplerCommon, samplerDepth, samplerFilter {}; +typedef sampler2DArray_impl* sampler2DArray; + +typedef struct sampler2DArrayR8_impl : sampler2DArray_impl{} * sampler2DArrayR8; +typedef struct sampler2DArrayRGBA8_impl : sampler2DArray_impl{} * + sampler2DArrayRGBA8; +typedef struct sampler2DArrayRGBA32F_impl : sampler2DArray_impl{} * + sampler2DArrayRGBA32F; + +struct sampler2D_impl : samplerCommon, samplerFilter {}; +typedef sampler2D_impl* sampler2D; + +typedef struct sampler2DR8_impl : sampler2D_impl{} * sampler2DR8; +typedef struct sampler2DRGBA8_impl : sampler2D_impl{} * sampler2DRGBA8; +typedef struct sampler2DRGBA32F_impl : sampler2D_impl{} * sampler2DRGBA32F; + +struct isampler2D_impl : samplerCommon {}; +typedef isampler2D_impl* isampler2D; + +struct isampler2DRGBA32I_impl : isampler2D_impl {}; +typedef isampler2DRGBA32I_impl* isampler2DRGBA32I; + +struct mat4_scalar; + +struct mat2_scalar { + vec2_scalar data[2]; + + mat2_scalar() = default; + constexpr mat2_scalar(float a) { + data[0] = vec2_scalar(a); + data[1] = vec2_scalar(a); + } + constexpr mat2_scalar(vec2_scalar a, vec2_scalar b) { + data[0] = a; + data[1] = b; + } + mat2_scalar(const mat4_scalar& mat); + + vec2_scalar& operator[](int index) { return data[index]; } + const vec2_scalar& operator[](int index) const { return data[index]; } + + friend vec2_scalar operator*(mat2_scalar m, vec2_scalar v) { + vec2_scalar u; + u.x = m[0].x * v.x + m[1].x * v.y; + u.y = m[0].y * v.x + m[1].y * v.y; + return u; + } + + friend vec2 operator*(mat2_scalar m, vec2 v) { + vec2 u; + u.x = m[0].x * v.x + m[1].x * v.y; + u.y = m[0].y * v.x + m[1].y * v.y; + return u; + } + + friend mat2_scalar operator*(mat2_scalar m, float f) { + mat2_scalar u = m; + u[0].x *= f; + u[0].y *= f; + u[1].x *= f; + u[1].y *= f; + return u; + } +}; + +struct mat4; + +struct mat2 { + vec2 data[2]; + + vec2& operator[](int index) { return data[index]; } + const vec2& operator[](int index) const { return data[index]; } + mat2() = default; + + mat2(Float a) { + data[0] = vec2(a); + data[1] = vec2(a); + } + + mat2(vec2 a, vec2 b) { + data[0] = a; + data[1] = b; + } + mat2(const mat4& mat); + constexpr mat2(mat2_scalar s) { + data[0] = vec2(s.data[0]); + data[1] = vec2(s.data[1]); + } + + friend vec2 operator*(mat2 m, vec2 v) { + vec2 u; + u.x = m[0].x * v.x + m[1].x * v.y; + u.y = m[0].y * v.x + m[1].y * v.y; + return u; + } + friend mat2 operator*(mat2 m, Float f) { + mat2 u = m; + u[0].x *= f; + u[0].y *= f; + u[1].x *= f; + u[1].y *= f; + return u; + } +}; + +mat2_scalar make_mat2(float n) { return mat2_scalar{{n, n}, {n, n}}; } + +mat2_scalar make_mat2(const mat2_scalar& m) { return m; } + +mat2_scalar make_mat2(const vec2_scalar& x, const vec2_scalar& y) { + return mat2_scalar{x, y}; +} + +template +mat2 make_mat2(const N& n) { + return mat2(n); +} + +template +mat2 make_mat2(const X& x, const Y& y) { + return mat2(x, y); +} + +SI mat2 if_then_else(I32 c, mat2 t, mat2 e) { + return mat2(if_then_else(c, t[0], e[0]), if_then_else(c, t[0], e[1])); +} + +SI mat2 if_then_else(int32_t c, mat2 t, mat2 e) { return c ? t : e; } + +struct mat3_scalar { + vec3_scalar data[3]; + + mat3_scalar() = default; + constexpr mat3_scalar(vec3_scalar a, vec3_scalar b, vec3_scalar c) { + data[0] = a; + data[1] = b; + data[2] = c; + } + mat3_scalar(const mat4_scalar& mat); + + vec3_scalar& operator[](int index) { return data[index]; } + const vec3_scalar& operator[](int index) const { return data[index]; } + + friend vec3_scalar operator*(mat3_scalar m, vec3_scalar v) { + vec3_scalar u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z; + return u; + } + + friend vec3 operator*(mat3_scalar m, vec3 v) { + vec3 u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z; + return u; + } +}; + +struct mat3 { + vec3 data[3]; + + vec3& operator[](int index) { return data[index]; } + const vec3& operator[](int index) const { return data[index]; } + mat3() = default; + mat3(vec3 a, vec3 b, vec3 c) { + data[0] = a; + data[1] = b; + data[2] = c; + } + + constexpr mat3(mat3_scalar s) { + data[0] = vec3(s.data[0]); + data[1] = vec3(s.data[1]); + data[2] = vec3(s.data[2]); + } + constexpr mat3(mat3_scalar s0, mat3_scalar s1, mat3_scalar s2, + mat3_scalar s3) { + data[0] = vec3(s0.data[0], s1.data[0], s2.data[0], s3.data[0]); + data[1] = vec3(s0.data[1], s1.data[1], s2.data[1], s3.data[1]); + data[2] = vec3(s0.data[2], s1.data[2], s2.data[2], s3.data[2]); + } + + constexpr mat3(Float d1, Float d2, Float d3, Float d4, Float d5, Float d6, + Float d7, Float d8, Float d9) { + data[0] = vec3(d1, d2, d3); + data[1] = vec3(d4, d5, d6); + data[2] = vec3(d7, d8, d9); + } + + mat3(const mat4& mat); + + friend vec3 operator*(mat3 m, vec3 v) { + vec3 u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z; + return u; + } +}; + +mat3_scalar force_scalar(const mat3& v) { + return mat3_scalar{force_scalar(v[0]), force_scalar(v[1]), + force_scalar(v[2])}; +} + +mat3_scalar make_mat3(const mat3_scalar& m) { return m; } + +mat3_scalar make_mat3(const vec3_scalar& x, const vec3_scalar& y, + const vec3_scalar& z) { + return mat3_scalar{x, y, z}; +} + +constexpr mat3_scalar make_mat3(float m0, float m1, float m2, float m3, + float m4, float m5, float m6, float m7, + float m8) { + return mat3_scalar{{m0, m1, m2}, {m3, m4, m5}, {m6, m7, m8}}; +} + +template +mat3 make_mat3(const N& n) { + return mat3(n); +} + +template +mat3 make_mat3(const X& x, const Y& y, const Z& z) { + return mat3(x, y, z); +} + +struct mat4_scalar { + vec4_scalar data[4]; + + mat4_scalar() = default; + constexpr mat4_scalar(vec4_scalar a, vec4_scalar b, vec4_scalar c, + vec4_scalar d) { + data[0] = a; + data[1] = b; + data[2] = c; + data[3] = d; + } + + vec4_scalar& operator[](int index) { return data[index]; } + const vec4_scalar& operator[](int index) const { return data[index]; } + + static mat4_scalar load_from_ptr(const float* f) { + mat4_scalar m; + // XXX: hopefully this is in the right order + m.data[0] = vec4_scalar{f[0], f[1], f[2], f[3]}; + m.data[1] = vec4_scalar{f[4], f[5], f[6], f[7]}; + m.data[2] = vec4_scalar{f[8], f[9], f[10], f[11]}; + m.data[3] = vec4_scalar{f[12], f[13], f[14], f[15]}; + return m; + } + + friend vec4_scalar operator*(mat4_scalar m, vec4_scalar v) { + vec4_scalar u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z + m[3].x * v.w; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z + m[3].y * v.w; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z + m[3].z * v.w; + u.w = m[0].w * v.x + m[1].w * v.y + m[2].w * v.z + m[3].w * v.w; + return u; + } + + friend vec4 operator*(mat4_scalar m, vec4 v) { + vec4 u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z + m[3].x * v.w; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z + m[3].y * v.w; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z + m[3].z * v.w; + u.w = m[0].w * v.x + m[1].w * v.y + m[2].w * v.z + m[3].w * v.w; + return u; + } +}; + +struct mat4 { + vec4 data[4]; + + mat4() = default; + constexpr mat4(mat4_scalar s) { + data[0] = vec4(s.data[0]); + data[1] = vec4(s.data[1]); + data[2] = vec4(s.data[2]); + data[3] = vec4(s.data[3]); + } + + mat4(vec4 a, vec4 b, vec4 c, vec4 d) { + data[0] = a; + data[1] = b; + data[2] = c; + data[3] = d; + } + + vec4& operator[](int index) { return data[index]; } + const vec4& operator[](int index) const { return data[index]; } + + friend vec4 operator*(mat4 m, vec4 v) { + vec4 u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z + m[3].x * v.w; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z + m[3].y * v.w; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z + m[3].z * v.w; + u.w = m[0].w * v.x + m[1].w * v.y + m[2].w * v.z + m[3].w * v.w; + return u; + } +}; + +mat3::mat3(const mat4& mat) + : mat3(vec3(mat[0].x, mat[0].y, mat[0].z), + vec3(mat[1].x, mat[1].y, mat[1].z), + vec3(mat[2].x, mat[2].y, mat[2].z)) {} + +mat3_scalar::mat3_scalar(const mat4_scalar& mat) + : mat3_scalar(vec3_scalar(mat[0].x, mat[0].y, mat[0].z), + vec3_scalar(mat[1].x, mat[1].y, mat[1].z), + vec3_scalar(mat[2].x, mat[2].y, mat[2].z)) {} + +mat2::mat2(const mat4& mat) + : mat2(vec2(mat[0].x, mat[0].y), vec2(mat[1].x, mat[1].y)) {} + +mat2_scalar::mat2_scalar(const mat4_scalar& mat) + : mat2_scalar(vec2_scalar(mat[0].x, mat[0].y), + vec2_scalar(mat[1].x, mat[1].y)) {} + +mat2_scalar make_mat2(const mat4_scalar& m) { return mat2_scalar(m); } + +mat3_scalar make_mat3(const mat4_scalar& m) { return mat3_scalar(m); } + +mat4_scalar force_scalar(const mat4& v) { + return mat4_scalar(force_scalar(v[0]), force_scalar(v[1]), force_scalar(v[2]), + force_scalar(v[3])); +} + +mat4_scalar make_mat4(const mat4_scalar& m) { return m; } + +mat4_scalar make_mat4(const vec4_scalar& x, const vec4_scalar& y, + const vec4_scalar& z, const vec4_scalar& w) { + return mat4_scalar{x, y, z, w}; +} + +constexpr mat4_scalar make_mat4(float m0, float m1, float m2, float m3, + float m4, float m5, float m6, float m7, + float m8, float m9, float m10, float m11, + float m12, float m13, float m14, float m15) { + return mat4_scalar{{m0, m1, m2, m3}, + {m4, m5, m6, m7}, + {m8, m9, m10, m11}, + {m12, m13, m14, m15}}; +} + +template +mat4 make_mat4(const N& n) { + return mat4(n); +} + +template +mat4 make_mat4(const X& x, const Y& y, const Z& z, const W& w) { + return mat4(x, y, z, w); +} + +SI mat3 if_then_else(I32 c, mat3 t, mat3 e) { + return mat3{if_then_else(c, t[0], e[0]), if_then_else(c, t[1], e[1]), + if_then_else(c, t[2], e[2])}; +} + +SI mat3 if_then_else(int32_t c, mat3 t, mat3 e) { return c ? t : e; } + +SI mat4 if_then_else(I32 c, mat4 t, mat4 e) { + return mat4{if_then_else(c, t[0], e[0]), if_then_else(c, t[1], e[1]), + if_then_else(c, t[2], e[2]), if_then_else(c, t[3], e[3])}; +} + +SI mat4 if_then_else(int32_t c, mat4 t, mat4 e) { return c ? t : e; } + +SI I32 clampCoord(I32 coord, int limit) { +#if USE_SSE2 + return _mm_min_epi16(_mm_max_epi16(coord, _mm_setzero_si128()), + _mm_set1_epi32(limit - 1)); +#else + return clamp(coord, 0, limit - 1); +#endif +} +SI int clampCoord(int coord, int limit) { + return min(max(coord, 0), limit - 1); +} +template +SI T clamp2D(T P, S sampler) { + return T{clampCoord(P.x, sampler->width), clampCoord(P.y, sampler->height)}; +} +template +SI T clamp2DArray(T P, sampler2DArray sampler) { + return T{clampCoord(P.x, sampler->width), clampCoord(P.y, sampler->height), + clampCoord(P.z, sampler->depth)}; +} + +float to_float(uint32_t x) { return x * (1.f / 255.f); } + +vec4 pixel_to_vec4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { + U32 pixels = {a, b, c, d}; + return vec4(cast((pixels >> 16) & 0xFF), cast((pixels >> 8) & 0xFF), + cast(pixels & 0xFF), cast(pixels >> 24)) * + (1.0f / 255.0f); +} + +vec4 pixel_float_to_vec4(Float a, Float b, Float c, Float d) { + return vec4(Float{a.x, b.x, c.x, d.x}, Float{a.y, b.y, c.y, d.y}, + Float{a.z, b.z, c.z, d.z}, Float{a.w, b.w, c.w, d.w}); +} + +ivec4 pixel_int_to_ivec4(I32 a, I32 b, I32 c, I32 d) { + return ivec4(I32{a.x, b.x, c.x, d.x}, I32{a.y, b.y, c.y, d.y}, + I32{a.z, b.z, c.z, d.z}, I32{a.w, b.w, c.w, d.w}); +} + +vec4_scalar pixel_to_vec4(uint32_t p) { + U32 i = {(p >> 16) & 0xFF, (p >> 8) & 0xFF, p & 0xFF, p >> 24}; + Float f = cast(i) * (1.0f / 255.0f); + return vec4_scalar(f.x, f.y, f.z, f.w); +} + +template +SI vec4 fetchOffsetsRGBA8(S sampler, I32 offset) { + return pixel_to_vec4(sampler->buf[offset.x], sampler->buf[offset.y], + sampler->buf[offset.z], sampler->buf[offset.w]); +} + +vec4 texelFetchRGBA8(sampler2D sampler, ivec2 P, int lod) { + I32 offset = P.x + P.y * sampler->stride; + return fetchOffsetsRGBA8(sampler, offset); +} + +vec4 texelFetchRGBA8(sampler2DArray sampler, ivec3 P, int lod) { + I32 offset = P.x + P.y * sampler->stride + P.z * sampler->height_stride; + return fetchOffsetsRGBA8(sampler, offset); +} + +template +SI Float fetchOffsetsR8(S sampler, I32 offset) { + U32 i = { + ((uint8_t*)sampler->buf)[offset.x], ((uint8_t*)sampler->buf)[offset.y], + ((uint8_t*)sampler->buf)[offset.z], ((uint8_t*)sampler->buf)[offset.w]}; + return cast(i) * (1.0f / 255.0f); +} + +vec4 texelFetchR8(sampler2D sampler, ivec2 P, int lod) { + I32 offset = P.x + P.y * sampler->stride; + return vec4(fetchOffsetsR8(sampler, offset), 0.0f, 0.0f, 1.0f); +} + +vec4 texelFetchR8(sampler2DArray sampler, ivec3 P, int lod) { + I32 offset = P.x + P.y * sampler->stride + P.z * sampler->height_stride; + return vec4(fetchOffsetsR8(sampler, offset), 0.0f, 0.0f, 1.0f); +} + +template +SI vec4 fetchOffsetsFloat(S sampler, I32 offset) { + return pixel_float_to_vec4( + *(Float*)&sampler->buf[offset.x], *(Float*)&sampler->buf[offset.y], + *(Float*)&sampler->buf[offset.z], *(Float*)&sampler->buf[offset.w]); +} + +vec4 texelFetchFloat(sampler2D sampler, ivec2 P, int lod) { + I32 offset = P.x * 4 + P.y * sampler->stride; + return fetchOffsetsFloat(sampler, offset); +} + +vec4 texelFetchFloat(sampler2DArray sampler, ivec3 P, int lod) { + I32 offset = P.x * 4 + P.y * sampler->stride + P.z * sampler->height_stride; + return fetchOffsetsFloat(sampler, offset); +} + +vec4 texelFetch(sampler2D sampler, ivec2 P, int lod) { + P = clamp2D(P, sampler); + if (sampler->format == TextureFormat::RGBA32F) { + return texelFetchFloat(sampler, P, lod); + } else if (sampler->format == TextureFormat::RGBA8) { + return texelFetchRGBA8(sampler, P, lod); + } else { + assert(sampler->format == TextureFormat::R8); + return texelFetchR8(sampler, P, lod); + } +} + +vec4 texelFetch(sampler2DRGBA32F sampler, ivec2 P, int lod) { + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA32F); + return texelFetchFloat(sampler, P, lod); +} + +vec4 texelFetch(sampler2DRGBA8 sampler, ivec2 P, int lod) { + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA8); + return texelFetchRGBA8(sampler, P, lod); +} + +vec4 texelFetch(sampler2DR8 sampler, ivec2 P, int lod) { + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::R8); + return texelFetchR8(sampler, P, lod); +} + +vec4_scalar texelFetch(sampler2D sampler, ivec2_scalar P, int lod) { + P = clamp2D(P, sampler); + if (sampler->format == TextureFormat::RGBA32F) { + return *(vec4_scalar*)&sampler->buf[P.x * 4 + P.y * sampler->stride]; + } else { + assert(sampler->format == TextureFormat::RGBA8); + return pixel_to_vec4(sampler->buf[P.x + P.y * sampler->stride]); + } +} + +vec4_scalar texelFetch(sampler2DRGBA32F sampler, ivec2_scalar P, int lod) { + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA32F); + return *(vec4_scalar*)&sampler->buf[P.x * 4 + P.y * sampler->stride]; +} + +vec4_scalar texelFetch(sampler2DRGBA8 sampler, ivec2_scalar P, int lod) { + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA8); + return pixel_to_vec4(sampler->buf[P.x + P.y * sampler->stride]); +} + +vec4_scalar texelFetch(sampler2DR8 sampler, ivec2_scalar P, int lod) { + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::R8); + return vec4_scalar{ + to_float(((uint8_t*)sampler->buf)[P.x + P.y * sampler->stride]), 0.0f, + 0.0f, 0.0f}; +} + +vec4 texelFetch(sampler2DArray sampler, ivec3 P, int lod) { + P = clamp2DArray(P, sampler); + if (sampler->format == TextureFormat::RGBA32F) { + return texelFetchFloat(sampler, P, lod); + } else if (sampler->format == TextureFormat::R8) { + return texelFetchR8(sampler, P, lod); + } else { + assert(sampler->format == TextureFormat::RGBA8); + return texelFetchRGBA8(sampler, P, lod); + } +} + +vec4 texelFetch(sampler2DArrayRGBA32F sampler, ivec3 P, int lod) { + P = clamp2DArray(P, sampler); + assert(sampler->format == TextureFormat::RGBA32F); + return texelFetchFloat(sampler, P, lod); +} + +vec4 texelFetch(sampler2DArrayRGBA8 sampler, ivec3 P, int lod) { + P = clamp2DArray(P, sampler); + assert(sampler->format == TextureFormat::RGBA8); + return texelFetchRGBA8(sampler, P, lod); +} + +vec4 texelFetch(sampler2DArrayR8 sampler, ivec3 P, int lod) { + P = clamp2DArray(P, sampler); + assert(sampler->format == TextureFormat::R8); + return texelFetchR8(sampler, P, lod); +} + +template +SI ivec4 fetchOffsetsInt(S sampler, I32 offset) { + return pixel_int_to_ivec4( + *(I32*)&sampler->buf[offset.x], *(I32*)&sampler->buf[offset.y], + *(I32*)&sampler->buf[offset.z], *(I32*)&sampler->buf[offset.w]); +} + +ivec4 texelFetch(isampler2D sampler, ivec2 P, int lod) { + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA32I); + I32 offset = P.x * 4 + P.y * sampler->stride; + return fetchOffsetsInt(sampler, offset); +} + +ivec4_scalar texelFetch(isampler2D sampler, ivec2_scalar P, int lod) { + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA32I); + return *(ivec4_scalar*)&sampler->buf[P.x * 4 + P.y * sampler->stride]; +} + +SI vec4_scalar* texelFetchPtr(sampler2D sampler, ivec2_scalar P, int min_x, + int max_x, int min_y, int max_y) { + P.x = min(max(P.x, -min_x), int(sampler->width) - 1 - max_x); + P.y = min(max(P.y, -min_y), int(sampler->height) - 1 - max_y); + assert(sampler->format == TextureFormat::RGBA32F); + return (vec4_scalar*)&sampler->buf[P.x * 4 + P.y * sampler->stride]; +} + +SI ivec4_scalar* texelFetchPtr(isampler2D sampler, ivec2_scalar P, int min_x, + int max_x, int min_y, int max_y) { + P.x = min(max(P.x, -min_x), int(sampler->width) - 1 - max_x); + P.y = min(max(P.y, -min_y), int(sampler->height) - 1 - max_y); + assert(sampler->format == TextureFormat::RGBA32I); + return (ivec4_scalar*)&sampler->buf[P.x * 4 + P.y * sampler->stride]; +} + +#define texelFetchOffset(sampler, P, lod, offset) \ + texelFetch(sampler, (P) + (offset), lod) + +template +SI R mix(T x, U y, A a) { + return (y - x) * a + x; +} + +SI Float mix(Float x, Float y, Float a) { return (y - x) * a + x; } + +template +SI T mix(T x, T y, float a) { + return (y - x) * a + x; +} + +template +SI T mix(T x, T y, vec4_scalar a) { + return T{mix(x.x, y.x, a.x), mix(x.y, y.y, a.y), mix(x.z, y.z, a.z), + mix(x.w, y.w, a.w)}; +} + +template +vec4 textureLinearRGBA8(S sampler, vec2 P, I32 zoffset = 0) { + assert(sampler->format == TextureFormat::RGBA8); + +#if USE_SSE2 + P.x *= sampler->width * 256.0f; + P.y *= sampler->height * 256.0f; + P -= 0.5f * 256.0f; + ivec2 i(P); + ivec2 frac = i & (I32)0xFF; + i >>= 8; + + __m128i row0 = _mm_min_epi16(_mm_max_epi16(i.y, _mm_setzero_si128()), + _mm_set1_epi32(sampler->height - 1)); + row0 = _mm_madd_epi16(row0, _mm_set1_epi32(sampler->stride)); + row0 = + _mm_add_epi32(row0, _mm_min_epi16(_mm_max_epi16(i.x, _mm_setzero_si128()), + _mm_set1_epi32(sampler->width - 1))); + row0 = _mm_add_epi32(row0, zoffset); + + if (_mm_movemask_epi8(_mm_cmpeq_epi8(_mm_or_si128(frac.x, frac.y), + _mm_setzero_si128())) == 0xFFFF) { + return fetchOffsetsRGBA8(sampler, row0); + } + + __m128i yinside = _mm_andnot_si128( + _mm_cmplt_epi32(i.y, _mm_setzero_si128()), + _mm_cmplt_epi32(i.y, _mm_set1_epi32(sampler->height - 1))); + __m128i row1 = _mm_and_si128(yinside, _mm_set1_epi32(sampler->stride)); + + __m128i xinside = _mm_andnot_si128( + _mm_cmplt_epi32(i.x, _mm_setzero_si128()), + _mm_cmplt_epi32(i.x, _mm_set1_epi32(sampler->width - 1))); + __m128i fracx = _mm_and_si128(xinside, frac.x); + fracx = _mm_shufflelo_epi16(fracx, _MM_SHUFFLE(2, 2, 0, 0)); + fracx = _mm_shufflehi_epi16(fracx, _MM_SHUFFLE(2, 2, 0, 0)); + fracx = _mm_slli_epi16(fracx, 4); + + __m128i fracy = _mm_or_si128(_mm_slli_epi32(frac.y, 16), + _mm_sub_epi32(_mm_set1_epi32(256), frac.y)); + + // r0,g0,b0,a0,r1,g1,b1,a1 \/ R0,G0,B0,A0,R1,G1,B1,A1 + // r0,R0,g0,G0,b0,B0,a0,A0,r1,R1,g1,G1,b1,B1,a1,A1 + // r0_,R0_,g0_,G0_,b0_,B0_,a0_,A0_ /\ r1_,R1_,g1_,G1_,b1_,B1_,a1_,A1_ + // (r0*(256-fracy) + R0*fracy), ... + __m128 r0, r1, r2, r3; +# define FILTER_LANE(out, idx) \ + { \ + uint32_t* buf = &sampler->buf[_mm_cvtsi128_si32( \ + _mm_shuffle_epi32(row0, _MM_SHUFFLE(idx, idx, idx, idx)))]; \ + __m128i cc = _mm_unpacklo_epi8( \ + _mm_loadl_epi64((__m128i*)buf), \ + _mm_loadl_epi64( \ + (__m128i*)(buf + _mm_extract_epi16(row1, 2 * idx)))); \ + __m128i cc0 = _mm_unpacklo_epi8(cc, _mm_setzero_si128()); \ + __m128i cc1 = _mm_unpackhi_epi8(cc, _mm_setzero_si128()); \ + cc = _mm_add_epi16( \ + cc0, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(cc1, cc0), 4), \ + _mm_shuffle_epi32( \ + fracx, _MM_SHUFFLE(idx, idx, idx, idx)))); \ + out = _mm_cvtepi32_ps(_mm_madd_epi16( \ + cc, _mm_shuffle_epi32(fracy, _MM_SHUFFLE(idx, idx, idx, idx)))); \ + } + FILTER_LANE(r0, 0); + FILTER_LANE(r1, 1); + FILTER_LANE(r2, 2); + FILTER_LANE(r3, 3); +# undef FILTER_LANE + + _MM_TRANSPOSE4_PS(r0, r1, r2, r3); + return vec4(r2, r1, r0, r3) * (1.0f / 0xFF00); +#else + P.x *= sampler->width * 128.0f; + P.y *= sampler->height * 128.0f; + P -= 0.5f * 128.0f; + ivec2 i(P); + ivec2 frac = i & (I32)0x7F; + i >>= 7; + + I32 row0 = clampCoord(i.x, sampler->width) + + clampCoord(i.y, sampler->height) * sampler->stride + zoffset; + I32 row1 = row0 + ((i.y > 0 && i.y < int32_t(sampler->height) - 1) & + I32(sampler->stride)); + I16 fracx = + CONVERT(frac.x & (i.x > 0 && i.x < int32_t(sampler->width) - 1), I16); + I16 fracy = CONVERT(frac.y, I16); + + auto a0 = + CONVERT(unaligned_load >(&sampler->buf[row0.x]), V8); + auto a1 = + CONVERT(unaligned_load >(&sampler->buf[row1.x]), V8); + a0 += ((a1 - a0) * fracy.x) >> 7; + + auto b0 = + CONVERT(unaligned_load >(&sampler->buf[row0.y]), V8); + auto b1 = + CONVERT(unaligned_load >(&sampler->buf[row1.y]), V8); + b0 += ((b1 - b0) * fracy.y) >> 7; + + auto abl = zipLow(a0, b0); + auto abh = zipHigh(a0, b0); + abl += ((abh - abl) * fracx.xyxyxyxy) >> 7; + + auto c0 = + CONVERT(unaligned_load >(&sampler->buf[row0.z]), V8); + auto c1 = + CONVERT(unaligned_load >(&sampler->buf[row1.z]), V8); + c0 += ((c1 - c0) * fracy.z) >> 7; + + auto d0 = + CONVERT(unaligned_load >(&sampler->buf[row0.w]), V8); + auto d1 = + CONVERT(unaligned_load >(&sampler->buf[row1.w]), V8); + d0 += ((d1 - d0) * fracy.w) >> 7; + + auto cdl = zipLow(c0, d0); + auto cdh = zipHigh(c0, d0); + cdl += ((cdh - cdl) * fracx.zwzwzwzw) >> 7; + + auto rg = CONVERT(V8(zip2Low(abl, cdl)), V8); + auto ba = CONVERT(V8(zip2High(abl, cdl)), V8); + + auto r = lowHalf(rg); + auto g = highHalf(rg); + auto b = lowHalf(ba); + auto a = highHalf(ba); + return vec4(b, g, r, a) * (1.0f / 255.0f); +#endif +} + +template +vec4 textureLinearR8(S sampler, vec2 P, I32 zoffset = 0) { + assert(sampler->format == TextureFormat::R8); + +#if USE_SSE2 + P.x *= sampler->width * 256.0f; + P.y *= sampler->height * 256.0f; + P -= 0.5f * 256.0f; + ivec2 i(P); + ivec2 frac = i & (I32)0xFF; + i >>= 8; + + __m128i row0 = _mm_min_epi16(_mm_max_epi16(i.y, _mm_setzero_si128()), + _mm_set1_epi32(sampler->height - 1)); + row0 = _mm_madd_epi16(row0, _mm_set1_epi32(sampler->stride)); + row0 = + _mm_add_epi32(row0, _mm_min_epi16(_mm_max_epi16(i.x, _mm_setzero_si128()), + _mm_set1_epi32(sampler->width - 1))); + row0 = _mm_add_epi32(row0, zoffset); + + __m128i yinside = _mm_andnot_si128( + _mm_cmplt_epi32(i.y, _mm_setzero_si128()), + _mm_cmplt_epi32(i.y, _mm_set1_epi32(sampler->height - 1))); + __m128i row1 = _mm_and_si128(yinside, _mm_set1_epi32(sampler->stride)); + + __m128i xinside = _mm_andnot_si128( + _mm_cmplt_epi32(i.x, _mm_setzero_si128()), + _mm_cmplt_epi32(i.x, _mm_set1_epi32(sampler->width - 1))); + __m128i fracx = _mm_and_si128(xinside, frac.x); + fracx = _mm_or_si128(_mm_slli_epi32(fracx, 16), + _mm_sub_epi32(_mm_set1_epi32(256), fracx)); + + __m128i fracy = _mm_slli_epi16(frac.y, 4); + fracy = _mm_shufflelo_epi16(fracy, _MM_SHUFFLE(2, 2, 0, 0)); + fracy = _mm_shufflehi_epi16(fracy, _MM_SHUFFLE(2, 2, 0, 0)); + + uint8_t* buf = (uint8_t*)sampler->buf; + uint8_t* buf0 = + buf + _mm_cvtsi128_si32(_mm_shuffle_epi32(row0, _MM_SHUFFLE(0, 0, 0, 0))); + uint8_t* buf1 = + buf + _mm_cvtsi128_si32(_mm_shuffle_epi32(row0, _MM_SHUFFLE(1, 1, 1, 1))); + uint8_t* buf2 = + buf + _mm_cvtsi128_si32(_mm_shuffle_epi32(row0, _MM_SHUFFLE(2, 2, 2, 2))); + uint8_t* buf3 = + buf + _mm_cvtsi128_si32(_mm_shuffle_epi32(row0, _MM_SHUFFLE(3, 3, 3, 3))); + __m128i cc0 = _mm_unpacklo_epi8( + _mm_setr_epi16(*(uint16_t*)buf0, *(uint16_t*)buf1, *(uint16_t*)buf2, + *(uint16_t*)buf3, 0, 0, 0, 0), + _mm_setzero_si128()); + __m128i cc1 = _mm_unpacklo_epi8( + _mm_setr_epi16(*(uint16_t*)(buf0 + _mm_extract_epi16(row1, 0)), + *(uint16_t*)(buf1 + _mm_extract_epi16(row1, 2)), + *(uint16_t*)(buf2 + _mm_extract_epi16(row1, 4)), + *(uint16_t*)(buf3 + _mm_extract_epi16(row1, 6)), 0, 0, 0, + 0), + _mm_setzero_si128()); + __m128i cc = _mm_add_epi16( + cc0, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(cc1, cc0), 4), fracy)); + __m128 r = _mm_cvtepi32_ps(_mm_madd_epi16(cc, fracx)); + return vec4((Float)r * (1.0f / 0xFF00), 0.0f, 0.0f, 1.0f); +#else + P.x *= sampler->width * 128.0f; + P.y *= sampler->height * 128.0f; + P -= 0.5f * 128.0f; + ivec2 i(P); + ivec2 frac = i & (I32)0x7F; + i >>= 7; + + I32 row0 = clampCoord(i.x, sampler->width) + + clampCoord(i.y, sampler->height) * sampler->stride + zoffset; + I32 row1 = row0 + ((i.y > 0 && i.y < int32_t(sampler->height) - 1) & + I32(sampler->stride)); + I16 fracx = + CONVERT(frac.x & (i.x > 0 && i.x < int32_t(sampler->width) - 1), I16); + I16 fracy = CONVERT(frac.y, I16); + + uint8_t* buf = (uint8_t*)sampler->buf; + auto a0 = unaligned_load >(&buf[row0.x]); + auto b0 = unaligned_load >(&buf[row0.y]); + auto c0 = unaligned_load >(&buf[row0.z]); + auto d0 = unaligned_load >(&buf[row0.w]); + auto abcd0 = CONVERT(combine(combine(a0, b0), combine(c0, d0)), V8); + + auto a1 = unaligned_load >(&buf[row1.x]); + auto b1 = unaligned_load >(&buf[row1.y]); + auto c1 = unaligned_load >(&buf[row1.z]); + auto d1 = unaligned_load >(&buf[row1.w]); + auto abcd1 = CONVERT(combine(combine(a1, b1), combine(c1, d1)), V8); + + abcd0 += ((abcd1 - abcd0) * fracy.xxyyzzww) >> 7; + + abcd0 = SHUFFLE(abcd0, abcd0, 0, 2, 4, 6, 1, 3, 5, 7); + auto abcdl = lowHalf(abcd0); + auto abcdh = highHalf(abcd0); + abcdl += ((abcdh - abcdl) * fracx) >> 7; + + Float r = CONVERT(U16(abcdl), Float); + return vec4(r * (1.0f / 255.0f), 0.0f, 0.0f, 1.0f); +#endif +} + +template +vec4 textureLinearRGBA32F(S sampler, vec2 P, I32 zoffset = 0) { + assert(sampler->format == TextureFormat::RGBA32F); + P.x *= sampler->width; + P.y *= sampler->height; + P -= 0.5f; + vec2 f = floor(P); + vec2 r = P - f; + ivec2 i(f); + ivec2 c = clamp2D(i, sampler); + r.x = if_then_else(i.x < 0 || i.x > sampler->width - 2, 0.0f, r.x); + I32 offset0 = c.x * 4 + c.y * sampler->stride + zoffset; + I32 offset1 = offset0 + if_then_else(r.y < 0 || r.y > sampler->height - 2, 0, + sampler->stride); + + Float c0 = mix(mix(*(Float*)&sampler->buf[offset0.x], + *(Float*)&sampler->buf[offset0.x + 4], r.x), + mix(*(Float*)&sampler->buf[offset1.x], + *(Float*)&sampler->buf[offset1.x + 4], r.x), + r.y); + Float c1 = mix(mix(*(Float*)&sampler->buf[offset0.y], + *(Float*)&sampler->buf[offset0.y + 4], r.x), + mix(*(Float*)&sampler->buf[offset1.y], + *(Float*)&sampler->buf[offset1.y + 4], r.x), + r.y); + Float c2 = mix(mix(*(Float*)&sampler->buf[offset0.z], + *(Float*)&sampler->buf[offset0.z + 4], r.x), + mix(*(Float*)&sampler->buf[offset1.z], + *(Float*)&sampler->buf[offset1.z + 4], r.x), + r.y); + Float c3 = mix(mix(*(Float*)&sampler->buf[offset0.w], + *(Float*)&sampler->buf[offset0.w + 4], r.x), + mix(*(Float*)&sampler->buf[offset1.w], + *(Float*)&sampler->buf[offset1.w + 4], r.x), + r.y); + return pixel_float_to_vec4(c0, c1, c2, c3); +} + +vec4 texture(sampler2D sampler, vec2 P) { + if (sampler->filter == TextureFilter::LINEAR) { + if (sampler->format == TextureFormat::RGBA32F) { + return textureLinearRGBA32F(sampler, P); + } else if (sampler->format == TextureFormat::RGBA8) { + return textureLinearRGBA8(sampler, P); + } else { + assert(sampler->format == TextureFormat::R8); + return textureLinearR8(sampler, P); + } + } else { + ivec2 coord(roundto(P.x, sampler->width), roundto(P.y, sampler->height)); + return texelFetch(sampler, coord, 0); + } +} + +vec4 texture(sampler2DArray sampler, vec3 P, Float layer) { + assert(0); + return vec4(); +} + +vec4 texture(sampler2DArray sampler, vec3 P) { + if (sampler->filter == TextureFilter::LINEAR) { + I32 zoffset = + clampCoord(roundto(P.z, 1.0f), sampler->depth) * sampler->height_stride; + if (sampler->format == TextureFormat::RGBA32F) { + return textureLinearRGBA32F(sampler, vec2(P.x, P.y), zoffset); + } else if (sampler->format == TextureFormat::RGBA8) { + return textureLinearRGBA8(sampler, vec2(P.x, P.y), zoffset); + } else { + assert(sampler->format == TextureFormat::R8); + return textureLinearR8(sampler, vec2(P.x, P.y), zoffset); + } + } else { + // just do nearest for now + ivec3 coord(roundto(P.x, sampler->width), roundto(P.y, sampler->height), + roundto(P.z, 1.0f)); + return texelFetch(sampler, coord, 0); + } +} + +vec4 textureLod(sampler2DArray sampler, vec3 P, float lod) { + assert(lod == 0.0); + return texture(sampler, P); +} + +ivec3_scalar textureSize(sampler2DArray sampler, int) { + return ivec3_scalar{int32_t(sampler->width), int32_t(sampler->height), + int32_t(sampler->depth)}; +} + +ivec2_scalar textureSize(sampler2D sampler, int) { + return ivec2_scalar{int32_t(sampler->width), int32_t(sampler->height)}; +} + +ivec4 ivec2::sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4) { + return ivec4(select(c1), select(c2), select(c3), select(c4)); +} + +vec4 vec2::sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4) { + return vec4(select(c1), select(c2), select(c3), select(c4)); +} + +bool any(bool x) { return x; } + +Bool any(bvec4 x) { return x.x | x.y | x.z | x.w; } + +bool any(bvec4_scalar x) { return x.x | x.y | x.z | x.w; } + +Bool any(bvec2 x) { return x.x | x.y; } + +bool any(bvec2_scalar x) { return x.x | x.y; } + +bool all(bool x) { return x; } + +Bool all(bvec2 x) { return x.x & x.y; } + +bool all(bvec2_scalar x) { return x.x & x.y; } + +Bool all(bvec4 x) { return x.x & x.y & x.z & x.w; } + +bool all(bvec4_scalar x) { return x.x & x.y & x.z & x.w; } + +SI vec4 if_then_else(bvec4 c, vec4 t, vec4 e) { + return vec4(if_then_else(c.x, t.x, e.x), if_then_else(c.y, t.y, e.y), + if_then_else(c.z, t.z, e.z), if_then_else(c.w, t.w, e.w)); +} +SI vec3 if_then_else(bvec3 c, vec3 t, vec3 e) { + return vec3(if_then_else(c.x, t.x, e.x), if_then_else(c.y, t.y, e.y), + if_then_else(c.z, t.z, e.z)); +} + +SI vec2 if_then_else(bvec2 c, vec2 t, vec2 e) { + return vec2(if_then_else(c.x, t.x, e.x), if_then_else(c.y, t.y, e.y)); +} + +template +SI R mix(T x, T y, bvec4 a) { + return if_then_else(a, y, x); +} + +template +SI R mix(T x, T y, bvec3 a) { + return if_then_else(a, y, x); +} + +template +SI R mix(T x, T y, bvec2 a) { + return if_then_else(a, y, x); +} + +template +SI T mix(T x, T y, bvec4_scalar a) { + return T{a.x ? y.x : x.x, a.y ? y.y : x.y, a.z ? y.z : x.z, a.w ? y.w : x.w}; +} + +template +SI T mix(T x, T y, bvec3_scalar a) { + return T{a.x ? y.x : x.x, a.y ? y.y : x.y, a.z ? y.z : x.z}; +} + +template +SI T mix(T x, T y, bvec2_scalar a) { + return T{a.x ? y.x : x.x, a.y ? y.y : x.y}; +} + +float dot(vec3_scalar a, vec3_scalar b) { + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +Float dot(vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; } + +float dot(vec2_scalar a, vec2_scalar b) { return a.x * b.x + a.y * b.y; } + +Float dot(vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; } + +#define sin __glsl_sin +#define cos __glsl_cos + +float sin(float x) { return sinf(x); } + +Float sin(Float v) { return {sinf(v.x), sinf(v.y), sinf(v.z), sinf(v.w)}; } + +float cos(float x) { return cosf(x); } + +Float cos(Float v) { return {cosf(v.x), cosf(v.y), cosf(v.z), cosf(v.w)}; } + +bvec4 notEqual(ivec4 a, ivec4 b) { + return bvec4(a.x != b.x, a.y != b.y, a.z != b.z, a.w != b.w); +} + +bvec4_scalar notEqual(ivec4_scalar a, ivec4_scalar b) { + return bvec4_scalar{a.x != b.x, a.y != b.y, a.z != b.z, a.w != b.w}; +} + +mat3 transpose(mat3 m) { + return mat3(vec3(m[0].x, m[1].x, m[2].x), vec3(m[0].y, m[1].y, m[2].y), + vec3(m[0].z, m[1].z, m[2].z)); +} + +mat3_scalar transpose(mat3_scalar m) { + return mat3_scalar{vec3_scalar(m[0].x, m[1].x, m[2].x), + vec3_scalar(m[0].y, m[1].y, m[2].y), + vec3_scalar(m[0].z, m[1].z, m[2].z)}; +} + +vec2 abs(vec2 v) { return vec2(abs(v.x), abs(v.y)); } + +vec2_scalar abs(vec2_scalar v) { return vec2_scalar{fabsf(v.x), fabsf(v.y)}; } + +Float mod(Float a, Float b) { return a - b * floor(a / b); } + +vec2 mod(vec2 a, vec2 b) { return vec2(mod(a.x, b.x), mod(a.y, b.y)); } + +vec3 abs(vec3 v) { return vec3(abs(v.x), abs(v.y), abs(v.z)); } + +mat2 inverse(mat2 v) { + Float det = v[0].x * v[1].y - v[0].y * v[1].x; + return mat2(vec2(v[1].y, -v[0].y), vec2(-v[1].x, v[0].x)) * (1. / det); +} + +mat2_scalar inverse(mat2_scalar v) { + float det = v[0].x * v[1].y - v[0].y * v[1].x; + return mat2_scalar{{v[1].y, -v[0].y}, {-v[1].x, v[0].x}} * (1. / det); +} + +int32_t get_nth(I32 a, int n) { return a[n]; } + +float get_nth(Float a, int n) { return a[n]; } + +float get_nth(float a, int n) { return a; } + +ivec2_scalar get_nth(ivec2 a, int n) { return ivec2_scalar{a.x[n], a.y[n]}; } + +vec2_scalar get_nth(vec2 a, int n) { return vec2_scalar{a.x[n], a.y[n]}; } + +vec3_scalar get_nth(vec3 a, int n) { + return vec3_scalar{a.x[n], a.y[n], a.z[n]}; +} + +vec4_scalar get_nth(vec4 a, int n) { + return vec4_scalar{a.x[n], a.y[n], a.z[n], a.w[n]}; +} + +ivec4_scalar get_nth(ivec4 a, int n) { + return ivec4_scalar{a.x[n], a.y[n], a.z[n], a.w[n]}; +} + +mat3_scalar get_nth(mat3 a, int n) { + return make_mat3(get_nth(a[0], n), get_nth(a[1], n), get_nth(a[2], n)); +} + +void put_nth(Float& dst, int n, float src) { dst[n] = src; } + +void put_nth(I32& dst, int n, int32_t src) { dst[n] = src; } + +void put_nth(ivec2& dst, int n, ivec2_scalar src) { + dst.x[n] = src.x; + dst.y[n] = src.y; +} + +void put_nth(vec2& dst, int n, vec2_scalar src) { + dst.x[n] = src.x; + dst.y[n] = src.y; +} + +void put_nth(vec3& dst, int n, vec3_scalar src) { + dst.x[n] = src.x; + dst.y[n] = src.y; + dst.z[n] = src.z; +} + +void put_nth(ivec4& dst, int n, ivec4_scalar src) { + dst.x[n] = src.x; + dst.y[n] = src.y; + dst.z[n] = src.z; + dst.w[n] = src.w; +} + +void put_nth(vec4& dst, int n, vec4_scalar src) { + dst.x[n] = src.x; + dst.y[n] = src.y; + dst.z[n] = src.z; + dst.w[n] = src.w; +} + +// Use an ElementType type constructor +// so that we can implement element_type for +// Int and Float +template +struct ElementType { + typedef typename V::element_type ty; +}; + +template <> +struct ElementType { + typedef float ty; +}; + +template <> +struct ElementType { + typedef float ty; +}; + +template <> +struct ElementType { + typedef float ty; +}; + +template <> +struct ElementType { + typedef int32_t ty; +}; + +void put_nth_component(ivec2_scalar& dst, int n, int32_t src) { + switch (n) { + case 0: + dst.x = src; + break; + case 1: + dst.y = src; + break; + } +} + +void put_nth_component(ivec4_scalar& dst, int n, int32_t src) { + switch (n) { + case 0: + dst.x = src; + break; + case 1: + dst.y = src; + break; + case 2: + dst.z = src; + break; + case 3: + dst.w = src; + break; + } +} + +void put_nth_component(int& dst, int n, int src) { + switch (n) { + case 0: + dst = src; + break; + } +} + +void put_nth_component(float& dst, int n, float src) { + switch (n) { + case 0: + dst = src; + break; + } +} + +void put_nth_component(vec2_scalar& dst, int n, float src) { + switch (n) { + case 0: + dst.x = src; + break; + case 1: + dst.y = src; + break; + } +} + +void put_nth_component(vec3_scalar& dst, int n, float src) { + switch (n) { + case 0: + dst.x = src; + break; + case 1: + dst.y = src; + break; + case 2: + dst.z = src; + break; + } +} + +void put_nth_component(vec4_scalar& dst, int n, float src) { + switch (n) { + case 0: + dst.x = src; + break; + case 1: + dst.y = src; + break; + case 2: + dst.z = src; + break; + case 3: + dst.w = src; + break; + } +} + +Float init_interp(float init0, float step) { + float init1 = init0 + step; + float init2 = init1 + step; + float init3 = init2 + step; + return {init0, init1, init2, init3}; +} + +vec2 init_interp(vec2_scalar init, vec2_scalar step) { + return vec2(init_interp(init.x, step.x), init_interp(init.y, step.y)); +} + +vec3 init_interp(vec3_scalar init, vec3_scalar step) { + return vec3(init_interp(init.x, step.x), init_interp(init.y, step.y), + init_interp(init.z, step.z)); +} + +vec4 init_interp(vec4_scalar init, vec4_scalar step) { + return vec4(init_interp(init.x, step.x), init_interp(init.y, step.y), + init_interp(init.z, step.z), init_interp(init.w, step.w)); +} + +template +struct Array { + T elements[N]; + T& operator[](size_t i) { return elements[i]; } + const T& operator[](size_t i) const { return elements[i]; } + template + void convert(const Array& s) { + for (size_t i = 0; i < N; ++i) elements[i] = T(s[i]); + } +}; + +template +Array if_then_else(I32 c, Array t, + Array e) { + Array r; + for (size_t i = 0; i < SIZE; i++) { + r[i] = if_then_else(c, t[i], e[i]); + } + return r; +} + +#if USE_SSE2 +bool test_all(Bool cond) { return _mm_movemask_ps(cond) == 0xF; } +bool test_any(Bool cond) { return _mm_movemask_ps(cond) != 0; } +bool test_none(Bool cond) { return _mm_movemask_ps(cond) == 0; } +#else +bool test_all(Bool cond) { + return bit_cast(CONVERT(cond, U8)) == 0xFFFFFFFFU; +} +bool test_any(Bool cond) { return bit_cast(CONVERT(cond, U8)) != 0; } +bool test_none(Bool cond) { return bit_cast(CONVERT(cond, U8)) == 0; } +#endif + +// See lp_build_sample_soa_code( +// lp_build_sample_aos used for common cases +// lp_build_sample_image_linear for an actual mip +// lp_build_sample_fetch_image_linear +// lp_build_lerp_simple + +// sampleQuad2D - does the bilinear lerp on 8bit values expanded to 16bit +// it does the lerp on 4 pixels at a time +// i.e. 4 Vector4s is 4*4*4 shorts +} // namespace glsl diff --git a/swgl/src/lib.rs b/swgl/src/lib.rs new file mode 100644 index 0000000000..e8fc030e0c --- /dev/null +++ b/swgl/src/lib.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![crate_name = "swgl"] +#![crate_type = "lib"] + +extern crate gleam; + +mod swgl_fns; + +pub use crate::swgl_fns::*; diff --git a/swgl/src/program.h b/swgl/src/program.h new file mode 100644 index 0000000000..120a5a2bb5 --- /dev/null +++ b/swgl/src/program.h @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +struct VertexAttrib; + +namespace glsl { + +struct VertexShaderImpl; +struct FragmentShaderImpl; + +struct ProgramImpl { + virtual ~ProgramImpl() {} + virtual int get_uniform(const char* name) const = 0; + virtual bool set_sampler(int index, int value) = 0; + virtual void bind_attrib(const char* name, int index) = 0; + virtual int get_attrib(const char* name) const = 0; + virtual VertexShaderImpl* get_vertex_shader() = 0; + virtual FragmentShaderImpl* get_fragment_shader() = 0; +}; + +typedef ProgramImpl* (*ProgramLoader)(); + +struct ShaderImpl { + typedef void (*SetUniform1iFunc)(ShaderImpl*, int index, int value); + typedef void (*SetUniform4fvFunc)(ShaderImpl*, int index, const float* value); + typedef void (*SetUniformMatrix4fvFunc)(ShaderImpl*, int index, + const float* value); + + SetUniform1iFunc set_uniform_1i_func = nullptr; + SetUniform4fvFunc set_uniform_4fv_func = nullptr; + SetUniformMatrix4fvFunc set_uniform_matrix4fv_func = nullptr; + + void set_uniform_1i(int index, int value) { + (*set_uniform_1i_func)(this, index, value); + } + + void set_uniform_4fv(int index, const float* value) { + (*set_uniform_4fv_func)(this, index, value); + } + + void set_uniform_matrix4fv(int index, const float* value) { + (*set_uniform_matrix4fv_func)(this, index, value); + } +}; + +struct VertexShaderImpl : ShaderImpl { + typedef void (*InitBatchFunc)(VertexShaderImpl*, ProgramImpl* prog); + typedef void (*LoadAttribsFunc)(VertexShaderImpl*, ProgramImpl* prog, + VertexAttrib* attribs, + unsigned short* indices, int start, + int instance, int count); + typedef void (*RunFunc)(VertexShaderImpl*, char* flats, char* interps, + size_t interp_stride); + + InitBatchFunc init_batch_func = nullptr; + LoadAttribsFunc load_attribs_func = nullptr; + RunFunc run_func = nullptr; + + vec4 gl_Position; + + void init_batch(ProgramImpl* prog) { (*init_batch_func)(this, prog); } + + ALWAYS_INLINE void load_attribs(ProgramImpl* prog, VertexAttrib* attribs, + unsigned short* indices, int start, + int instance, int count) { + (*load_attribs_func)(this, prog, attribs, indices, start, instance, count); + } + + ALWAYS_INLINE void run(char* flats, char* interps, size_t interp_stride) { + (*run_func)(this, flats, interps, interp_stride); + } +}; + +struct FragmentShaderImpl : ShaderImpl { + typedef void (*InitBatchFunc)(FragmentShaderImpl*, ProgramImpl* prog); + typedef void (*InitPrimitiveFunc)(FragmentShaderImpl*, const void* flats); + typedef void (*InitSpanFunc)(FragmentShaderImpl*, const void* interps, + const void* step, float step_width); + typedef void (*RunFunc)(FragmentShaderImpl*); + typedef void (*SkipFunc)(FragmentShaderImpl*, int chunks); + typedef bool (*UseDiscardFunc)(FragmentShaderImpl*); + typedef void (*DrawSpanRGBA8Func)(FragmentShaderImpl*, uint32_t* buf, + int len); + typedef void (*DrawSpanR8Func)(FragmentShaderImpl*, uint8_t* buf, int len); + + InitBatchFunc init_batch_func = nullptr; + InitPrimitiveFunc init_primitive_func = nullptr; + InitSpanFunc init_span_func = nullptr; + RunFunc run_func = nullptr; + SkipFunc skip_func = nullptr; + UseDiscardFunc use_discard_func = nullptr; + DrawSpanRGBA8Func draw_span_RGBA8_func = nullptr; + DrawSpanR8Func draw_span_R8_func = nullptr; + + vec2 gl_FragCoordXY; + vec2_scalar gl_FragCoordZW; + Bool isPixelDiscarded; + vec4 gl_FragColor; + vec4 gl_SecondaryFragColor; + + ALWAYS_INLINE void step_fragcoord() { gl_FragCoordXY.x += 4; } + + ALWAYS_INLINE void step_fragcoord(int chunks) { + gl_FragCoordXY.x += 4 * chunks; + } + + void init_batch(ProgramImpl* prog) { (*init_batch_func)(this, prog); } + + void init_primitive(const void* flats) { + (*init_primitive_func)(this, flats); + } + + ALWAYS_INLINE void init_span(const void* interps, const void* step, + float step_width) { + (*init_span_func)(this, interps, step, step_width); + } + + ALWAYS_INLINE void run() { (*run_func)(this); } + + ALWAYS_INLINE void skip(int chunks = 1) { (*skip_func)(this, chunks); } + + ALWAYS_INLINE bool use_discard() { return (*use_discard_func)(this); } + + ALWAYS_INLINE void draw_span(uint32_t* buf, int len) { + (*draw_span_RGBA8_func)(this, buf, len); + } + + ALWAYS_INLINE bool has_draw_span(uint32_t*) { + return draw_span_RGBA8_func != nullptr; + } + + ALWAYS_INLINE void draw_span(uint8_t* buf, int len) { + (*draw_span_R8_func)(this, buf, len); + } + + ALWAYS_INLINE bool has_draw_span(uint8_t*) { + return draw_span_R8_func != nullptr; + } +}; + +} // namespace glsl diff --git a/swgl/src/swgl_fns.rs b/swgl/src/swgl_fns.rs new file mode 100644 index 0000000000..0d5dd80faa --- /dev/null +++ b/swgl/src/swgl_fns.rs @@ -0,0 +1,2217 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#![allow(unused_variables)] + +use gleam::gl::*; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_int, c_void}; +use std::ptr; +use std::str; + +#[allow(unused)] +macro_rules! debug { + ($($x:tt)*) => {}; +} + +extern "C" {} + +extern "C" { + fn ActiveTexture(texture: GLenum); + fn BindTexture(target: GLenum, texture: GLuint); + fn BindBuffer(target: GLenum, buffer: GLuint); + fn BindVertexArray(vao: GLuint); + fn BindFramebuffer(target: GLenum, fb: GLuint); + fn BindRenderbuffer(target: GLenum, rb: GLuint); + fn BlendFunc(srgb: GLenum, drgb: GLenum, sa: GLenum, da: GLenum); + fn BlendColor(r: GLfloat, g: GLfloat, b: GLfloat, a: GLfloat); + fn BlendEquation(mode: GLenum); + fn Enable(cap: GLenum); + fn Disable(cap: GLenum); + fn GenQueries(n: GLsizei, result: *mut GLuint); + fn BeginQuery(target: GLenum, id: GLuint); + fn EndQuery(target: GLenum); + fn GetQueryObjectui64v(id: GLuint, pname: GLenum, params: *mut GLuint64); + fn GenBuffers(n: i32, result: *mut GLuint); + fn GenTextures(n: i32, result: *mut GLuint); + fn GenFramebuffers(n: i32, result: *mut GLuint); + fn GenRenderbuffers(n: i32, result: *mut GLuint); + fn BufferData(target: GLenum, size: GLsizeiptr, data: *const GLvoid, usage: GLenum); + fn BufferSubData(target: GLenum, offset: GLintptr, size: GLsizeiptr, data: *const GLvoid); + fn MapBuffer(target: GLenum, access: GLbitfield) -> *mut c_void; + fn MapBufferRange( + target: GLenum, + offset: GLintptr, + length: GLsizeiptr, + access: GLbitfield, + ) -> *mut c_void; + fn UnmapBuffer(target: GLenum) -> GLboolean; + fn TexStorage2D( + target: GLenum, + levels: GLint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + ); + fn FramebufferTexture2D( + target: GLenum, + attachment: GLenum, + textarget: GLenum, + texture: GLuint, + level: GLint, + ); + fn CheckFramebufferStatus(target: GLenum) -> GLenum; + fn InvalidateFramebuffer( + target: GLenum, + num_attachments: GLsizei, + attachments: *const GLenum, + ); + fn TexStorage3D( + target: GLenum, + levels: GLint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + ); + fn TexImage2D( + target: GLenum, + level: GLint, + internal_format: GLint, + width: GLsizei, + height: GLsizei, + border: GLint, + format: GLenum, + ty: GLenum, + data: *const c_void, + ); + fn TexImage3D( + target: GLenum, + level: GLint, + internal_format: GLint, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + border: GLint, + format: GLenum, + ty: GLenum, + data: *const c_void, + ); + fn TexSubImage2D( + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + ty: GLenum, + data: *const c_void, + ); + fn TexSubImage3D( + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + zoffset: GLint, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + format: GLenum, + ty: GLenum, + data: *const c_void, + ); + fn GetUniformLocation(program: GLuint, name: *const GLchar) -> GLint; + fn BindAttribLocation(program: GLuint, index: GLuint, name: *const GLchar); + fn GetAttribLocation(program: GLuint, name: *const GLchar) -> GLint; + fn GenVertexArrays(n: i32, result: *mut GLuint); + fn VertexAttribPointer( + index: GLuint, + size: GLint, + type_: GLenum, + normalized: GLboolean, + stride: GLsizei, + offset: *const GLvoid, + ); + fn VertexAttribIPointer( + index: GLuint, + size: GLint, + type_: GLenum, + stride: GLsizei, + offset: *const GLvoid, + ); + fn CreateShader(shader_type: GLenum) -> GLuint; + fn AttachShader(program: GLuint, shader: GLuint); + fn CreateProgram() -> GLuint; + fn Uniform1i(location: GLint, v0: GLint); + fn Uniform4fv(location: GLint, count: GLsizei, value: *const GLfloat); + fn UniformMatrix4fv( + location: GLint, + count: GLsizei, + transpose: GLboolean, + value: *const GLfloat, + ); + + fn DrawElementsInstanced( + mode: GLenum, + count: GLsizei, + type_: GLenum, + indices: *const c_void, + instancecount: GLsizei, + ); + fn EnableVertexAttribArray(index: GLuint); + fn VertexAttribDivisor(index: GLuint, divisor: GLuint); + fn LinkProgram(program: GLuint); + fn UseProgram(program: GLuint); + fn SetViewport(x: GLint, y: GLint, width: GLsizei, height: GLsizei); + fn FramebufferTextureLayer( + target: GLenum, + attachment: GLenum, + texture: GLuint, + level: GLint, + layer: GLint, + ); + fn FramebufferRenderbuffer( + target: GLenum, + attachment: GLenum, + renderbuffertarget: GLenum, + renderbuffer: GLuint, + ); + fn RenderbufferStorage(target: GLenum, internalformat: GLenum, width: GLsizei, height: GLsizei); + fn DepthMask(flag: GLboolean); + fn DepthFunc(func: GLenum); + fn SetScissor(x: GLint, y: GLint, width: GLsizei, height: GLsizei); + fn ClearColor(r: GLfloat, g: GLfloat, b: GLfloat, a: GLfloat); + fn ClearDepth(depth: GLdouble); + fn Clear(mask: GLbitfield); + fn PixelStorei(name: GLenum, param: GLint); + fn ReadPixels( + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + ty: GLenum, + data: *mut c_void, + ); + fn Finish(); + fn ShaderSourceByName(shader: GLuint, name: *const GLchar); + fn TexParameteri(target: GLenum, pname: GLenum, param: GLint); + fn CopyImageSubData( + src_name: GLuint, + src_target: GLenum, + src_level: GLint, + src_x: GLint, + src_y: GLint, + src_z: GLint, + dst_name: GLuint, + dst_target: GLenum, + dst_level: GLint, + dst_x: GLint, + dst_y: GLint, + dst_z: GLint, + src_width: GLsizei, + src_height: GLsizei, + src_depth: GLsizei, + ); + fn CopyTexSubImage2D( + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + ); + fn CopyTexSubImage3D( + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + zoffset: GLint, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + ); + fn BlitFramebuffer( + src_x0: GLint, + src_y0: GLint, + src_x1: GLint, + src_y1: GLint, + dst_x0: GLint, + dst_y0: GLint, + dst_x1: GLint, + dst_y1: GLint, + mask: GLbitfield, + filter: GLenum, + ); + fn GetIntegerv(pname: GLenum, params: *mut GLint); + fn GetBooleanv(pname: GLenum, params: *mut GLboolean); + fn GetString(name: GLenum) -> *const c_char; + fn GetStringi(name: GLenum, index: GLuint) -> *const c_char; + fn GetError() -> GLenum; + fn InitDefaultFramebuffer(width: i32, height: i32); + fn GetColorBuffer( + fbo: GLuint, + flush: GLboolean, + width: *mut i32, + height: *mut i32, + ) -> *mut c_void; + fn SetTextureBuffer( + tex: GLuint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + buf: *mut c_void, + min_width: GLsizei, + min_height: GLsizei, + ); + fn DeleteTexture(n: GLuint); + fn DeleteRenderbuffer(n: GLuint); + fn DeleteFramebuffer(n: GLuint); + fn DeleteBuffer(n: GLuint); + fn DeleteVertexArray(n: GLuint); + fn DeleteQuery(n: GLuint); + fn DeleteShader(shader: GLuint); + fn DeleteProgram(program: GLuint); + fn Composite( + src_id: GLuint, + src_x: GLint, + src_y: GLint, + src_width: GLsizei, + src_height: GLsizei, + dst_x: GLint, + dst_y: GLint, + opaque: GLboolean, + flip: GLboolean, + ); + fn CreateContext() -> *mut c_void; + fn DestroyContext(ctx: *mut c_void); + fn MakeCurrent(ctx: *mut c_void); +} + +#[derive(Clone)] +pub struct Context(*mut c_void); + +impl Context { + pub fn create() -> Self { + Context(unsafe { CreateContext() }) + } + + pub fn destroy(&self) { + unsafe { + DestroyContext(self.0); + } + } + + pub fn make_current(&self) { + unsafe { + MakeCurrent(self.0); + } + } + + pub fn init_default_framebuffer(&self, width: i32, height: i32) { + unsafe { + InitDefaultFramebuffer(width, height); + } + } + + pub fn get_color_buffer(&self, fbo: GLuint, flush: bool) -> (*mut c_void, i32, i32) { + unsafe { + let mut width: i32 = 0; + let mut height: i32 = 0; + let data_ptr = GetColorBuffer(fbo, flush as GLboolean, &mut width, &mut height); + (data_ptr, width, height) + } + } + + pub fn set_texture_buffer( + &self, + tex: GLuint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + buf: *mut c_void, + min_width: GLsizei, + min_height: GLsizei, + ) { + unsafe { + SetTextureBuffer( + tex, + internal_format, + width, + height, + buf, + min_width, + min_height, + ); + } + } + + pub fn composite( + &self, + src_id: GLuint, + src_x: GLint, + src_y: GLint, + src_width: GLsizei, + src_height: GLint, + dst_x: GLint, + dst_y: GLint, + opaque: bool, + flip: bool, + ) { + unsafe { + Composite( + src_id, + src_x, + src_y, + src_width, + src_height, + dst_x, + dst_y, + opaque as GLboolean, + flip as GLboolean, + ); + } + } +} + +impl From<*mut c_void> for Context { + fn from(ptr: *mut c_void) -> Self { + Context(ptr) + } +} + +impl From for *mut c_void { + fn from(ctx: Context) -> Self { + ctx.0 + } +} + +fn calculate_length(width: GLsizei, height: GLsizei, format: GLenum, pixel_type: GLenum) -> usize { + let colors = match format { + RED => 1, + RGB => 3, + BGR => 3, + + RGBA => 4, + BGRA => 4, + + ALPHA => 1, + R16 => 1, + LUMINANCE => 1, + DEPTH_COMPONENT => 1, + _ => panic!("unsupported format for read_pixels: {:?}", format), + }; + let depth = match pixel_type { + UNSIGNED_BYTE => 1, + UNSIGNED_SHORT => 2, + SHORT => 2, + FLOAT => 4, + _ => panic!("unsupported pixel_type for read_pixels: {:?}", pixel_type), + }; + + return (width * height * colors * depth) as usize; +} + +impl Gl for Context { + fn get_type(&self) -> GlType { + GlType::Gl + } + + fn buffer_data_untyped( + &self, + target: GLenum, + size: GLsizeiptr, + data: *const GLvoid, + usage: GLenum, + ) { + debug!( + "buffer_data_untyped {} {} {:?} {}", + target, size, data, usage + ); + //panic!(); + unsafe { + BufferData(target, size, data, usage); + } + } + + fn buffer_sub_data_untyped( + &self, + target: GLenum, + offset: isize, + size: GLsizeiptr, + data: *const GLvoid, + ) { + debug!( + "buffer_sub_data_untyped {} {} {} {:?}", + target, offset, size, data + ); + //panic!(); + unsafe { + BufferSubData(target, offset, size, data); + } + } + + fn map_buffer(&self, target: GLenum, access: GLbitfield) -> *mut c_void { + unsafe { MapBuffer(target, access) } + } + + fn map_buffer_range( + &self, + target: GLenum, + offset: GLintptr, + length: GLsizeiptr, + access: GLbitfield, + ) -> *mut c_void { + unsafe { MapBufferRange(target, offset, length, access) } + } + + fn unmap_buffer(&self, target: GLenum) -> GLboolean { + unsafe { UnmapBuffer(target) } + } + + fn shader_source(&self, shader: GLuint, strings: &[&[u8]]) { + //panic!(); + debug!("shader_source {}", shader); + //for s in strings { + // debug!("{}", str::from_utf8(s).unwrap()); + //} + //panic!(); + for s in strings { + let u = str::from_utf8(s).unwrap(); + const PREFIX: &'static str = "// shader: "; + if let Some(start) = u.find(PREFIX) { + if let Some(end) = u[start ..].find('\n') { + debug!( + "shader name: {}", + u[start + PREFIX.len() .. start + end].trim() + ); + unsafe { + let c_string = CString::new( + u[start + PREFIX.len() .. start + end] + .trim() + .replace(" ", ""), + ) + .unwrap(); + ShaderSourceByName(shader, c_string.as_ptr()); + return; + } + } + } + } + panic!("unknown shader"); + } + + fn tex_buffer(&self, target: GLenum, internal_format: GLenum, buffer: GLuint) { + panic!(); + } + + fn read_buffer(&self, mode: GLenum) { + panic!(); + } + + fn read_pixels_into_buffer( + &self, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + pixel_type: GLenum, + dst_buffer: &mut [u8], + ) { + // Assumes that the user properly allocated the size for dst_buffer. + assert!(calculate_length(width, height, format, pixel_type) == dst_buffer.len()); + + unsafe { + ReadPixels( + x, + y, + width, + height, + format, + pixel_type, + dst_buffer.as_mut_ptr() as *mut c_void, + ); + } + } + + fn read_pixels( + &self, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + pixel_type: GLenum, + ) -> Vec { + let len = calculate_length(width, height, format, pixel_type); + let mut pixels: Vec = Vec::new(); + pixels.reserve(len); + unsafe { + pixels.set_len(len); + } + + self.read_pixels_into_buffer( + x, + y, + width, + height, + format, + pixel_type, + pixels.as_mut_slice(), + ); + + pixels + } + + unsafe fn read_pixels_into_pbo( + &self, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + pixel_type: GLenum, + ) { + panic!(); + } + + fn sample_coverage(&self, value: GLclampf, invert: bool) { + panic!(); + } + + fn polygon_offset(&self, factor: GLfloat, units: GLfloat) { + panic!(); + } + + fn pixel_store_i(&self, name: GLenum, param: GLint) { + //panic!(); + debug!("pixel_store_i {:x} {}", name, param); + unsafe { + PixelStorei(name, param); + } + } + + fn gen_buffers(&self, n: GLsizei) -> Vec { + //panic!(); + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenBuffers(n, result.as_mut_ptr()); + } + result + } + + fn gen_renderbuffers(&self, n: GLsizei) -> Vec { + debug!("gen_renderbuffers {}", n); + //panic!(); + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenRenderbuffers(n, result.as_mut_ptr()); + } + result + } + + fn gen_framebuffers(&self, n: GLsizei) -> Vec { + //panic!(); + debug!("gen_framebuffers {}", n); + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenFramebuffers(n, result.as_mut_ptr()); + } + result + } + + fn gen_textures(&self, n: GLsizei) -> Vec { + //panic!(); + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenTextures(n, result.as_mut_ptr()); + } + result + } + + fn gen_vertex_arrays(&self, n: GLsizei) -> Vec { + //panic!(); + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenVertexArrays(n, result.as_mut_ptr()); + } + result + } + + fn gen_vertex_arrays_apple(&self, n: GLsizei) -> Vec { + self.gen_vertex_arrays(n) + } + + fn gen_queries(&self, n: GLsizei) -> Vec { + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenQueries(n, result.as_mut_ptr()); + } + result + } + + fn begin_query(&self, target: GLenum, id: GLuint) { + unsafe { + BeginQuery(target, id); + } + } + + fn end_query(&self, target: GLenum) { + unsafe { + EndQuery(target); + } + } + + fn query_counter(&self, id: GLuint, target: GLenum) { + panic!(); + } + + fn get_query_object_iv(&self, id: GLuint, pname: GLenum) -> i32 { + panic!(); + //0 + } + + fn get_query_object_uiv(&self, id: GLuint, pname: GLenum) -> u32 { + panic!(); + //0 + } + + fn get_query_object_i64v(&self, id: GLuint, pname: GLenum) -> i64 { + panic!(); + //0 + } + + fn get_query_object_ui64v(&self, id: GLuint, pname: GLenum) -> u64 { + let mut result = 0; + unsafe { + GetQueryObjectui64v(id, pname, &mut result); + } + result + } + + fn delete_queries(&self, queries: &[GLuint]) { + unsafe { + for q in queries { + DeleteQuery(*q); + } + } + } + + fn delete_vertex_arrays(&self, vertex_arrays: &[GLuint]) { + unsafe { + for v in vertex_arrays { + DeleteVertexArray(*v); + } + } + } + + fn delete_vertex_arrays_apple(&self, vertex_arrays: &[GLuint]) { + self.delete_vertex_arrays(vertex_arrays) + } + + fn delete_buffers(&self, buffers: &[GLuint]) { + unsafe { + for b in buffers { + DeleteBuffer(*b); + } + } + } + + fn delete_renderbuffers(&self, renderbuffers: &[GLuint]) { + unsafe { + for r in renderbuffers { + DeleteRenderbuffer(*r); + } + } + } + + fn delete_framebuffers(&self, framebuffers: &[GLuint]) { + unsafe { + for f in framebuffers { + DeleteFramebuffer(*f); + } + } + } + + fn delete_textures(&self, textures: &[GLuint]) { + unsafe { + for t in textures { + DeleteTexture(*t); + } + } + } + + fn framebuffer_renderbuffer( + &self, + target: GLenum, + attachment: GLenum, + renderbuffertarget: GLenum, + renderbuffer: GLuint, + ) { + debug!( + "framebufer_renderbuffer {} {} {} {}", + target, attachment, renderbuffertarget, renderbuffer + ); + //panic!(); + unsafe { + FramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer); + } + } + + fn renderbuffer_storage( + &self, + target: GLenum, + internalformat: GLenum, + width: GLsizei, + height: GLsizei, + ) { + debug!( + "renderbuffer_storage {} {} {} {}", + target, internalformat, width, height + ); + //panic!(); + unsafe { + RenderbufferStorage(target, internalformat, width, height); + } + } + + fn depth_func(&self, func: GLenum) { + debug!("depth_func {}", func); + //panic!(); + unsafe { + DepthFunc(func); + } + } + + fn active_texture(&self, texture: GLenum) { + //panic!(); + unsafe { + ActiveTexture(texture); + } + } + + fn attach_shader(&self, program: GLuint, shader: GLuint) { + debug!("attach shader {} {}", program, shader); + //panic!(); + unsafe { + AttachShader(program, shader); + } + } + + fn bind_attrib_location(&self, program: GLuint, index: GLuint, name: &str) { + debug!("bind_attrib_location {} {} {}", program, index, name); + //panic!(); + let c_string = CString::new(name).unwrap(); + unsafe { BindAttribLocation(program, index, c_string.as_ptr()) } + } + + // https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glGetUniform.xml + unsafe fn get_uniform_iv(&self, program: GLuint, location: GLint, result: &mut [GLint]) { + panic!(); + //assert!(!result.is_empty()); + } + + // https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glGetUniform.xml + unsafe fn get_uniform_fv(&self, program: GLuint, location: GLint, result: &mut [GLfloat]) { + panic!(); + //assert!(!result.is_empty()); + } + + fn get_uniform_block_index(&self, program: GLuint, name: &str) -> GLuint { + panic!(); + //0 + } + + fn get_uniform_indices(&self, program: GLuint, names: &[&str]) -> Vec { + panic!(); + //Vec::new() + } + + fn bind_buffer_base(&self, target: GLenum, index: GLuint, buffer: GLuint) { + panic!(); + } + + fn bind_buffer_range( + &self, + target: GLenum, + index: GLuint, + buffer: GLuint, + offset: GLintptr, + size: GLsizeiptr, + ) { + panic!(); + } + + fn uniform_block_binding( + &self, + program: GLuint, + uniform_block_index: GLuint, + uniform_block_binding: GLuint, + ) { + panic!(); + } + + fn bind_buffer(&self, target: GLenum, buffer: GLuint) { + //panic!(); + unsafe { + BindBuffer(target, buffer); + } + } + + fn bind_vertex_array(&self, vao: GLuint) { + //panic!(); + unsafe { + BindVertexArray(vao); + } + } + + fn bind_vertex_array_apple(&self, vao: GLuint) { + self.bind_vertex_array(vao) + } + + fn bind_renderbuffer(&self, target: GLenum, renderbuffer: GLuint) { + debug!("bind_renderbuffer {} {}", target, renderbuffer); + //panic!(); + unsafe { + BindRenderbuffer(target, renderbuffer); + } + } + + fn bind_framebuffer(&self, target: GLenum, framebuffer: GLuint) { + debug!("bind_framebuffer {} {}", target, framebuffer); + //panic!(); + unsafe { + BindFramebuffer(target, framebuffer); + } + } + + fn bind_texture(&self, target: GLenum, texture: GLuint) { + //panic!(); + unsafe { + BindTexture(target, texture); + } + } + + fn draw_buffers(&self, bufs: &[GLenum]) { + panic!(); + //unsafe {} + } + + // FIXME: Does not verify buffer size -- unsafe! + fn tex_image_2d( + &self, + target: GLenum, + level: GLint, + internal_format: GLint, + width: GLsizei, + height: GLsizei, + border: GLint, + format: GLenum, + ty: GLenum, + opt_data: Option<&[u8]>, + ) { + unsafe { + let pdata = match opt_data { + Some(data) => data.as_ptr() as *const GLvoid, + None => ptr::null(), + }; + TexImage2D( + target, + level, + internal_format, + width, + height, + border, + format, + ty, + pdata, + ); + } + } + + fn compressed_tex_image_2d( + &self, + target: GLenum, + level: GLint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + border: GLint, + data: &[u8], + ) { + panic!(); + } + + fn compressed_tex_sub_image_2d( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + data: &[u8], + ) { + panic!(); + } + + // FIXME: Does not verify buffer size -- unsafe! + fn tex_image_3d( + &self, + target: GLenum, + level: GLint, + internal_format: GLint, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + border: GLint, + format: GLenum, + ty: GLenum, + opt_data: Option<&[u8]>, + ) { + unsafe { + let pdata = match opt_data { + Some(data) => data.as_ptr() as *const GLvoid, + None => ptr::null(), + }; + TexImage3D( + target, + level, + internal_format, + width, + height, + depth, + border, + format, + ty, + pdata, + ); + } + } + + fn copy_tex_image_2d( + &self, + target: GLenum, + level: GLint, + internal_format: GLenum, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + border: GLint, + ) { + panic!(); + } + + fn copy_tex_sub_image_2d( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + ) { + unsafe { + CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); + } + } + + fn copy_tex_sub_image_3d( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + zoffset: GLint, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + ) { + unsafe { + CopyTexSubImage3D( + target, level, xoffset, yoffset, zoffset, x, y, width, height, + ); + } + } + + fn tex_sub_image_2d( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + ty: GLenum, + data: &[u8], + ) { + debug!( + "tex_sub_image_2d {} {} {} {} {} {} {} {}", + target, level, xoffset, yoffset, width, height, format, ty + ); + //panic!(); + unsafe { + TexSubImage2D( + target, + level, + xoffset, + yoffset, + width, + height, + format, + ty, + data.as_ptr() as *const c_void, + ); + } + } + + fn tex_sub_image_2d_pbo( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + ty: GLenum, + offset: usize, + ) { + debug!( + "tex_sub_image_2d_pbo {} {} {} {} {} {} {} {} {}", + target, level, xoffset, yoffset, width, height, format, ty, offset + ); + //panic!(); + unsafe { + TexSubImage2D( + target, + level, + xoffset, + yoffset, + width, + height, + format, + ty, + offset as *const c_void, + ); + } + } + + fn tex_sub_image_3d( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + zoffset: GLint, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + format: GLenum, + ty: GLenum, + data: &[u8], + ) { + debug!("tex_sub_image_3d"); + //panic!(); + unsafe { + TexSubImage3D( + target, + level, + xoffset, + yoffset, + zoffset, + width, + height, + depth, + format, + ty, + data.as_ptr() as *const c_void, + ); + } + } + + fn tex_sub_image_3d_pbo( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + zoffset: GLint, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + format: GLenum, + ty: GLenum, + offset: usize, + ) { + unsafe { + TexSubImage3D( + target, + level, + xoffset, + yoffset, + zoffset, + width, + height, + depth, + format, + ty, + offset as *const c_void, + ); + } + } + + fn tex_storage_2d( + &self, + target: GLenum, + levels: GLint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + ) { + //panic!(); + unsafe { + TexStorage2D(target, levels, internal_format, width, height); + } + } + + fn tex_storage_3d( + &self, + target: GLenum, + levels: GLint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + ) { + //panic!(); + unsafe { + TexStorage3D(target, levels, internal_format, width, height, depth); + } + } + + fn get_tex_image_into_buffer( + &self, + target: GLenum, + level: GLint, + format: GLenum, + ty: GLenum, + output: &mut [u8], + ) { + panic!(); + } + + unsafe fn copy_image_sub_data( + &self, + src_name: GLuint, + src_target: GLenum, + src_level: GLint, + src_x: GLint, + src_y: GLint, + src_z: GLint, + dst_name: GLuint, + dst_target: GLenum, + dst_level: GLint, + dst_x: GLint, + dst_y: GLint, + dst_z: GLint, + src_width: GLsizei, + src_height: GLsizei, + src_depth: GLsizei, + ) { + CopyImageSubData( + src_name, src_target, src_level, src_x, src_y, src_z, dst_name, dst_target, dst_level, + dst_x, dst_y, dst_z, src_width, src_height, src_depth, + ); + } + + fn invalidate_framebuffer(&self, target: GLenum, attachments: &[GLenum]) { + unsafe { + InvalidateFramebuffer(target, attachments.len() as GLsizei, attachments.as_ptr()); + } + } + + fn invalidate_sub_framebuffer( + &self, + target: GLenum, + attachments: &[GLenum], + xoffset: GLint, + yoffset: GLint, + width: GLsizei, + height: GLsizei, + ) { + } + + #[inline] + unsafe fn get_integer_v(&self, name: GLenum, result: &mut [GLint]) { + //panic!(); + assert!(!result.is_empty()); + GetIntegerv(name, result.as_mut_ptr()); + } + + #[inline] + unsafe fn get_integer_64v(&self, name: GLenum, result: &mut [GLint64]) { + panic!(); + //assert!(!result.is_empty()); + } + + #[inline] + unsafe fn get_integer_iv(&self, name: GLenum, index: GLuint, result: &mut [GLint]) { + panic!(); + //assert!(!result.is_empty()); + } + + #[inline] + unsafe fn get_integer_64iv(&self, name: GLenum, index: GLuint, result: &mut [GLint64]) { + panic!(); + //assert!(!result.is_empty()); + } + + #[inline] + unsafe fn get_boolean_v(&self, name: GLenum, result: &mut [GLboolean]) { + debug!("get_boolean_v {}", name); + //panic!(); + assert!(!result.is_empty()); + GetBooleanv(name, result.as_mut_ptr()); + } + + #[inline] + unsafe fn get_float_v(&self, name: GLenum, result: &mut [GLfloat]) { + panic!(); + //assert!(!result.is_empty()); + } + + fn get_framebuffer_attachment_parameter_iv( + &self, + target: GLenum, + attachment: GLenum, + pname: GLenum, + ) -> GLint { + panic!(); + //0 + } + + fn get_renderbuffer_parameter_iv(&self, target: GLenum, pname: GLenum) -> GLint { + panic!(); + //0 + } + + fn get_tex_parameter_iv(&self, target: GLenum, pname: GLenum) -> GLint { + panic!(); + //0 + } + + fn get_tex_parameter_fv(&self, target: GLenum, pname: GLenum) -> GLfloat { + panic!(); + //0.0 + } + + fn tex_parameter_i(&self, target: GLenum, pname: GLenum, param: GLint) { + //panic!(); + unsafe { + TexParameteri(target, pname, param); + } + } + + fn tex_parameter_f(&self, target: GLenum, pname: GLenum, param: GLfloat) { + panic!(); + } + + fn framebuffer_texture_2d( + &self, + target: GLenum, + attachment: GLenum, + textarget: GLenum, + texture: GLuint, + level: GLint, + ) { + debug!( + "framebuffer_texture_2d {} {} {} {} {}", + target, attachment, textarget, texture, level + ); + //panic!(); + unsafe { + FramebufferTexture2D(target, attachment, textarget, texture, level); + } + } + + fn framebuffer_texture_layer( + &self, + target: GLenum, + attachment: GLenum, + texture: GLuint, + level: GLint, + layer: GLint, + ) { + debug!( + "framebuffer_texture_layer {} {} {} {} {}", + target, attachment, texture, level, layer + ); + //panic!(); + unsafe { + FramebufferTextureLayer(target, attachment, texture, level, layer); + } + } + + fn blit_framebuffer( + &self, + src_x0: GLint, + src_y0: GLint, + src_x1: GLint, + src_y1: GLint, + dst_x0: GLint, + dst_y0: GLint, + dst_x1: GLint, + dst_y1: GLint, + mask: GLbitfield, + filter: GLenum, + ) { + unsafe { + BlitFramebuffer( + src_x0, src_y0, src_x1, src_y1, dst_x0, dst_y0, dst_x1, dst_y1, mask, filter, + ); + } + } + + fn vertex_attrib_4f(&self, index: GLuint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) { + panic!(); + } + + fn vertex_attrib_pointer_f32( + &self, + index: GLuint, + size: GLint, + normalized: bool, + stride: GLsizei, + offset: GLuint, + ) { + panic!(); + } + + fn vertex_attrib_pointer( + &self, + index: GLuint, + size: GLint, + type_: GLenum, + normalized: bool, + stride: GLsizei, + offset: GLuint, + ) { + debug!( + "vertex_attrib_pointer {} {} {} {} {} {}", + index, size, type_, normalized, stride, offset + ); + //panic!(); + unsafe { + VertexAttribPointer( + index, + size, + type_, + normalized as GLboolean, + stride, + offset as *const GLvoid, + ); + } + } + + fn vertex_attrib_i_pointer( + &self, + index: GLuint, + size: GLint, + type_: GLenum, + stride: GLsizei, + offset: GLuint, + ) { + debug!( + "vertex_attrib_i_pointer {} {} {} {} {}", + index, size, type_, stride, offset + ); + //panic!(); + unsafe { + VertexAttribIPointer(index, size, type_, stride, offset as *const GLvoid); + } + } + + fn vertex_attrib_divisor(&self, index: GLuint, divisor: GLuint) { + debug!("vertex_attrib_divisor {} {}", index, divisor); + //assert!(index == 0 && divisor == 0); + //panic!(); + unsafe { + VertexAttribDivisor(index, divisor); + } + } + + fn viewport(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) { + debug!("viewport {} {} {} {}", x, y, width, height); + //panic!(); + unsafe { + SetViewport(x, y, width, height); + } + } + + fn scissor(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) { + //panic!(); + unsafe { + SetScissor(x, y, width, height); + } + } + + fn line_width(&self, width: GLfloat) { + panic!(); + } + + fn use_program(&self, program: GLuint) { + //panic!(); + unsafe { + UseProgram(program); + } + } + + fn validate_program(&self, program: GLuint) { + panic!(); + } + + fn draw_arrays(&self, mode: GLenum, first: GLint, count: GLsizei) { + panic!(); + } + + fn draw_arrays_instanced( + &self, + mode: GLenum, + first: GLint, + count: GLsizei, + primcount: GLsizei, + ) { + panic!(); + } + + fn draw_elements( + &self, + mode: GLenum, + count: GLsizei, + element_type: GLenum, + indices_offset: GLuint, + ) { + debug!( + "draw_elements {} {} {} {} {}", + mode, count, element_type, indices_offset + ); + //panic!(); + unsafe { + DrawElementsInstanced( + mode, + count, + element_type, + indices_offset as *const c_void, + 1, + ); + } + } + + fn draw_elements_instanced( + &self, + mode: GLenum, + count: GLsizei, + element_type: GLenum, + indices_offset: GLuint, + primcount: GLsizei, + ) { + debug!( + "draw_elements_instanced {} {} {} {} {}", + mode, count, element_type, indices_offset, primcount + ); + //panic!(); + unsafe { + DrawElementsInstanced( + mode, + count, + element_type, + indices_offset as *const c_void, + primcount, + ); + } + } + + fn blend_color(&self, r: f32, g: f32, b: f32, a: f32) { + unsafe { + BlendColor(r, g, b, a); + } + } + + fn blend_func(&self, sfactor: GLenum, dfactor: GLenum) { + unsafe { + BlendFunc(sfactor, dfactor, sfactor, dfactor); + } + } + + fn blend_func_separate( + &self, + src_rgb: GLenum, + dest_rgb: GLenum, + src_alpha: GLenum, + dest_alpha: GLenum, + ) { + unsafe { + BlendFunc(src_rgb, dest_rgb, src_alpha, dest_alpha); + } + } + + fn blend_equation(&self, mode: GLenum) { + unsafe { + BlendEquation(mode); + } + } + + fn blend_equation_separate(&self, mode_rgb: GLenum, mode_alpha: GLenum) { + panic!(); + } + + fn color_mask(&self, r: bool, g: bool, b: bool, a: bool) { + panic!(); + } + + fn cull_face(&self, mode: GLenum) { + panic!(); + } + + fn front_face(&self, mode: GLenum) { + panic!(); + } + + fn enable(&self, cap: GLenum) { + debug!("enable {}", cap); + //panic!(); + unsafe { + Enable(cap); + } + } + + fn disable(&self, cap: GLenum) { + debug!("disable {}", cap); + //panic!(); + unsafe { + Disable(cap); + } + } + + fn hint(&self, param_name: GLenum, param_val: GLenum) { + panic!(); + } + + fn is_enabled(&self, cap: GLenum) -> GLboolean { + panic!(); + //0 + } + + fn is_shader(&self, shader: GLuint) -> GLboolean { + panic!(); + //0 + } + + fn is_texture(&self, texture: GLenum) -> GLboolean { + panic!(); + //0 + } + + fn is_framebuffer(&self, framebuffer: GLenum) -> GLboolean { + panic!(); + //0 + } + + fn is_renderbuffer(&self, renderbuffer: GLenum) -> GLboolean { + panic!(); + //0 + } + + fn check_frame_buffer_status(&self, target: GLenum) -> GLenum { + debug!("check_frame_buffer_status {}", target); + //panic!(); + unsafe { CheckFramebufferStatus(target) } + } + + fn enable_vertex_attrib_array(&self, index: GLuint) { + //panic!(); + debug!("enable_vertex_attrib_array {}", index); + unsafe { + EnableVertexAttribArray(index); + //assert_eq!(index, 0); + } + } + + fn disable_vertex_attrib_array(&self, index: GLuint) { + panic!(); + } + + fn uniform_1f(&self, location: GLint, v0: GLfloat) { + panic!(); + } + + fn uniform_1fv(&self, location: GLint, values: &[f32]) { + panic!(); + } + + fn uniform_1i(&self, location: GLint, v0: GLint) { + debug!("uniform_1i {} {}", location, v0); + //panic!(); + unsafe { + Uniform1i(location, v0); + } + } + + fn uniform_1iv(&self, location: GLint, values: &[i32]) { + panic!(); + } + + fn uniform_1ui(&self, location: GLint, v0: GLuint) { + panic!(); + } + + fn uniform_2f(&self, location: GLint, v0: GLfloat, v1: GLfloat) { + panic!(); + } + + fn uniform_2fv(&self, location: GLint, values: &[f32]) { + panic!(); + } + + fn uniform_2i(&self, location: GLint, v0: GLint, v1: GLint) { + panic!(); + } + + fn uniform_2iv(&self, location: GLint, values: &[i32]) { + panic!(); + } + + fn uniform_2ui(&self, location: GLint, v0: GLuint, v1: GLuint) { + panic!(); + } + + fn uniform_3f(&self, location: GLint, v0: GLfloat, v1: GLfloat, v2: GLfloat) { + panic!(); + } + + fn uniform_3fv(&self, location: GLint, values: &[f32]) { + panic!(); + } + + fn uniform_3i(&self, location: GLint, v0: GLint, v1: GLint, v2: GLint) { + panic!(); + } + + fn uniform_3iv(&self, location: GLint, values: &[i32]) { + panic!(); + } + + fn uniform_3ui(&self, location: GLint, v0: GLuint, v1: GLuint, v2: GLuint) { + panic!(); + } + + fn uniform_4f(&self, location: GLint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) { + panic!(); + } + + fn uniform_4i(&self, location: GLint, x: GLint, y: GLint, z: GLint, w: GLint) { + panic!(); + } + + fn uniform_4iv(&self, location: GLint, values: &[i32]) { + panic!(); + } + + fn uniform_4ui(&self, location: GLint, x: GLuint, y: GLuint, z: GLuint, w: GLuint) { + panic!(); + } + + fn uniform_4fv(&self, location: GLint, values: &[f32]) { + unsafe { + Uniform4fv(location, (values.len() / 4) as GLsizei, values.as_ptr()); + } + } + + fn uniform_matrix_2fv(&self, location: GLint, transpose: bool, value: &[f32]) { + panic!(); + } + + fn uniform_matrix_3fv(&self, location: GLint, transpose: bool, value: &[f32]) { + panic!(); + } + + fn uniform_matrix_4fv(&self, location: GLint, transpose: bool, value: &[f32]) { + debug!("uniform_matrix_4fv {} {} {:?}", location, transpose, value); + //panic!(); + unsafe { + UniformMatrix4fv( + location, + (value.len() / 16) as GLsizei, + transpose as GLboolean, + value.as_ptr(), + ); + } + } + + fn depth_mask(&self, flag: bool) { + debug!("depth_mask {}", flag); + //panic!(); + unsafe { + DepthMask(flag as GLboolean); + } + } + + fn depth_range(&self, near: f64, far: f64) { + panic!(); + } + + fn get_active_attrib(&self, program: GLuint, index: GLuint) -> (i32, u32, String) { + panic!(); + //(0, 0, String::new()) + } + + fn get_active_uniform(&self, program: GLuint, index: GLuint) -> (i32, u32, String) { + panic!(); + //(0, 0, String::new()) + } + + fn get_active_uniforms_iv( + &self, + program: GLuint, + indices: Vec, + pname: GLenum, + ) -> Vec { + panic!(); + //Vec::new() + } + + fn get_active_uniform_block_i(&self, program: GLuint, index: GLuint, pname: GLenum) -> GLint { + panic!(); + //0 + } + + fn get_active_uniform_block_iv( + &self, + program: GLuint, + index: GLuint, + pname: GLenum, + ) -> Vec { + panic!(); + //Vec::new() + } + + fn get_active_uniform_block_name(&self, program: GLuint, index: GLuint) -> String { + panic!(); + //String::new() + } + + fn get_attrib_location(&self, program: GLuint, name: &str) -> c_int { + let name = CString::new(name).unwrap(); + unsafe { GetAttribLocation(program, name.as_ptr()) } + } + + fn get_frag_data_location(&self, program: GLuint, name: &str) -> c_int { + panic!(); + //0 + } + + fn get_uniform_location(&self, program: GLuint, name: &str) -> c_int { + debug!("get_uniform_location {} {}", program, name); + //panic!(); + let name = CString::new(name).unwrap(); + unsafe { GetUniformLocation(program, name.as_ptr()) } + } + + fn get_program_info_log(&self, program: GLuint) -> String { + panic!(); + //String::new() + } + + #[inline] + unsafe fn get_program_iv(&self, program: GLuint, pname: GLenum, result: &mut [GLint]) { + debug!("get_program_iv {}", pname); + //panic!(); + assert!(!result.is_empty()); + //#define GL_LINK_STATUS 0x8B82 + if pname == 0x8b82 { + result[0] = 1; + } + } + + fn get_program_binary(&self, program: GLuint) -> (Vec, GLenum) { + panic!(); + //(Vec::new(), NONE) + } + + fn program_binary(&self, program: GLuint, format: GLenum, binary: &[u8]) { + panic!(); + } + + fn program_parameter_i(&self, program: GLuint, pname: GLenum, value: GLint) { + panic!(); + } + + #[inline] + unsafe fn get_vertex_attrib_iv(&self, index: GLuint, pname: GLenum, result: &mut [GLint]) { + panic!(); + //assert!(!result.is_empty()); + } + + #[inline] + unsafe fn get_vertex_attrib_fv(&self, index: GLuint, pname: GLenum, result: &mut [GLfloat]) { + panic!(); + //assert!(!result.is_empty()); + } + + fn get_vertex_attrib_pointer_v(&self, index: GLuint, pname: GLenum) -> GLsizeiptr { + panic!(); + //0 + } + + fn get_buffer_parameter_iv(&self, target: GLuint, pname: GLenum) -> GLint { + panic!(); + //0 + } + + fn get_shader_info_log(&self, shader: GLuint) -> String { + debug!("get_shader_info_log {}", shader); + //panic!(); + String::new() + } + + fn get_string(&self, which: GLenum) -> String { + // panic!(); + unsafe { + let llstr = GetString(which); + if !llstr.is_null() { + return str::from_utf8_unchecked(CStr::from_ptr(llstr).to_bytes()).to_string(); + } else { + return "".to_string(); + } + } + } + + fn get_string_i(&self, which: GLenum, index: GLuint) -> String { + //panic!(); + unsafe { + let llstr = GetStringi(which, index); + if !llstr.is_null() { + str::from_utf8_unchecked(CStr::from_ptr(llstr).to_bytes()).to_string() + } else { + "".to_string() + } + } + } + + unsafe fn get_shader_iv(&self, shader: GLuint, pname: GLenum, result: &mut [GLint]) { + debug!("get_shader_iv"); + //panic!(); + assert!(!result.is_empty()); + if pname == 0x8B81 + /*gl::COMPILE_STATUS*/ + { + result[0] = 1; + } + } + + fn get_shader_precision_format( + &self, + _shader_type: GLuint, + precision_type: GLuint, + ) -> (GLint, GLint, GLint) { + // gl.GetShaderPrecisionFormat is not available until OpenGL 4.1. + // Fallback to OpenGL standard precissions that most desktop hardware support. + match precision_type { + LOW_FLOAT | MEDIUM_FLOAT | HIGH_FLOAT => { + // Fallback to IEEE 754 single precision + // Range: from -2^127 to 2^127 + // Significand precision: 23 bits + (127, 127, 23) + } + LOW_INT | MEDIUM_INT | HIGH_INT => { + // Fallback to single precision integer + // Range: from -2^24 to 2^24 + // Precision: For integer formats this value is always 0 + (24, 24, 0) + } + _ => (0, 0, 0), + } + } + + fn compile_shader(&self, shader: GLuint) { + debug!("compile_shader {}", shader); + //panic!(); + } + + fn create_program(&self) -> GLuint { + debug!("create_program"); + //panic!(); + unsafe { CreateProgram() } + } + + fn delete_program(&self, program: GLuint) { + unsafe { + DeleteProgram(program); + } + } + + fn create_shader(&self, shader_type: GLenum) -> GLuint { + debug!("create_shader {}", shader_type); + //panic!(); + unsafe { CreateShader(shader_type) } + } + + fn delete_shader(&self, shader: GLuint) { + debug!("delete_shader {}", shader); + //panic!(); + unsafe { + DeleteShader(shader); + } + } + + fn detach_shader(&self, program: GLuint, shader: GLuint) { + debug!("detach_shader {} {}", program, shader); + //panic!(); + } + + fn link_program(&self, program: GLuint) { + debug!("link_program {}", program); + //panic!(); + unsafe { + LinkProgram(program); + } + } + + fn clear_color(&self, r: f32, g: f32, b: f32, a: f32) { + //panic!(); + unsafe { + ClearColor(r, g, b, a); + } + } + + fn clear(&self, buffer_mask: GLbitfield) { + debug!("clear {}", buffer_mask); + //panic!(); + unsafe { + Clear(buffer_mask); + } + } + + fn clear_depth(&self, depth: f64) { + debug!("clear_depth {}", depth); + //panic!(); + unsafe { + ClearDepth(depth as GLclampd); + } + } + + fn clear_stencil(&self, s: GLint) { + panic!(); + } + + fn flush(&self) {} + + fn finish(&self) { + unsafe { + Finish(); + } + } + + fn get_error(&self) -> GLenum { + //panic!(); + unsafe { GetError() } + } + + fn stencil_mask(&self, mask: GLuint) { + panic!(); + } + + fn stencil_mask_separate(&self, face: GLenum, mask: GLuint) { + panic!(); + } + + fn stencil_func(&self, func: GLenum, ref_: GLint, mask: GLuint) { + panic!(); + } + + fn stencil_func_separate(&self, face: GLenum, func: GLenum, ref_: GLint, mask: GLuint) { + panic!(); + } + + fn stencil_op(&self, sfail: GLenum, dpfail: GLenum, dppass: GLenum) { + panic!(); + } + + fn stencil_op_separate(&self, face: GLenum, sfail: GLenum, dpfail: GLenum, dppass: GLenum) { + panic!(); + } + + fn egl_image_target_texture2d_oes(&self, target: GLenum, image: GLeglImageOES) { + panic!("not supported") + } + + fn egl_image_target_renderbuffer_storage_oes(&self, target: GLenum, image: GLeglImageOES) { + panic!("not supported") + } + + fn generate_mipmap(&self, target: GLenum) { + panic!(); + } + + fn insert_event_marker_ext(&self, message: &str) { + panic!(); + } + + fn push_group_marker_ext(&self, message: &str) { + debug!("push group {}", message); + panic!(); + } + + fn pop_group_marker_ext(&self) { + debug!("pop group"); + panic!(); + } + + fn debug_message_insert_khr( + &self, + source: GLenum, + type_: GLenum, + id: GLuint, + severity: GLenum, + message: &str, + ) { + panic!(); + } + + fn push_debug_group_khr(&self, source: GLenum, id: GLuint, message: &str) { + panic!(); + } + + fn pop_debug_group_khr(&self) { + panic!(); + } + + fn fence_sync(&self, condition: GLenum, flags: GLbitfield) -> GLsync { + panic!(); + //ptr::null() + } + + fn client_wait_sync(&self, sync: GLsync, flags: GLbitfield, timeout: GLuint64) { + panic!(); + } + + fn wait_sync(&self, sync: GLsync, flags: GLbitfield, timeout: GLuint64) { + panic!(); + } + + fn texture_range_apple(&self, target: GLenum, data: &[u8]) { + panic!(); + } + + fn delete_sync(&self, sync: GLsync) { + panic!(); + } + + fn gen_fences_apple(&self, n: GLsizei) -> Vec { + panic!(); + //Vec::new() + } + + fn delete_fences_apple(&self, fences: &[GLuint]) { + panic!(); + } + + fn set_fence_apple(&self, fence: GLuint) { + panic!(); + } + + fn finish_fence_apple(&self, fence: GLuint) { + panic!(); + } + + fn test_fence_apple(&self, fence: GLuint) { + panic!(); + } + + fn test_object_apple(&self, object: GLenum, name: GLuint) -> GLboolean { + panic!(); + //0 + } + + fn finish_object_apple(&self, object: GLenum, name: GLuint) { + panic!(); + } + + // GL_ARB_blend_func_extended + fn bind_frag_data_location_indexed( + &self, + program: GLuint, + color_number: GLuint, + index: GLuint, + name: &str, + ) { + panic!(); + } + + fn get_frag_data_index(&self, program: GLuint, name: &str) -> GLint { + panic!(); + //-1 + } + + // GL_KHR_debug + fn get_debug_messages(&self) -> Vec { + Vec::new() + } + + fn provoking_vertex_angle(&self, _mode: GLenum) { + unimplemented!("This extension is GLES only"); + } + + // GL_KHR_blend_equation_advanced + fn blend_barrier_khr(&self) { + panic!(); + } + + // GL_CHROMIUM_copy_texture + fn copy_texture_chromium( + &self, + _source_id: GLuint, + _source_level: GLint, + _dest_target: GLenum, + _dest_id: GLuint, + _dest_level: GLint, + _internal_format: GLint, + _dest_type: GLenum, + _unpack_flip_y: GLboolean, + _unpack_premultiply_alpha: GLboolean, + _unpack_unmultiply_alpha: GLboolean, + ) { + unimplemented!("This extension is GLES only"); + } + fn copy_sub_texture_chromium( + &self, + _source_id: GLuint, + _source_level: GLint, + _dest_target: GLenum, + _dest_id: GLuint, + _dest_level: GLint, + _x_offset: GLint, + _y_offset: GLint, + _x: GLint, + _y: GLint, + _width: GLsizei, + _height: GLsizei, + _unpack_flip_y: GLboolean, + _unpack_premultiply_alpha: GLboolean, + _unpack_unmultiply_alpha: GLboolean, + ) { + unimplemented!("This extension is GLES only"); + } +} diff --git a/swgl/src/texture.h b/swgl/src/texture.h new file mode 100644 index 0000000000..3ec0136c9c --- /dev/null +++ b/swgl/src/texture.h @@ -0,0 +1,121 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +template +void textureLinearCommit4(S sampler, ivec2 i, int zoffset, uint32_t* buf) { + assert(sampler->format == TextureFormat::RGBA8); + ivec2 frac = i & 0x7F; + i >>= 7; + + I32 row0 = clampCoord(i.x, sampler->width) + + clampCoord(i.y, sampler->height) * sampler->stride + zoffset; + I32 row1 = row0 + ((i.y > 0 && i.y < int32_t(sampler->height) - 1) & + I32(sampler->stride)); + I16 fracx = + CONVERT(frac.x & (i.x > 0 && i.x < int32_t(sampler->width) - 1), I16); + I16 fracy = CONVERT(frac.y, I16); + + auto a0 = + CONVERT(unaligned_load>(&sampler->buf[row0.x]), V8); + auto a1 = + CONVERT(unaligned_load>(&sampler->buf[row1.x]), V8); + a0 += ((a1 - a0) * fracy.x) >> 7; + + auto b0 = + CONVERT(unaligned_load>(&sampler->buf[row0.y]), V8); + auto b1 = + CONVERT(unaligned_load>(&sampler->buf[row1.y]), V8); + b0 += ((b1 - b0) * fracy.y) >> 7; + + auto abl = combine(lowHalf(a0), lowHalf(b0)); + auto abh = combine(highHalf(a0), highHalf(b0)); + abl += ((abh - abl) * fracx.xxxxyyyy) >> 7; + + auto c0 = + CONVERT(unaligned_load>(&sampler->buf[row0.z]), V8); + auto c1 = + CONVERT(unaligned_load>(&sampler->buf[row1.z]), V8); + c0 += ((c1 - c0) * fracy.z) >> 7; + + auto d0 = + CONVERT(unaligned_load>(&sampler->buf[row0.w]), V8); + auto d1 = + CONVERT(unaligned_load>(&sampler->buf[row1.w]), V8); + d0 += ((d1 - d0) * fracy.w) >> 7; + + auto cdl = combine(lowHalf(c0), lowHalf(d0)); + auto cdh = combine(highHalf(c0), highHalf(d0)); + cdl += ((cdh - cdl) * fracx.zzzzwwww) >> 7; + + commit_span(buf, pack(combine(HalfRGBA8(abl), HalfRGBA8(cdl)))); +} + +template +void textureLinearCommit8(S sampler, ivec2_scalar i, int zoffset, + uint32_t* buf) { + assert(sampler->format == TextureFormat::RGBA8); + ivec2_scalar frac = i & 0x7F; + i >>= 7; + + uint32_t* row0 = + &sampler + ->buf[clampCoord(i.x, sampler->width) + + clampCoord(i.y, sampler->height) * sampler->stride + zoffset]; + uint32_t* row1 = + row0 + + ((i.y > 0 && i.y < int32_t(sampler->height) - 1) ? sampler->stride : 0); + int16_t fracx = i.x > 0 && i.x < int32_t(sampler->width) - 1 ? frac.x : 0; + int16_t fracy = frac.y; + + U32 pix0 = unaligned_load(row0); + U32 pix0n = unaligned_load(row0 + 4); + uint32_t pix0x = row0[8]; + U32 pix1 = unaligned_load(row1); + U32 pix1n = unaligned_load(row1 + 4); + uint32_t pix1x = row1[8]; + + { + auto ab0 = CONVERT(bit_cast>(SHUFFLE(pix0, pix0, 0, 1, 1, 2)), + V16); + auto ab1 = CONVERT(bit_cast>(SHUFFLE(pix1, pix1, 0, 1, 1, 2)), + V16); + ab0 += ((ab1 - ab0) * fracy) >> 7; + + auto cd0 = CONVERT(bit_cast>(SHUFFLE(pix0, pix0n, 2, 3, 3, 4)), + V16); + auto cd1 = CONVERT(bit_cast>(SHUFFLE(pix1, pix1n, 2, 3, 3, 4)), + V16); + cd0 += ((cd1 - cd0) * fracy) >> 7; + + auto abcdl = combine(lowHalf(ab0), lowHalf(cd0)); + auto abcdh = combine(highHalf(ab0), highHalf(cd0)); + abcdl += ((abcdh - abcdl) * fracx) >> 7; + + commit_span(buf, pack(WideRGBA8(abcdl))); + } + + { + auto ab0 = + CONVERT(bit_cast>(SHUFFLE(pix0n, pix0n, 0, 1, 1, 2)), + V16); + auto ab1 = + CONVERT(bit_cast>(SHUFFLE(pix1n, pix1n, 0, 1, 1, 2)), + V16); + ab0 += ((ab1 - ab0) * fracy) >> 7; + + auto cd0 = + CONVERT(bit_cast>(SHUFFLE(pix0n, U32(pix0x), 2, 3, 3, 4)), + V16); + auto cd1 = + CONVERT(bit_cast>(SHUFFLE(pix1n, U32(pix1x), 2, 3, 3, 4)), + V16); + cd0 += ((cd1 - cd0) * fracy) >> 7; + + auto abcdl = combine(lowHalf(ab0), lowHalf(cd0)); + auto abcdh = combine(highHalf(ab0), highHalf(cd0)); + abcdl += ((abcdh - abcdl) * fracx) >> 7; + + commit_span(buf + 4, pack(WideRGBA8(abcdl))); + } +} diff --git a/swgl/src/vector_type.h b/swgl/src/vector_type.h new file mode 100644 index 0000000000..da291cc587 --- /dev/null +++ b/swgl/src/vector_type.h @@ -0,0 +1,473 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef __clang__ +# ifdef __SSE2__ +# include +# define USE_SSE2 1 +# endif +# ifdef __ARM_NEON +# include +# define USE_NEON 1 +# endif +#endif + +namespace glsl { + +#ifdef __clang__ +template +using VectorType = T __attribute__((ext_vector_type(N))); + +# define CONVERT(vector, type) __builtin_convertvector(vector, type) +# define SHUFFLE(a, b, ...) __builtin_shufflevector(a, b, __VA_ARGS__) + +template +SI VectorType combine(VectorType a, VectorType b) { + return __builtin_shufflevector(a, b, 0, 1, 2, 3); +} + +template +SI VectorType combine(VectorType a, VectorType b) { + return __builtin_shufflevector(a, b, 0, 1, 2, 3, 4, 5, 6, 7); +} + +template +SI VectorType combine(VectorType a, VectorType b) { + return __builtin_shufflevector(a, b, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15); +} + +template +SI VectorType lowHalf(VectorType a) { + return __builtin_shufflevector(a, a, 0, 1, 2, 3); +} + +template +SI VectorType highHalf(VectorType a) { + return __builtin_shufflevector(a, a, 4, 5, 6, 7); +} + +template +SI VectorType lowHalf(VectorType a) { + return __builtin_shufflevector(a, a, 0, 1, 2, 3, 4, 5, 6, 7); +} + +template +SI VectorType highHalf(VectorType a) { + return __builtin_shufflevector(a, a, 8, 9, 10, 11, 12, 13, 14, 15); +} + +template +SI VectorType expand(VectorType a) { + return __builtin_shufflevector(a, a, 0, 1, 2, 3, -1, -1, -1, -1); +} +#else +template +struct VectorMask { + typedef T type; +}; +template <> +struct VectorMask { + typedef int32_t type; +}; +template <> +struct VectorMask { + typedef int16_t type; +}; +template <> +struct VectorMask { + typedef int8_t type; +}; +template <> +struct VectorMask { + typedef int type; +}; + +template +struct VectorType { + enum { SIZE = N }; + + typedef T data_type __attribute__((vector_size(sizeof(T) * N))); + typedef typename VectorMask::type mask_index; + typedef mask_index mask_type + __attribute__((vector_size(sizeof(mask_index) * N))); + typedef T half_type __attribute__((vector_size(sizeof(T) * (N / 2)))); + union { + data_type data; + struct { + T x, y, z, w; + }; + T elements[N]; + struct { + half_type low_half, high_half; + }; + }; + + VectorType() = default; + constexpr VectorType(T n) : data{n, n, n, n} {} + constexpr VectorType(T a, T b, T c, T d) : data{a, b, c, d} {} + constexpr VectorType(T a, T b, T c, T d, T e, T f, T g, T h) + : data{a, b, c, d, e, f, g, h} {} + constexpr VectorType(T a, T b, T c, T d, T e, T f, T g, T h, T i, T j, T k, + T l, T m, T n, T o, T p) + : data{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p} {} + + SI VectorType wrap(data_type data) { + VectorType v; + v.data = data; + return v; + } + + T& operator[](size_t i) { return elements[i]; } + T operator[](size_t i) const { return elements[i]; } + + template + operator VectorType() const { + return VectorType::wrap( + (typename VectorType::data_type){U(x), U(y)}); + } + template + operator VectorType() const { + return VectorType::wrap( + (typename VectorType::data_type){U(x), U(y), U(z), U(w)}); + } + template + operator VectorType() const { + return VectorType::wrap((typename VectorType::data_type){ + U(elements[0]), U(elements[1]), U(elements[2]), U(elements[3]), + U(elements[4]), U(elements[5]), U(elements[6]), U(elements[7])}); + } + template + operator VectorType() const { + return VectorType::wrap((typename VectorType::data_type){ + U(elements[0]), + U(elements[1]), + U(elements[2]), + U(elements[3]), + U(elements[4]), + U(elements[5]), + U(elements[6]), + U(elements[7]), + U(elements[8]), + U(elements[9]), + U(elements[10]), + U(elements[11]), + U(elements[12]), + U(elements[13]), + U(elements[14]), + U(elements[15]), + }); + } + + VectorType operator-() const { return wrap(-data); } + VectorType operator~() const { return wrap(~data); } + + VectorType operator&(VectorType x) const { return wrap(data & x.data); } + VectorType operator&(T x) const { return wrap(data & x); } + VectorType operator|(VectorType x) const { return wrap(data | x.data); } + VectorType operator|(T x) const { return wrap(data | x); } + VectorType operator^(VectorType x) const { return wrap(data ^ x.data); } + VectorType operator^(T x) const { return wrap(data ^ x); } + VectorType operator<<(int x) const { return wrap(data << x); } + VectorType operator>>(int x) const { return wrap(data >> x); } + VectorType operator+(VectorType x) const { return wrap(data + x.data); } + VectorType operator+(T x) const { return wrap(data + x); } + friend VectorType operator+(T x, VectorType y) { return wrap(x + y.data); } + VectorType operator-(VectorType x) const { return wrap(data - x.data); } + VectorType operator-(T x) const { return wrap(data - x); } + friend VectorType operator-(T x, VectorType y) { return wrap(x - y.data); } + VectorType operator*(VectorType x) const { return wrap(data * x.data); } + VectorType operator*(T x) const { return wrap(data * x); } + friend VectorType operator*(T x, VectorType y) { return wrap(x * y.data); } + VectorType operator/(VectorType x) const { return wrap(data / x.data); } + VectorType operator/(T x) const { return wrap(data / x); } + friend VectorType operator/(T x, VectorType y) { return wrap(x / y.data); } + VectorType operator%(int x) const { return wrap(data % x); } + + VectorType& operator&=(VectorType x) { + data &= x.data; + return *this; + } + VectorType& operator|=(VectorType x) { + data |= x.data; + return *this; + } + VectorType& operator^=(VectorType x) { + data ^= x.data; + return *this; + } + VectorType& operator<<=(int x) { + data <<= x; + return *this; + } + VectorType& operator>>=(int x) { + data >>= x; + return *this; + } + VectorType& operator+=(VectorType x) { + data += x.data; + return *this; + } + VectorType& operator-=(VectorType x) { + data -= x.data; + return *this; + } + VectorType& operator*=(VectorType x) { + data *= x.data; + return *this; + } + VectorType& operator/=(VectorType x) { + data /= x.data; + return *this; + } + VectorType& operator%=(int x) { + data %= x; + return *this; + } + + VectorType operator==(VectorType x) const { + return VectorType::wrap(data == x.data); + } + VectorType operator!=(VectorType x) const { + return VectorType::wrap(data != x.data); + } + VectorType operator<(VectorType x) const { + return VectorType::wrap(data < x.data); + } + VectorType operator>(VectorType x) const { + return VectorType::wrap(data > x.data); + } + VectorType operator<=(VectorType x) const { + return VectorType::wrap(data <= x.data); + } + VectorType operator>=(VectorType x) const { + return VectorType::wrap(data >= x.data); + } + + VectorType operator!() const { return wrap(!data); } + VectorType operator&&(VectorType x) const { return wrap(data & x.data); } + VectorType operator||(VectorType x) const { return wrap(data | x.data); } + + VectorType& operator=(VectorType x) { + data = x.data; + return *this; + } + + VectorType shuffle(VectorType b, mask_index x, mask_index y, + mask_index z, mask_index w) const { + return VectorType::wrap(__builtin_shuffle( + data, b.data, (typename VectorType::mask_type){x, y, z, w})); + } + VectorType shuffle(VectorType b, mask_index x, mask_index y, + mask_index z, mask_index w, mask_index s, + mask_index t, mask_index u, mask_index v) const { + return VectorType::wrap(__builtin_shuffle( + data, b.data, + (typename VectorType::mask_type){x, y, z, w, s, t, u, v})); + } + VectorType shuffle(VectorType b, mask_index x, mask_index y, + mask_index z, mask_index w, mask_index s, + mask_index t, mask_index u, mask_index v, + mask_index i, mask_index j, mask_index k, + mask_index l, mask_index m, mask_index n, + mask_index o, mask_index p) const { + return VectorType::wrap( + __builtin_shuffle(data, b.data, + (typename VectorType::mask_type){ + x, y, z, w, s, t, u, v, i, j, k, l, m, n, o, p})); + } + + VectorType swizzle(mask_index x, mask_index y, mask_index z, + mask_index w) const { + return VectorType::wrap(__builtin_shuffle( + data, (typename VectorType::mask_type){x, y, z, w})); + } + VectorType swizzle(mask_index x, mask_index y, mask_index z, + mask_index w, mask_index s, mask_index t, + mask_index u, mask_index v) const { + return VectorType::wrap(__builtin_shuffle( + data, (typename VectorType::mask_type){x, y, z, w, s, t, u, v})); + } + + SI VectorType wrap(half_type low, half_type high) { + VectorType v; + v.low_half = low; + v.high_half = high; + return v; + } + + VectorType combine(VectorType high) const { + return VectorType::wrap(data, high.data); + } + +# define xyxy swizzle(0, 1, 0, 1) +# define zwzw swizzle(2, 3, 2, 3) +# define zyxw swizzle(2, 1, 0, 3) +# define xxxxyyyy XXXXYYYY() + VectorType XXXXYYYY() const { + return swizzle(0, 0, 0, 0).combine(swizzle(1, 1, 1, 1)); + } +# define zzzzwwww ZZZZWWWW() + VectorType ZZZZWWWW() const { + return swizzle(2, 2, 2, 2).combine(swizzle(3, 3, 3, 3)); + } +# define xyzwxyzw XYZWXYZW() + VectorType XYZWXYZW() const { return combine(*this); } +# define xyxyxyxy XYXYXYXY() + VectorType XYXYXYXY() const { + return swizzle(0, 1, 0, 1).combine(swizzle(0, 1, 0, 1)); + } +# define zwzwzwzw ZWZWZWZW() + VectorType ZWZWZWZW() const { + return swizzle(2, 3, 2, 3).combine(swizzle(2, 3, 2, 3)); + } +# define xxyyzzww XXYYZZWW() + VectorType XXYYZZWW() const { + return swizzle(0, 0, 1, 1).combine(swizzle(2, 2, 3, 3)); + } +}; + +template +struct VectorType { + typedef T data_type __attribute__((vector_size(sizeof(T) * 2))); + union { + data_type data; + struct { + T x, y; + }; + T elements[2]; + }; +}; + +# define CONVERT(vector, type) ((type)(vector)) +# define SHUFFLE(a, b, ...) a.shuffle(b, __VA_ARGS__) + +template +SI VectorType combine(VectorType a, VectorType b) { + return VectorType::wrap(a.data, b.data); +} + +template +SI VectorType lowHalf(VectorType a) { + return VectorType::wrap(a.low_half); +} + +template +SI VectorType highHalf(VectorType a) { + return VectorType::wrap(a.high_half); +} + +template +SI VectorType expand(VectorType a) { + return combine(a, a); +} +#endif + +template +SI VectorType zipLow(VectorType a, VectorType b) { + return SHUFFLE(a, b, 0, 4, 1, 5); +} + +template +SI VectorType zipHigh(VectorType a, VectorType b) { + return SHUFFLE(a, b, 2, 6, 3, 7); +} + +template +SI VectorType zipLow(VectorType a, VectorType b) { + return SHUFFLE(a, b, 0, 8, 1, 9, 2, 10, 3, 11); +} + +template +SI VectorType zipHigh(VectorType a, VectorType b) { + return SHUFFLE(a, b, 4, 12, 5, 13, 6, 14, 7, 15); +} + +template +SI VectorType zipLow(VectorType a, VectorType b) { + return SHUFFLE(a, b, 0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23); +} + +template +SI VectorType zipHigh(VectorType a, VectorType b) { + return SHUFFLE(a, b, 8, 9, 10, 11, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30, + 31); +} + +template +SI VectorType zip2Low(VectorType a, VectorType b) { + return SHUFFLE(a, b, 0, 1, 8, 9, 2, 3, 10, 11); +} + +template +SI VectorType zip2High(VectorType a, VectorType b) { + return SHUFFLE(a, b, 4, 5, 12, 13, 6, 7, 14, 15); +} + +template +struct Unaligned { + template + SI T load(const P* p) { + T v; + memcpy(&v, p, sizeof(v)); + return v; + } + + template + SI void store(P* p, T v) { + memcpy(p, &v, sizeof(v)); + } +}; + +#ifndef __clang__ +template +struct Unaligned> { + template + SI VectorType load(const P* p) { + VectorType v; + memcpy(v.elements, p, sizeof(v)); + return v; + } + + template + SI void store(P* p, VectorType v) { + memcpy(p, v.elements, sizeof(v)); + } +}; +#endif + +template +SI T unaligned_load(const P* p) { + return Unaligned::load(p); +} + +template +SI void unaligned_store(P* p, T v) { + Unaligned::store(p, v); +} + +template +SI D bit_cast(const S& src) { + static_assert(sizeof(D) == sizeof(S), ""); + return unaligned_load(&src); +} + +template +using V2 = VectorType; +template +using V4 = VectorType; +using Float = V4; +using I32 = V4; +using I16 = V4; +using U64 = V4; +using U32 = V4; +using U16 = V4; +using U8 = V4; +using Bool = V4; +template +using V8 = VectorType; +template +using V16 = VectorType; + +} // namespace glsl diff --git a/webrender/Cargo.toml b/webrender/Cargo.toml index 14677d16ad..cb051db475 100644 --- a/webrender/Cargo.toml +++ b/webrender/Cargo.toml @@ -11,7 +11,7 @@ edition = "2018" [features] default = ["freetype-lib"] freetype-lib = ["freetype/servo-freetype-sys"] -profiler = ["thread_profiler/thread_profiler"] +profiler = ["tracy-rs/enable_profiler"] debugger = ["ws", "serde_json", "serde", "image_loader", "base64"] capture = ["api/serialize", "ron", "serde", "smallvec/serde"] replay = ["api/deserialize", "ron", "serde", "smallvec/serde"] @@ -32,7 +32,7 @@ cfg-if = "0.1.2" cstr = "0.1.2" euclid = { version = "0.20.0", features = ["serde"] } fxhash = "0.2.1" -gleam = "0.9.2" +gleam = "0.10.0" image_loader = { optional = true, version = "0.23", package = "image", default-features = false, features = ["png"] } lazy_static = "1" log = "0.4" @@ -45,13 +45,13 @@ ron = { optional = true, version = "0.1.7" } serde = { optional = true, version = "1.0", features = ["serde_derive"] } serde_json = { optional = true, version = "1.0" } smallvec = "0.6" -thread_profiler = "0.1.1" time = "0.1" api = { version = "0.61.0", path = "../webrender_api", package = "webrender_api" } webrender_build = { version = "0.0.1", path = "../webrender_build" } malloc_size_of = { version = "0.0.1", path = "../wr_malloc_size_of", package = "wr_malloc_size_of" } ws = { optional = true, version = "0.9" } svg_fmt = "0.4" +tracy-rs = { version = "0.1" } [dev-dependencies] mozangle = "0.3.1" diff --git a/webrender/res/base.glsl b/webrender/res/base.glsl index 9d66a064a9..eb343c019b 100644 --- a/webrender/res/base.glsl +++ b/webrender/res/base.glsl @@ -36,6 +36,14 @@ #endif #ifdef WR_VERTEX_SHADER + #ifdef SWGL + // Annotate a vertex attribute as being flat per each drawn primitive instance. + // SWGL can use this information to avoid redundantly loading the attribute in all SIMD lanes. + #define PER_INSTANCE flat + #else + #define PER_INSTANCE + #endif + #define varying out #endif diff --git a/webrender/res/brush.glsl b/webrender/res/brush.glsl index 410b690ecb..4589e34b29 100644 --- a/webrender/res/brush.glsl +++ b/webrender/res/brush.glsl @@ -92,6 +92,17 @@ FWD_DECLARE_VS_FUNCTION(radial_gradient_brush_vs) FWD_DECLARE_VS_FUNCTION(conic_gradient_brush_vs) FWD_DECLARE_VS_FUNCTION(yuv_brush_vs) FWD_DECLARE_VS_FUNCTION(opacity_brush_vs) +FWD_DECLARE_VS_FUNCTION(text_brush_vs) + +// Forward-declare the text vertex shader entry point which is currently +// different from other brushes. +void text_shader_main( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask task, + ClipArea clip_area +); void multi_brush_vs( VertexInfo vi, @@ -120,12 +131,15 @@ void multi_brush_vs( #define INVALID_SEGMENT_INDEX 0xffff -void main(void) { - // Load the brush instance from vertex attributes. - Instance instance = decode_instance_attributes(); +void brush_shader_main_vs( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask pic_task, + ClipArea clip_area +) { int edge_flags = instance.flags & 0xff; int brush_flags = (instance.flags >> 8) & 0xff; - PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address); // Fetch the segment of this brush primitive we are drawing. vec4 segment_data; @@ -156,12 +170,6 @@ void main(void) { VertexInfo vi; - // Fetch the dynamic picture that we are drawing on. - PictureTask pic_task = fetch_picture_task(instance.picture_task_address); - ClipArea clip_area = fetch_clip_area(instance.clip_address); - - Transform transform = fetch_transform(ph.transform_id); - // Write the normal vertex information out. if (transform.is_axis_aligned) { @@ -245,8 +253,25 @@ void main(void) { } +#ifndef WR_VERTEX_SHADER_MAIN_FUNCTION +// If the entry-point was not overridden before including the brush shader, +// use the default one. +#define WR_VERTEX_SHADER_MAIN_FUNCTION brush_shader_main_vs #endif +void main(void) { + + Instance instance = decode_instance_attributes(); + PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address); + Transform transform = fetch_transform(ph.transform_id); + PictureTask task = fetch_picture_task(instance.picture_task_address); + ClipArea clip_area = fetch_clip_area(instance.clip_address); + + WR_VERTEX_SHADER_MAIN_FUNCTION(instance, ph, transform, task, clip_area); +} + +#endif // WR_VERTEX_SHADER + #ifdef WR_FRAGMENT_SHADER // Foward-declare all brush entry-points. diff --git a/webrender/res/brush_multi.glsl b/webrender/res/brush_multi.glsl index 8188c70da6..447186b1fc 100644 --- a/webrender/res/brush_multi.glsl +++ b/webrender/res/brush_multi.glsl @@ -7,7 +7,7 @@ // This type of uber-shader comes at a cost so the goal for this is to // provide opportunities for aggressive batching when the number of draw // calls so high that reducing the number of draw calls is worth the -// cost of this "über-shader". +// cost of this "uber-shader". #define WR_FEATURE_MULTI_BRUSH @@ -26,8 +26,30 @@ int vecs_per_brush(int brush_kind); + +#ifdef WR_FEATURE_TEXT_BRUSH +// Before including the brush source, if we need support for text we override +// the vertex shader's main entry point with one that can call into the text +// shader or the regular brush shaders. + +// Foward-declare the new entry point. +void multi_brush_main_vs( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask pic_task, + ClipArea clip_area +); + +// Override the default entry point. +#define WR_VERTEX_SHADER_MAIN_FUNCTION multi_brush_main_vs + +#endif + + #include shared,prim_shared,brush + #ifdef WR_FEATURE_IMAGE_BRUSH #include brush_image #endif @@ -88,6 +110,30 @@ int vecs_per_brush(int brush_kind); #include brush_opacity #endif +#undef VECS_PER_SPECIFIC_BRUSH +#undef WR_BRUSH_VS_FUNCTION +#undef WR_BRUSH_FS_FUNCTION + +#ifdef WR_FEATURE_TEXT_BRUSH +#include ps_text_run + +// Special entry point when text support is needed. +void multi_brush_main_vs( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask pic_task, + ClipArea clip_area +) { + if (instance.brush_kind == BRUSH_SHADER_KIND_TEXT) { + text_shader_main(instance, ph, transform, task, clip_area); + } else { + brush_shader_main(instance, ph, transform, task, clip_area); + } +} + +#endif + int vecs_per_brush(int brush_kind) { switch (brush_kind) { // The default arm should never be taken, we let it point to whichever shader @@ -240,6 +286,10 @@ Fragment multi_brush_fs(int brush_kind) { #ifdef WR_FEATURE_OPACITY_BRUSH case BRUSH_KIND_OPACITY: return opacity_brush_fs(); #endif + + #ifdef WR_FEATURE_TEXT_BRUSH + case BRUSH_KIND_TEXT: return text_brush_fs(); + #endif } } diff --git a/webrender/res/brush_solid.frag.h b/webrender/res/brush_solid.frag.h new file mode 100644 index 0000000000..9f65063816 --- /dev/null +++ b/webrender/res/brush_solid.frag.h @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +ALWAYS_INLINE int draw_span(uint32_t* buf, int len) { + auto color = pack_span(buf, flat_varying_vec4_0); + commit_solid_span(buf, color, len); + return len; +} + +ALWAYS_INLINE int draw_span(uint8_t* buf, int len) { + auto color = pack_span(buf, flat_varying_vec4_0.x); + commit_solid_span(buf, color, len); + return len; +} diff --git a/webrender/res/clip_shared.glsl b/webrender/res/clip_shared.glsl index a6a7d2dd50..e5b8038968 100644 --- a/webrender/res/clip_shared.glsl +++ b/webrender/res/clip_shared.glsl @@ -6,13 +6,13 @@ #ifdef WR_VERTEX_SHADER -in ivec2 aTransformIds; -in ivec4 aClipDataResourceAddress; -in vec2 aClipLocalPos; -in vec4 aClipTileRect; -in vec4 aClipDeviceArea; -in vec4 aClipOrigins; -in float aDevicePixelScale; +PER_INSTANCE in ivec2 aTransformIds; +PER_INSTANCE in ivec4 aClipDataResourceAddress; +PER_INSTANCE in vec2 aClipLocalPos; +PER_INSTANCE in vec4 aClipTileRect; +PER_INSTANCE in vec4 aClipDeviceArea; +PER_INSTANCE in vec4 aClipOrigins; +PER_INSTANCE in float aDevicePixelScale; struct ClipMaskInstance { int clip_transform_id; diff --git a/webrender/res/composite.glsl b/webrender/res/composite.glsl index a63df80e02..81b4454fab 100644 --- a/webrender/res/composite.glsl +++ b/webrender/res/composite.glsl @@ -23,16 +23,16 @@ varying vec2 vUv; #endif #ifdef WR_VERTEX_SHADER -in vec4 aDeviceRect; -in vec4 aDeviceClipRect; -in vec4 aColor; -in vec4 aParams; -in vec3 aTextureLayers; +PER_INSTANCE in vec4 aDeviceRect; +PER_INSTANCE in vec4 aDeviceClipRect; +PER_INSTANCE in vec4 aColor; +PER_INSTANCE in vec4 aParams; +PER_INSTANCE in vec3 aTextureLayers; #ifdef WR_FEATURE_YUV -in vec4 aUvRect0; -in vec4 aUvRect1; -in vec4 aUvRect2; +PER_INSTANCE in vec4 aUvRect0; +PER_INSTANCE in vec4 aUvRect1; +PER_INSTANCE in vec4 aUvRect2; #endif void main(void) { diff --git a/webrender/res/cs_blur.glsl b/webrender/res/cs_blur.glsl index db3583210b..a10fe00d79 100644 --- a/webrender/res/cs_blur.glsl +++ b/webrender/res/cs_blur.glsl @@ -18,9 +18,9 @@ flat varying int vSupport; #define DIR_HORIZONTAL 0 #define DIR_VERTICAL 1 -in int aBlurRenderTaskAddress; -in int aBlurSourceTaskAddress; -in int aBlurDirection; +PER_INSTANCE in int aBlurRenderTaskAddress; +PER_INSTANCE in int aBlurSourceTaskAddress; +PER_INSTANCE in int aBlurDirection; struct BlurTask { RenderTaskCommonData common_data; diff --git a/webrender/res/cs_border_segment.glsl b/webrender/res/cs_border_segment.glsl index 0b833d278a..9eb5155f1f 100644 --- a/webrender/res/cs_border_segment.glsl +++ b/webrender/res/cs_border_segment.glsl @@ -72,15 +72,15 @@ varying vec2 vPos; #ifdef WR_VERTEX_SHADER -in vec2 aTaskOrigin; -in vec4 aRect; -in vec4 aColor0; -in vec4 aColor1; -in int aFlags; -in vec2 aWidths; -in vec2 aRadii; -in vec4 aClipParams1; -in vec4 aClipParams2; +PER_INSTANCE in vec2 aTaskOrigin; +PER_INSTANCE in vec4 aRect; +PER_INSTANCE in vec4 aColor0; +PER_INSTANCE in vec4 aColor1; +PER_INSTANCE in int aFlags; +PER_INSTANCE in vec2 aWidths; +PER_INSTANCE in vec2 aRadii; +PER_INSTANCE in vec4 aClipParams1; +PER_INSTANCE in vec4 aClipParams2; vec2 get_outer_corner_scale(int segment) { vec2 p; diff --git a/webrender/res/cs_border_solid.glsl b/webrender/res/cs_border_solid.glsl index dde00b83ec..d574814353 100644 --- a/webrender/res/cs_border_solid.glsl +++ b/webrender/res/cs_border_solid.glsl @@ -45,15 +45,15 @@ varying vec2 vPos; #ifdef WR_VERTEX_SHADER -in vec2 aTaskOrigin; -in vec4 aRect; -in vec4 aColor0; -in vec4 aColor1; -in int aFlags; -in vec2 aWidths; -in vec2 aRadii; -in vec4 aClipParams1; -in vec4 aClipParams2; +PER_INSTANCE in vec2 aTaskOrigin; +PER_INSTANCE in vec4 aRect; +PER_INSTANCE in vec4 aColor0; +PER_INSTANCE in vec4 aColor1; +PER_INSTANCE in int aFlags; +PER_INSTANCE in vec2 aWidths; +PER_INSTANCE in vec2 aRadii; +PER_INSTANCE in vec4 aClipParams1; +PER_INSTANCE in vec4 aClipParams2; vec2 get_outer_corner_scale(int segment) { vec2 p; diff --git a/webrender/res/cs_gradient.glsl b/webrender/res/cs_gradient.glsl index 6e848a34dc..6e6b5c4ad0 100644 --- a/webrender/res/cs_gradient.glsl +++ b/webrender/res/cs_gradient.glsl @@ -13,14 +13,14 @@ flat varying vec4 vColor3; #ifdef WR_VERTEX_SHADER -in vec4 aTaskRect; -in float aAxisSelect; -in vec4 aStops; -in vec4 aColor0; -in vec4 aColor1; -in vec4 aColor2; -in vec4 aColor3; -in vec2 aStartStop; +PER_INSTANCE in vec4 aTaskRect; +PER_INSTANCE in float aAxisSelect; +PER_INSTANCE in vec4 aStops; +PER_INSTANCE in vec4 aColor0; +PER_INSTANCE in vec4 aColor1; +PER_INSTANCE in vec4 aColor2; +PER_INSTANCE in vec4 aColor3; +PER_INSTANCE in vec2 aStartStop; void main(void) { vPos = mix(aStartStop.x, aStartStop.y, mix(aPosition.x, aPosition.y, aAxisSelect)); diff --git a/webrender/res/cs_line_decoration.glsl b/webrender/res/cs_line_decoration.glsl index 42336fcc11..7063bcffb1 100644 --- a/webrender/res/cs_line_decoration.glsl +++ b/webrender/res/cs_line_decoration.glsl @@ -21,22 +21,22 @@ flat varying vec4 vParams; #ifdef WR_VERTEX_SHADER // The size of the mask tile we're rendering, in pixels. -in vec4 aTaskRect; +PER_INSTANCE in vec4 aTaskRect; // The size of the mask tile. aLocalSize.x is always horizontal and .y vertical, // regardless of the line's orientation. The size is chosen by // prim_store::get_line_decoration_sizes. -in vec2 aLocalSize; +PER_INSTANCE in vec2 aLocalSize; // A LINE_STYLE_* value, indicating what sort of line to draw. -in int aStyle; +PER_INSTANCE in int aStyle; // 0.0 for a horizontal line, 1.0 for a vertical line. -in float aAxisSelect; +PER_INSTANCE in float aAxisSelect; // The thickness of the wavy line itself, not the amplitude of the waves (i.e., // the thickness of the final decorated line). -in float aWavyLineThickness; +PER_INSTANCE in float aWavyLineThickness; void main(void) { vec2 size = mix(aLocalSize, aLocalSize.yx, aAxisSelect); diff --git a/webrender/res/cs_scale.glsl b/webrender/res/cs_scale.glsl index 183e6774cc..6d0c0e5998 100644 --- a/webrender/res/cs_scale.glsl +++ b/webrender/res/cs_scale.glsl @@ -9,9 +9,9 @@ flat varying vec4 vUvRect; #ifdef WR_VERTEX_SHADER -in vec4 aScaleTargetRect; -in ivec4 aScaleSourceRect; -in int aScaleSourceLayer; +PER_INSTANCE in vec4 aScaleTargetRect; +PER_INSTANCE in ivec4 aScaleSourceRect; +PER_INSTANCE in int aScaleSourceLayer; void main(void) { RectWithSize src_rect = RectWithSize(vec2(aScaleSourceRect.xy), vec2(aScaleSourceRect.zw)); diff --git a/webrender/res/cs_svg_filter.glsl b/webrender/res/cs_svg_filter.glsl index b43ca2ecf9..62d808a1e3 100644 --- a/webrender/res/cs_svg_filter.glsl +++ b/webrender/res/cs_svg_filter.glsl @@ -39,13 +39,13 @@ flat varying int vFuncs[4]; #ifdef WR_VERTEX_SHADER -in int aFilterRenderTaskAddress; -in int aFilterInput1TaskAddress; -in int aFilterInput2TaskAddress; -in int aFilterKind; -in int aFilterInputCount; -in int aFilterGenericInt; -in ivec2 aFilterExtraDataAddress; +PER_INSTANCE in int aFilterRenderTaskAddress; +PER_INSTANCE in int aFilterInput1TaskAddress; +PER_INSTANCE in int aFilterInput2TaskAddress; +PER_INSTANCE in int aFilterKind; +PER_INSTANCE in int aFilterInputCount; +PER_INSTANCE in int aFilterGenericInt; +PER_INSTANCE in ivec2 aFilterExtraDataAddress; struct FilterTask { RenderTaskCommonData common_data; diff --git a/webrender/res/debug_color.glsl b/webrender/res/debug_color.glsl index 15285bdbbe..b5a636e535 100644 --- a/webrender/res/debug_color.glsl +++ b/webrender/res/debug_color.glsl @@ -11,7 +11,7 @@ in vec4 aColor; void main(void) { vColor = vec4(aColor.rgb * aColor.a, aColor.a); - vec4 pos = vec4(aPosition, 1.0); + vec4 pos = vec4(aPosition, 0.0, 1.0); pos.xy = floor(pos.xy + 0.5); gl_Position = uTransform * pos; } diff --git a/webrender/res/debug_font.glsl b/webrender/res/debug_font.glsl index b93c4bcaa5..5b9fb2109f 100644 --- a/webrender/res/debug_font.glsl +++ b/webrender/res/debug_font.glsl @@ -14,7 +14,7 @@ in vec2 aColorTexCoord; void main(void) { vColor = aColor; vColorTexCoord = aColorTexCoord; - vec4 pos = vec4(aPosition, 1.0); + vec4 pos = vec4(aPosition, 0.0, 1.0); pos.xy = floor(pos.xy + 0.5); gl_Position = uTransform * pos; } diff --git a/webrender/res/pf_vector_cover.glsl b/webrender/res/pf_vector_cover.glsl index a4eece1a1f..1b2eeabb7d 100644 --- a/webrender/res/pf_vector_cover.glsl +++ b/webrender/res/pf_vector_cover.glsl @@ -6,10 +6,10 @@ #ifdef WR_VERTEX_SHADER -in ivec4 aTargetRect; -in ivec2 aStencilOrigin; -in int aSubpixel; -in int aPad; +PER_INSTANCE in ivec4 aTargetRect; +PER_INSTANCE in ivec2 aStencilOrigin; +PER_INSTANCE in int aSubpixel; +PER_INSTANCE in int aPad; out vec2 vStencilUV; flat out int vSubpixel; diff --git a/webrender/res/pf_vector_stencil.glsl b/webrender/res/pf_vector_stencil.glsl index e951eeba16..2029768fcb 100644 --- a/webrender/res/pf_vector_stencil.glsl +++ b/webrender/res/pf_vector_stencil.glsl @@ -6,14 +6,14 @@ #ifdef WR_VERTEX_SHADER -in vec2 aFromPosition; -in vec2 aCtrlPosition; -in vec2 aToPosition; -in vec2 aFromNormal; -in vec2 aCtrlNormal; -in vec2 aToNormal; -in int aPathID; -in int aPad; +PER_INSTANCE in vec2 aFromPosition; +PER_INSTANCE in vec2 aCtrlPosition; +PER_INSTANCE in vec2 aToPosition; +PER_INSTANCE in vec2 aFromNormal; +PER_INSTANCE in vec2 aCtrlNormal; +PER_INSTANCE in vec2 aToNormal; +PER_INSTANCE in int aPathID; +PER_INSTANCE in int aPad; out vec2 vFrom; out vec2 vCtrl; diff --git a/webrender/res/pls_init.glsl b/webrender/res/pls_init.glsl index 4d3e391a7e..a9fe0a6c4a 100644 --- a/webrender/res/pls_init.glsl +++ b/webrender/res/pls_init.glsl @@ -11,7 +11,7 @@ #include shared #ifdef WR_VERTEX_SHADER -in vec4 aRect; +PER_INSTANCE in vec4 aRect; void main(void) { vec2 pos = aRect.xy + aPosition.xy * aRect.zw; diff --git a/webrender/res/pls_resolve.glsl b/webrender/res/pls_resolve.glsl index 70ba5f1cca..363ce2ccfe 100644 --- a/webrender/res/pls_resolve.glsl +++ b/webrender/res/pls_resolve.glsl @@ -11,7 +11,7 @@ #include shared #ifdef WR_VERTEX_SHADER -in vec4 aRect; +PER_INSTANCE in vec4 aRect; void main(void) { vec2 pos = aRect.xy + aPosition.xy * aRect.zw; diff --git a/webrender/res/prim_shared.glsl b/webrender/res/prim_shared.glsl index d4a06b2a2d..f89340ff49 100644 --- a/webrender/res/prim_shared.glsl +++ b/webrender/res/prim_shared.glsl @@ -45,7 +45,7 @@ uniform HIGHP_SAMPLER_FLOAT sampler2D sPrimitiveHeadersF; uniform HIGHP_SAMPLER_FLOAT isampler2D sPrimitiveHeadersI; // Instanced attributes -in ivec4 aData; +PER_INSTANCE in ivec4 aData; #define VECS_PER_PRIM_HEADER_F 2U #define VECS_PER_PRIM_HEADER_I 2U diff --git a/webrender/res/ps_text_run.glsl b/webrender/res/ps_text_run.glsl index 59c23cb7b2..c2f626dded 100644 --- a/webrender/res/ps_text_run.glsl +++ b/webrender/res/ps_text_run.glsl @@ -2,19 +2,28 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#define WR_VERTEX_SHADER_MAIN_FUNCTION text_shader_main_vs +#define WR_BRUSH_FS_FUNCTION text_brush_fs +#define WR_BRUSH_VS_FUNCTION text_brush_vs +// The text brush shader doesn't use this but the macro must be defined +// to compile the brush infrastructure. +#define VECS_PER_SPECIFIC_BRUSH 0 + #include shared,prim_shared -// A few varying slots for the brushes to use. -// Using these instead of adding dedicated varyings avoids using a high -// number of varyings in the multi-brush shader. -// -// TODO: This is duplicated from the brush shader code and will be merged -// back once text runs use the brush infrastructure. -flat varying vec4 flat_varying_vec4_0; -flat varying vec4 flat_varying_vec4_1; -flat varying vec4 flat_varying_vec4_2; -varying vec4 varying_vec4_0; -varying vec4 varying_vec4_1; +#ifdef WR_VERTEX_SHADER +// Forward-declare the text vertex shader's main entry-point before including +// the brush shader. +void text_shader_main_vs( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask task, + ClipArea clip_area +); +#endif + +#include brush #define V_COLOR flat_varying_vec4_0 #define V_MASK_SWIZZLE flat_varying_vec4_1.xy @@ -112,18 +121,17 @@ vec2 get_snap_bias(int subpx_dir) { } } -void main(void) { - Instance instance = decode_instance_attributes(); - +void text_shader_main_vs( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask task, + ClipArea clip_area +) { int glyph_index = instance.segment_index; int subpx_dir = (instance.flags >> 8) & 0xff; int color_mode = instance.flags & 0xff; - PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address); - Transform transform = fetch_transform(ph.transform_id); - ClipArea clip_area = fetch_clip_area(instance.clip_address); - PictureTask task = fetch_picture_task(instance.picture_task_address); - // Note that the reference frame relative offset is stored in the prim local // rect size during batching, instead of the actual size of the primitive. TextRun text = fetch_text_run(ph.specific_prim_address); @@ -272,7 +280,27 @@ void main(void) { V_LAYER = res.layer; V_UV_BOUNDS = (res.uv_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy; } -#endif + +void text_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize prim_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 segment_data +) { + // This function is empty and unused for now. It has to be defined to build the shader + // as a brush, but the brush shader currently branches into text_shader_main_vs earlier + // instead of using the regular brush vertex interface for text. + // In the future we should strive to further unify text and brushes, and actually make + // use of this function. +} + +#endif // WR_VERTEX_SHADER #ifdef WR_FRAGMENT_SHADER @@ -296,20 +324,12 @@ Fragment text_brush_fs(void) { return frag; } -void main(void) { - Fragment frag = text_brush_fs(); - - float clip_mask = do_clip(); - frag.color *= clip_mask; - - #if defined(WR_FEATURE_DEBUG_OVERDRAW) - oFragColor = WR_DEBUG_OVERDRAW_COLOR; - #elif defined(WR_FEATURE_DUAL_SOURCE_BLENDING) - oFragColor = frag.color; - oFragBlend = frag.blend * clip_mask; - #else - write_output(frag.color); - #endif -} - #endif // WR_FRAGMENT_SHADER + +// Undef macro names that could be re-defined by other shaders. +#undef V_COLOR +#undef V_MASK_SWIZZLE +#undef V_UV_BOUNDS +#undef V_UV +#undef V_LAYER +#undef V_UV_CLIP diff --git a/webrender/res/shared.glsl b/webrender/res/shared.glsl index 0b375d66f4..7723b70f19 100644 --- a/webrender/res/shared.glsl +++ b/webrender/res/shared.glsl @@ -50,7 +50,7 @@ uniform mat4 uTransform; // Orthographic projection // Attribute inputs - in vec3 aPosition; + in vec2 aPosition; // get_fetch_uv is a macro to work around a macOS Intel driver parsing bug. // TODO: convert back to a function once the driver issues are resolved, if ever. diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index db81681852..cfa766029e 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -19,7 +19,8 @@ use crate::internal_types::{FastHashMap, SavedTargetIndex, Swizzle, TextureSourc use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive}; use crate::prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind, PrimitiveVisibilityIndex, PrimitiveVisibilityMask}; use crate::prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex}; -use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, VECS_PER_SEGMENT, SpaceMapper}; +use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, PrimitiveVisibilityFlags}; +use crate::prim_store::{VECS_PER_SEGMENT, SpaceMapper}; use crate::prim_store::image::ImageSource; use crate::render_target::RenderTargetContext; use crate::render_task_graph::{RenderTaskId, RenderTaskGraph}; @@ -193,6 +194,16 @@ impl AlphaBatchList { } } + /// Clear all current batches in this list. This is typically used + /// when a primitive is encountered that occludes all previous + /// content in this batch list. + fn clear(&mut self) { + self.current_batch_index = usize::MAX; + self.current_z_id = ZBufferId::invalid(); + self.batches.clear(); + self.item_rects.clear(); + } + pub fn set_params_and_get_batch( &mut self, key: BatchKey, @@ -290,6 +301,14 @@ impl OpaqueBatchList { } } + /// Clear all current batches in this list. This is typically used + /// when a primitive is encountered that occludes all previous + /// content in this batch list. + fn clear(&mut self) { + self.current_batch_index = usize::MAX; + self.batches.clear(); + } + pub fn set_params_and_get_batch( &mut self, key: BatchKey, @@ -502,6 +521,14 @@ impl AlphaBatchBuilder { } } + /// Clear all current batches in this builder. This is typically used + /// when a primitive is encountered that occludes all previous + /// content in this batch list. + fn clear(&mut self) { + self.alpha_batch_list.clear(); + self.opaque_batch_list.clear(); + } + pub fn build( mut self, batch_containers: &mut Vec, @@ -653,6 +680,14 @@ impl BatchBuilder { } } + /// Clear all current batchers. This is typically used when a primitive + /// is encountered that occludes all previous content in this batch list. + fn clear_batches(&mut self) { + for batcher in &mut self.batchers { + batcher.clear(); + } + } + /// Add a picture to a given batch builder. pub fn add_pic_to_batch( &mut self, @@ -731,6 +766,16 @@ impl BatchBuilder { let prim_info = &ctx.scratch.prim_info[prim_instance.visibility_info.0 as usize]; let bounding_rect = &prim_info.clip_chain.pic_clip_rect; + // If this primitive is a backdrop, that means that it is known to cover + // the entire picture cache background. In that case, the renderer will + // use the backdrop color as a clear color, and so we can drop this + // primitive and any prior primitives from the batch lists for this + // picture cache slice. + if prim_info.flags.contains(PrimitiveVisibilityFlags::IS_BACKDROP) { + self.clear_batches(); + return; + } + let z_id = z_generator.next(); let prim_common_data = &ctx.data_stores.as_common_data(&prim_instance); diff --git a/webrender/src/composite.rs b/webrender/src/composite.rs index 47880abac6..1e82c592cf 100644 --- a/webrender/src/composite.rs +++ b/webrender/src/composite.rs @@ -94,6 +94,7 @@ pub struct ExternalSurfaceDescriptor { pub local_rect: PictureRect, pub world_rect: WorldRect, pub device_rect: DeviceRect, + pub local_clip_rect: PictureRect, pub clip_rect: DeviceRect, pub image_dependencies: [ImageDependency; 3], pub image_rendering: ImageRendering, @@ -321,6 +322,7 @@ impl CompositeState { compositor_kind: CompositorKind, mut picture_caching_is_enabled: bool, global_device_pixel_scale: DevicePixelScale, + max_depth_ids: i32, ) -> Self { // The native compositor interface requires picture caching to work, so // force it here and warn if it was disabled. @@ -335,7 +337,7 @@ impl CompositeState { opaque_tiles: Vec::new(), alpha_tiles: Vec::new(), clear_tiles: Vec::new(), - z_generator: ZBufferIdGenerator::new(0), + z_generator: ZBufferIdGenerator::new(0, max_depth_ids), dirty_rects_are_valid: true, compositor_kind, picture_caching_is_enabled, @@ -756,6 +758,9 @@ pub trait Compositor { /// Enable/disable native compositor usage fn enable_native_compositor(&mut self, enable: bool); + /// Safely deinitialize any remaining resources owned by the compositor. + fn deinit(&mut self); + /// Get the capabilities struct for this compositor. This is used to /// specify what features a compositor supports, depending on the /// underlying platform diff --git a/webrender/src/debug_render.rs b/webrender/src/debug_render.rs index 42cf43bf05..1be03c5acf 100644 --- a/webrender/src/debug_render.rs +++ b/webrender/src/debug_render.rs @@ -8,7 +8,7 @@ use crate::debug_font_data; use crate::device::{Device, Program, Texture, TextureSlot, VertexDescriptor, ShaderError, VAO}; use crate::device::{TextureFilter, VertexAttribute, VertexAttributeKind, VertexUsageHint}; use euclid::{Point2D, Rect, Size2D, Transform3D, default}; -use crate::internal_types::{ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, Swizzle}; +use crate::internal_types::Swizzle; use std::f32; #[cfg_attr(feature = "capture", derive(Serialize))] @@ -333,8 +333,8 @@ impl DebugRenderer { viewport_size.width as f32 * scale, bottom, top, - ORTHO_NEAR_PLANE, - ORTHO_FAR_PLANE, + device.ortho_near_plane(), + device.ortho_far_plane(), ); // Triangles diff --git a/webrender/src/device/gl.rs b/webrender/src/device/gl.rs index bf30978b13..0638e4c68e 100644 --- a/webrender/src/device/gl.rs +++ b/webrender/src/device/gl.rs @@ -269,8 +269,21 @@ fn build_shader_prefix_string( // GLSL requires that the version number comes first. output(gl_version_string); + let mut features_key = String::new(); + for feat in features.lines() { + const PREFIX: &'static str = "#define WR_FEATURE_"; + if let Some(i) = feat.find(PREFIX) { + if i + PREFIX.len() < feat.len() { + if !features_key.is_empty() { + features_key.push_str("_"); + } + features_key.push_str(&feat[i + PREFIX.len() ..]); + } + } + } + // Insert the shader name to make debugging easier. - let name_string = format!("// {}\n", base_filename); + let name_string = format!("// shader: {} {}\n", base_filename, features_key); output(&name_string); // Define a constant depending on whether we are compiling VS or FS. @@ -1021,6 +1034,11 @@ enum TexStorageUsage { Always, } +// We get 24 bits of Z value - use up 22 bits of it to give us +// 4 bits to account for GPU issues. This seems to manifest on +// some GPUs under certain perspectives due to z interpolation +// precision problems. +const RESERVE_DEPTH_BITS: i32 = 2; pub struct Device { gl: Rc, @@ -1051,6 +1069,7 @@ pub struct Device { color_formats: TextureFormatPair, bgra_formats: TextureFormatPair, swizzle_settings: SwizzleSettings, + depth_format: gl::GLuint, /// Map from texture dimensions to shared depth buffers for render targets. /// @@ -1399,13 +1418,19 @@ impl Device { let is_emulator = renderer_name.starts_with("Android Emulator"); let avoid_tex_image = is_emulator; + let supports_texture_storage = allow_texture_storage_support && + match gl.get_type() { + gl::GlType::Gl => supports_extension(&extensions, "GL_ARB_texture_storage"), + // ES 3 technically always supports glTexStorage, but only check here for the extension + // necessary to interact with BGRA. + gl::GlType::Gles => supports_extension(&extensions, "GL_EXT_texture_storage"), + }; + let supports_texture_swizzle = allow_texture_swizzling && + (gl.get_type() == gl::GlType::Gles || supports_extension(&extensions, "GL_ARB_texture_swizzle")); + let (color_formats, bgra_formats, bgra8_sampling_swizzle, texture_storage_usage) = match gl.get_type() { // There is `glTexStorage`, use it and expect RGBA on the input. - gl::GlType::Gl if - allow_texture_storage_support && - allow_texture_swizzling && - supports_extension(&extensions, "GL_ARB_texture_storage") - => ( + gl::GlType::Gl if supports_texture_storage && supports_texture_swizzle => ( TextureFormatPair::from(ImageFormat::RGBA8), TextureFormatPair { internal: gl::RGBA8, external: gl::RGBA }, Swizzle::Bgra, // pretend it's RGBA, rely on swizzling @@ -1419,7 +1444,7 @@ impl Device { TexStorageUsage::Never ), // We can use glTexStorage with BGRA8 as the internal format. - gl::GlType::Gles if supports_gles_bgra && allow_texture_storage_support && supports_extension(&extensions, "GL_EXT_texture_storage") => ( + gl::GlType::Gles if supports_gles_bgra && supports_texture_storage => ( TextureFormatPair::from(ImageFormat::BGRA8), TextureFormatPair { internal: gl::BGRA8_EXT, external: gl::BGRA_EXT }, Swizzle::Rgba, // no conversion needed @@ -1427,7 +1452,7 @@ impl Device { ), // For BGRA8 textures we must use the unsized BGRA internal // format and glTexImage. If texture storage is supported we can - // use it for other formats. + // use it for other formats, which is always the case for ES 3. // We can't use glTexStorage with BGRA8 as the internal format. gl::GlType::Gles if supports_gles_bgra && !avoid_tex_image => ( TextureFormatPair::from(ImageFormat::RGBA8), @@ -1437,7 +1462,7 @@ impl Device { ), // BGRA is not supported as an internal format, therefore we will // use RGBA. The swizzling will happen at the texture unit. - gl::GlType::Gles if allow_texture_swizzling => ( + gl::GlType::Gles if supports_texture_swizzle => ( TextureFormatPair::from(ImageFormat::RGBA8), TextureFormatPair { internal: gl::RGBA8, external: gl::RGBA }, Swizzle::Bgra, // pretend it's RGBA, rely on swizzling @@ -1452,8 +1477,14 @@ impl Device { ), }; - info!("GL texture cache {:?}, bgra {:?} swizzle {:?}, texture storage {:?}", - color_formats, bgra_formats, bgra8_sampling_swizzle, texture_storage_usage); + let (depth_format, upload_method) = if renderer_name.starts_with("Software WebRender") { + (gl::DEPTH_COMPONENT16, UploadMethod::Immediate) + } else { + (gl::DEPTH_COMPONENT24, upload_method) + }; + + info!("GL texture cache {:?}, bgra {:?} swizzle {:?}, texture storage {:?}, depth {:?}", + color_formats, bgra_formats, bgra8_sampling_swizzle, texture_storage_usage, depth_format); let supports_copy_image_sub_data = supports_extension(&extensions, "GL_EXT_copy_image") || supports_extension(&extensions, "GL_ARB_copy_image"); @@ -1481,10 +1512,6 @@ impl Device { supports_extension(&extensions, "GL_KHR_blend_equation_advanced") && !is_adreno; - let supports_texture_swizzle = allow_texture_swizzling && - (gl.get_type() == gl::GlType::Gles || supports_extension(&extensions, "GL_ARB_texture_storage")); - - // On the android emulator, glShaderSource can crash if the source // strings are not null-terminated. See bug 1591945. let requires_null_terminated_shader_source = is_emulator; @@ -1529,6 +1556,7 @@ impl Device { swizzle_settings: SwizzleSettings { bgra8_sampling_swizzle, }, + depth_format, depth_targets: FastHashMap::default(), @@ -1609,6 +1637,28 @@ impl Device { } } + pub fn depth_bits(&self) -> i32 { + match self.depth_format { + gl::DEPTH_COMPONENT16 => 16, + gl::DEPTH_COMPONENT24 => 24, + _ => panic!("Unknown depth format {:?}", self.depth_format), + } + } + + // See gpu_types.rs where we declare the number of possible documents and + // number of items per document. This should match up with that. + pub fn max_depth_ids(&self) -> i32 { + return 1 << (self.depth_bits() - RESERVE_DEPTH_BITS); + } + + pub fn ortho_near_plane(&self) -> f32 { + return -self.max_depth_ids() as f32; + } + + pub fn ortho_far_plane(&self) -> f32 { + return (self.max_depth_ids() - 1) as f32; + } + pub fn optimal_pbo_stride(&self) -> NonZeroUsize { self.optimal_pbo_stride } @@ -2405,13 +2455,14 @@ impl Device { fn acquire_depth_target(&mut self, dimensions: DeviceIntSize) -> RBOId { let gl = &self.gl; + let depth_format = self.depth_format; let target = self.depth_targets.entry(dimensions).or_insert_with(|| { let renderbuffer_ids = gl.gen_renderbuffers(1); let depth_rb = renderbuffer_ids[0]; gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb); gl.renderbuffer_storage( gl::RENDERBUFFER, - gl::DEPTH_COMPONENT24, + depth_format, dimensions.width as _, dimensions.height as _, ); diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index deb2c9a5c3..de399e7682 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -15,7 +15,7 @@ use crate::gpu_types::TransformData; use crate::internal_types::{FastHashMap, PlaneSplitter, SavedTargetIndex}; use crate::picture::{PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex, RecordedDirtyRegion}; use crate::picture::{RetainedTiles, TileCacheInstance, DirtyRegion, SurfaceRenderTasks, SubpixelMode}; -use crate::picture::{TileCacheLogger}; +use crate::picture::{BackdropKind, TileCacheLogger}; use crate::prim_store::{SpaceMapper, PictureIndex, PrimitiveDebugId, PrimitiveScratchBuffer}; use crate::prim_store::{DeferredResolve, PrimitiveVisibilityMask}; use crate::profiler::{FrameProfileCounters, TextureCacheProfileCounters, ResourceProfileCounters}; @@ -66,6 +66,7 @@ pub struct FrameBuilderConfig { pub background_color: Option, pub compositor_kind: CompositorKind, pub tile_size_override: Option, + pub max_depth_ids: i32, } /// A set of common / global resources that are retained between @@ -569,6 +570,7 @@ impl FrameBuilder { scene.config.compositor_kind, picture_caching_is_enabled, global_device_pixel_scale, + scene.config.max_depth_ids, ); let main_render_task_id = self.build_layer_screen_rects_and_cull_layers( @@ -605,7 +607,7 @@ impl FrameBuilder { ); // Used to generated a unique z-buffer value per primitive. - let mut z_generator = ZBufferIdGenerator::new(layer); + let mut z_generator = ZBufferIdGenerator::new(layer, scene.config.max_depth_ids); let use_dual_source_blending = scene.config.dual_source_blending_is_enabled && scene.config.dual_source_blending_is_supported; @@ -881,14 +883,19 @@ pub fn build_render_pass( Some(color) => color.a >= 1.0, None => false, }; - // TODO(gw): Once we have multiple slices enabled, take advantage of - // option to skip clears if the slice is opaque. - let clear_color = if forced_opaque { + let mut clear_color = if forced_opaque { Some(ColorF::WHITE) } else { Some(ColorF::TRANSPARENT) }; + // If this picture cache has a valid color backdrop, we will use + // that as the clear color, skipping the draw of the backdrop + // primitive (and anything prior to it) during batching. + if let Some(BackdropKind::Color { color }) = tile_cache.backdrop.kind { + clear_color = Some(color); + } + // Create an alpha batcher for each of the tasks of this picture. let mut batchers = Vec::new(); for task_id in &task_ids { diff --git a/webrender/src/glyph_rasterizer/mod.rs b/webrender/src/glyph_rasterizer/mod.rs index e180ea9290..4b8b595fe3 100644 --- a/webrender/src/glyph_rasterizer/mod.rs +++ b/webrender/src/glyph_rasterizer/mod.rs @@ -1079,15 +1079,11 @@ mod test_glyph_rasterizer { IdNamespace, ColorU}; use api::units::{Au, DevicePoint}; use crate::render_backend::FrameId; - use thread_profiler::register_thread_with_profiler; use std::sync::Arc; use crate::glyph_rasterizer::{FORMAT, FontInstance, BaseFontInstance, GlyphKey, GlyphRasterizer}; let worker = ThreadPoolBuilder::new() .thread_name(|idx|{ format!("WRWorker#{}", idx) }) - .start_handler(move |idx| { - register_thread_with_profiler(format!("WRWorker#{}", idx)); - }) .build(); let workers = Arc::new(worker.unwrap()); let mut glyph_rasterizer = GlyphRasterizer::new(workers).unwrap(); diff --git a/webrender/src/gpu_types.rs b/webrender/src/gpu_types.rs index 57b9042a9a..067ba52551 100644 --- a/webrender/src/gpu_types.rs +++ b/webrender/src/gpu_types.rs @@ -25,12 +25,7 @@ pub const VECS_PER_TRANSFORM: usize = 8; #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct ZBufferId(pub i32); -// We get 24 bits of Z value - use up 22 bits of it to give us -// 4 bits to account for GPU issues. This seems to manifest on -// some GPUs under certain perspectives due to z interpolation -// precision problems. const MAX_DOCUMENT_LAYERS : i8 = 1 << 3; -const MAX_ITEMS_PER_DOCUMENT_LAYER : i32 = 1 << 19; const MAX_DOCUMENT_LAYER_VALUE : i8 = MAX_DOCUMENT_LAYERS / 2 - 1; const MIN_DOCUMENT_LAYER_VALUE : i8 = -MAX_DOCUMENT_LAYERS / 2; @@ -46,20 +41,23 @@ impl ZBufferId { pub struct ZBufferIdGenerator { base: i32, next: i32, + max_items_per_document_layer: i32, } impl ZBufferIdGenerator { - pub fn new(layer: DocumentLayer) -> Self { + pub fn new(layer: DocumentLayer, max_depth_ids: i32) -> Self { debug_assert!(layer >= MIN_DOCUMENT_LAYER_VALUE); debug_assert!(layer <= MAX_DOCUMENT_LAYER_VALUE); + let max_items_per_document_layer = max_depth_ids / MAX_DOCUMENT_LAYERS as i32; ZBufferIdGenerator { - base: layer as i32 * MAX_ITEMS_PER_DOCUMENT_LAYER, - next: 0 + base: layer as i32 * max_items_per_document_layer, + next: 0, + max_items_per_document_layer, } } pub fn next(&mut self) -> ZBufferId { - debug_assert!(self.next < MAX_ITEMS_PER_DOCUMENT_LAYER); + debug_assert!(self.next < self.max_items_per_document_layer); let id = ZBufferId(self.next + self.base); self.next += 1; id diff --git a/webrender/src/internal_types.rs b/webrender/src/internal_types.rs index 1e8412bd69..a7f9996a07 100644 --- a/webrender/src/internal_types.rs +++ b/webrender/src/internal_types.rs @@ -299,11 +299,6 @@ pub enum TextureSource { Dummy, } -// See gpu_types.rs where we declare the number of possible documents and -// number of items per document. This should match up with that. -pub const ORTHO_NEAR_PLANE: f32 = -(1 << 22) as f32; -pub const ORTHO_FAR_PLANE: f32 = ((1 << 22) - 1) as f32; - #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] diff --git a/webrender/src/lib.rs b/webrender/src/lib.rs index 0aeb12b3dd..dc11147586 100644 --- a/webrender/src/lib.rs +++ b/webrender/src/lib.rs @@ -70,7 +70,7 @@ extern crate malloc_size_of_derive; #[macro_use] extern crate serde; #[macro_use] -extern crate thread_profiler; +extern crate tracy_rs; extern crate malloc_size_of; extern crate svg_fmt; @@ -218,6 +218,7 @@ pub use crate::renderer::{ MAX_VERTEX_TEXTURE_WIDTH, }; pub use crate::hit_test::SharedHitTester; +pub use crate::internal_types::FastHashMap; pub use crate::screen_capture::{AsyncScreenshotHandle, RecordedFrameHandle}; pub use crate::shade::{Shaders, WrShaders}; pub use api as webrender_api; diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index f4faf055fe..c13dd59910 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -120,7 +120,7 @@ use crate::prim_store::{SpaceMapper, PrimitiveVisibilityMask, PointKey, Primitiv use crate::prim_store::{SpaceSnapper, PictureIndex, PrimitiveInstance, PrimitiveInstanceKind}; use crate::prim_store::{get_raster_rects, PrimitiveScratchBuffer, RectangleKey}; use crate::prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex}; -use crate::prim_store::{ColorBindingStorage, ColorBindingIndex}; +use crate::prim_store::{ColorBindingStorage, ColorBindingIndex, PrimitiveVisibilityFlags}; use crate::print_tree::{PrintTree, PrintTreePrinter}; use crate::render_backend::DataStores; use crate::render_task_graph::RenderTaskId; @@ -1252,7 +1252,7 @@ impl Tile { let clipped_rect = self.current_descriptor.local_valid_rect .intersection(&ctx.local_clip_rect) .unwrap_or_else(PictureRect::zero); - let mut is_opaque = ctx.backdrop.rect.contains_rect(&clipped_rect); + let mut is_opaque = ctx.backdrop.opaque_rect.contains_rect(&clipped_rect); if self.has_compositor_surface { // If we found primitive(s) that are ordered _after_ the first compositor @@ -1338,7 +1338,7 @@ impl Tile { // color tiles. We can definitely support this in DC, so this // should be added as a follow up. let is_simple_prim = - ctx.backdrop.kind.can_be_promoted_to_compositor_surface() && + ctx.backdrop.kind.is_some() && self.current_descriptor.prims.len() == 1 && self.is_opaque && supports_simple_prims; @@ -1349,15 +1349,15 @@ impl Tile { // surface unconditionally (this will drop any previously used // texture cache backing surface). match ctx.backdrop.kind { - BackdropKind::Color { color } => { + Some(BackdropKind::Color { color }) => { TileSurface::Color { color, } } - BackdropKind::Clear => { + Some(BackdropKind::Clear) => { TileSurface::Clear } - BackdropKind::Image => { + None => { // This should be prevented by the is_simple_prim check above. unreachable!(); } @@ -1807,42 +1807,29 @@ impl ::std::fmt::Debug for RecordedDirtyRegion { } #[derive(Debug, Copy, Clone)] -enum BackdropKind { +pub enum BackdropKind { Color { color: ColorF, }, Clear, - Image, -} - -impl BackdropKind { - /// Returns true if the compositor can directly draw this backdrop. - fn can_be_promoted_to_compositor_surface(&self) -> bool { - match self { - BackdropKind::Color { .. } | BackdropKind::Clear => true, - BackdropKind::Image => false, - } - } } /// Stores information about the calculated opaque backdrop of this slice. #[derive(Debug, Copy, Clone)] -struct BackdropInfo { +pub struct BackdropInfo { /// The picture space rectangle that is known to be opaque. This is used /// to determine where subpixel AA can be used, and where alpha blending /// can be disabled. - rect: PictureRect, + pub opaque_rect: PictureRect, /// Kind of the backdrop - kind: BackdropKind, + pub kind: Option, } impl BackdropInfo { fn empty() -> Self { BackdropInfo { - rect: PictureRect::zero(), - kind: BackdropKind::Color { - color: ColorF::BLACK, - }, + opaque_rect: PictureRect::zero(), + kind: None, } } } @@ -2215,7 +2202,7 @@ pub struct TileCacheInstance { /// fine to clear the tiles to this and allow subpixel text on the first slice. pub background_color: Option, /// Information about the calculated backdrop content of this cache. - backdrop: BackdropInfo, + pub backdrop: BackdropInfo, /// The allowed subpixel mode for this surface, which depends on the detected /// opacity of the background. pub subpixel_mode: SubpixelMode, @@ -2830,7 +2817,7 @@ impl TileCacheInstance { image_instances: &ImageInstanceStorage, surface_stack: &[SurfaceIndex], composite_state: &mut CompositeState, - ) -> bool { + ) -> Option { // This primitive exists on the last element on the current surface stack. let prim_surface_index = *surface_stack.last().unwrap(); @@ -2838,7 +2825,7 @@ impl TileCacheInstance { // is no need to add it to any primitive dependencies. let prim_clip_chain = match prim_clip_chain { Some(prim_clip_chain) => prim_clip_chain, - None => return false, + None => return None, }; self.map_local_to_surface.set_target_spatial_node( @@ -2849,12 +2836,12 @@ impl TileCacheInstance { // Map the primitive local rect into picture space. let prim_rect = match self.map_local_to_surface.map(&local_prim_rect) { Some(rect) => rect, - None => return false, + None => return None, }; // If the rect is invalid, no need to create dependencies. if prim_rect.size.is_empty_or_negative() { - return false; + return None; } // If the primitive is directly drawn onto this picture cache surface, then @@ -2894,7 +2881,7 @@ impl TileCacheInstance { rect.inflate(surface.inflation_factor, surface.inflation_factor) } None => { - return false; + return None; } }; @@ -2910,7 +2897,7 @@ impl TileCacheInstance { // If the primitive is outside the tiling rects, it's known to not // be visible. if p0.x == p1.x || p0.y == p1.y { - return false; + return None; } // Build the list of resources that this primitive has dependencies on. @@ -2975,7 +2962,10 @@ impl TileCacheInstance { _ => unreachable!(), }; if color.a >= 1.0 { - backdrop_candidate = Some(BackdropKind::Color { color }); + backdrop_candidate = Some(BackdropInfo { + opaque_rect: pic_clip_rect, + kind: Some(BackdropKind::Color { color }), + }); } } else { let opacity_binding = &opacity_binding_store[opacity_binding_index]; @@ -2997,9 +2987,17 @@ impl TileCacheInstance { if opacity_binding_index == OpacityBindingIndex::INVALID { if let Some(image_properties) = resource_cache.get_image_properties(image_data.key) { - // If this image is opaque, it can be considered as a possible opaque backdrop - if image_properties.descriptor.is_opaque() { - backdrop_candidate = Some(BackdropKind::Image); + // For an image to be a possible opaque backdrop, it must: + // - Have a valid, opaque image descriptor + // - Not use tiling (since they can fail to draw) + // - Not having any spacing / padding + if image_properties.descriptor.is_opaque() && + image_properties.tiling.is_none() && + image_data.tile_spacing == LayoutSize::zero() { + backdrop_candidate = Some(BackdropInfo { + opaque_rect: pic_clip_rect, + kind: None, + }); } } } else { @@ -3175,6 +3173,7 @@ impl TileCacheInstance { self.external_surfaces.push(ExternalSurfaceDescriptor { local_rect: prim_info.prim_clip_rect, world_rect, + local_clip_rect: prim_info.prim_clip_rect, image_dependencies, image_rendering: prim_data.kind.image_rendering, device_rect, @@ -3208,7 +3207,7 @@ impl TileCacheInstance { PrimitiveInstanceKind::PushClipChain | PrimitiveInstanceKind::PopClipChain => { // Early exit to ensure this doesn't get added as a dependency on the tile. - return false; + return None; } PrimitiveInstanceKind::TextRun { data_handle, .. } => { // Only do these checks if we haven't already disabled subpx @@ -3228,13 +3227,16 @@ impl TileCacheInstance { // correctly determined as we recurse through pictures in take_context. if on_picture_surface && subpx_requested - && !self.backdrop.rect.contains_rect(&pic_clip_rect) { + && !self.backdrop.opaque_rect.contains_rect(&pic_clip_rect) { self.subpixel_mode = SubpixelMode::Deny; } } } PrimitiveInstanceKind::Clear { .. } => { - backdrop_candidate = Some(BackdropKind::Clear); + backdrop_candidate = Some(BackdropInfo { + opaque_rect: pic_clip_rect, + kind: Some(BackdropKind::Clear), + }); } PrimitiveInstanceKind::LineDecoration { .. } | PrimitiveInstanceKind::NormalBorder { .. } | @@ -3248,16 +3250,18 @@ impl TileCacheInstance { // If this primitive considers itself a backdrop candidate, apply further // checks to see if it matches all conditions to be a backdrop. + let mut vis_flags = PrimitiveVisibilityFlags::empty(); + if let Some(backdrop_candidate) = backdrop_candidate { - let is_suitable_backdrop = match backdrop_candidate { - BackdropKind::Clear => { + let is_suitable_backdrop = match backdrop_candidate.kind { + Some(BackdropKind::Clear) => { // Clear prims are special - they always end up in their own slice, // and always set the backdrop. In future, we hope to completely // remove clear prims, since they don't integrate with the compositing // system cleanly. true } - BackdropKind::Image | BackdropKind::Color { .. } => { + Some(BackdropKind::Color { .. }) | None => { // Check a number of conditions to see if we can consider this // primitive as an opaque backdrop rect. Several of these are conservative // checks and could be relaxed in future. However, these checks @@ -3280,12 +3284,25 @@ impl TileCacheInstance { }; if is_suitable_backdrop - && !prim_clip_chain.needs_mask - && pic_clip_rect.contains_rect(&self.backdrop.rect) { - self.backdrop = BackdropInfo { - rect: pic_clip_rect, - kind: backdrop_candidate, - }; + && self.external_surfaces.is_empty() + && !prim_clip_chain.needs_mask { + + if backdrop_candidate.opaque_rect.contains_rect(&self.backdrop.opaque_rect) { + self.backdrop.opaque_rect = backdrop_candidate.opaque_rect; + } + + if let Some(kind) = backdrop_candidate.kind { + if backdrop_candidate.opaque_rect.contains_rect(&self.local_rect) { + // If we have a color backdrop, mark the visibility flags + // of the primitive so it is skipped during batching (and + // also clears any previous primitives). + if let BackdropKind::Color { .. } = kind { + vis_flags |= PrimitiveVisibilityFlags::IS_BACKDROP; + } + + self.backdrop.kind = Some(kind); + } + } } } @@ -3311,7 +3328,7 @@ impl TileCacheInstance { } } - true + Some(vis_flags) } /// Print debug information about this picture cache to a tree printer. @@ -3370,23 +3387,23 @@ impl TileCacheInstance { }; } + let map_pic_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + self.spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + // Register the opaque region of this tile cache as an occluder, which // is used later in the frame to occlude other tiles. - if self.backdrop.rect.is_well_formed_and_nonempty() { - let backdrop_rect = self.backdrop.rect + if self.backdrop.opaque_rect.is_well_formed_and_nonempty() { + let backdrop_rect = self.backdrop.opaque_rect .intersection(&self.local_rect) .and_then(|r| { r.intersection(&self.local_clip_rect) }); if let Some(backdrop_rect) = backdrop_rect { - let map_pic_to_world = SpaceMapper::new_with_target( - ROOT_SPATIAL_NODE_INDEX, - self.spatial_node_index, - frame_context.global_screen_world_rect, - frame_context.spatial_tree, - ); - let world_backdrop_rect = map_pic_to_world .map(&backdrop_rect) .expect("bug: unable to map backdrop to world space"); @@ -3405,10 +3422,22 @@ impl TileCacheInstance { // able to occlude every background tile (avoiding allocation, rasterizion // and compositing). for external_surface in &self.external_surfaces { - frame_state.composite_state.register_occluder( - external_surface.z_id, - external_surface.world_rect, - ); + let local_surface_rect = external_surface.local_rect + .intersection(&external_surface.local_clip_rect) + .and_then(|r| { + r.intersection(&self.local_clip_rect) + }); + + if let Some(local_surface_rect) = local_surface_rect { + let world_surface_rect = map_pic_to_world + .map(&local_surface_rect) + .expect("bug: unable to map external surface to world space"); + + frame_state.composite_state.register_occluder( + external_surface.z_id, + world_surface_rect, + ); + } } // A simple GC of the native external surface cache, to remove and free any diff --git a/webrender/src/prim_store/mod.rs b/webrender/src/prim_store/mod.rs index 72e47fe458..ad1e24e61a 100644 --- a/webrender/src/prim_store/mod.rs +++ b/webrender/src/prim_store/mod.rs @@ -1519,6 +1519,20 @@ impl PrimitiveVisibilityMask { pub const MAX_DIRTY_REGIONS: usize = 8 * mem::size_of::(); } +bitflags! { + /// A set of bitflags that can be set in the visibility information + /// for a primitive instance. This can be used to control how primitives + /// are treated during batching. + // TODO(gw): We should also move `is_compositor_surface` to be part of + // this flags struct. + #[cfg_attr(feature = "capture", derive(Serialize))] + pub struct PrimitiveVisibilityFlags: u16 { + /// Implies that this primitive covers the entire picture cache slice, + /// and can thus be dropped during batching and drawn with clear color. + const IS_BACKDROP = 1; + } +} + /// Information stored for a visible primitive about the visible /// rect and associated clip information. #[cfg_attr(feature = "capture", derive(Serialize))] @@ -1539,6 +1553,10 @@ pub struct PrimitiveVisibility { /// a list of clip task ids (one per segment). pub clip_task_index: ClipTaskIndex, + /// A set of flags that define how this primitive should be handled + /// during batching of visibile primitives. + pub flags: PrimitiveVisibilityFlags, + /// A mask defining which of the dirty regions this primitive is visible in. pub visibility_mask: PrimitiveVisibilityMask, @@ -2095,6 +2113,7 @@ impl PrimitiveStore { clip_task_index: ClipTaskIndex::INVALID, combined_local_clip_rect: LayoutRect::zero(), visibility_mask: PrimitiveVisibilityMask::empty(), + flags: PrimitiveVisibilityFlags::empty(), } ); @@ -2156,11 +2175,16 @@ impl PrimitiveStore { prim_instance.is_chased(), ); + // Primitive visibility flags default to empty, but may be supplied + // by the `update_prim_dependencies` method below when picture caching + // is active. + let mut vis_flags = PrimitiveVisibilityFlags::empty(); + if let Some(ref mut tile_cache) = frame_state.tile_cache { // TODO(gw): Refactor how tile_cache is stored in frame_state // so that we can pass frame_state directly to // update_prim_dependencies, rather than splitting borrows. - if !tile_cache.update_prim_dependencies( + match tile_cache.update_prim_dependencies( prim_instance, cluster.spatial_node_index, clip_chain.as_ref(), @@ -2176,11 +2200,16 @@ impl PrimitiveStore { &frame_state.surface_stack, &mut frame_state.composite_state, ) { - prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; - // Ensure the primitive clip is popped - perhaps we can use - // some kind of scope to do this automatically in future. - frame_state.clip_chain_stack.pop_clip(); - continue; + Some(flags) => { + vis_flags = flags; + } + None => { + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + // Ensure the primitive clip is popped - perhaps we can use + // some kind of scope to do this automatically in future. + frame_state.clip_chain_stack.pop_clip(); + continue; + } } } @@ -2308,6 +2337,7 @@ impl PrimitiveStore { clip_task_index: ClipTaskIndex::INVALID, combined_local_clip_rect, visibility_mask: PrimitiveVisibilityMask::empty(), + flags: vis_flags, } ); diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index c3887efc4a..358b4a1221 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -1512,6 +1512,8 @@ impl RenderBackend { ) { let requested_frame = render_frame; + let requires_frame_build = self.requires_frame_build(); + let doc = self.documents.get_mut(&document_id).unwrap(); // If we have a sampler, get more frame ops from it and add them // to the transaction. This is a hook to allow the WR user code to // fiddle with things after a potentially long scene build, but just @@ -1519,12 +1521,11 @@ impl RenderBackend { // async transforms. if requested_frame || has_built_scene { if let Some(ref sampler) = self.sampler { - frame_ops.append(&mut sampler.sample(document_id)); + frame_ops.append(&mut sampler.sample(document_id, + &doc.scene.pipeline_epochs)); } } - let requires_frame_build = self.requires_frame_build(); - let doc = self.documents.get_mut(&document_id).unwrap(); doc.has_built_scene |= has_built_scene; // TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index c1860cc142..dc017c0d1c 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -69,7 +69,7 @@ use crate::gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList}; use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd}; use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, SvgFilterInstance, TransformData}; use crate::gpu_types::{CompositeInstance, ResolveInstanceData, ZBufferId}; -use crate::internal_types::{TextureSource, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError}; +use crate::internal_types::{TextureSource, ResourceCacheError}; use crate::internal_types::{CacheTextureId, DebugOutput, FastHashMap, FastHashSet, LayerIndex, RenderedDocument, ResultMsg}; use crate::internal_types::{TextureCacheAllocationKind, TextureCacheUpdate, TextureUpdateList, TextureUpdateSource}; use crate::internal_types::{RenderTargetInfo, SavedTargetIndex, Swizzle}; @@ -112,7 +112,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, Receiver}; use std::thread; use std::cell::RefCell; -use thread_profiler::{register_thread_with_profiler, write_profile}; +use tracy_rs::register_thread_with_profiler; use time::precise_time_ns; cfg_if! { @@ -1308,17 +1308,38 @@ struct CacheRow { /// Mirrored block data on CPU for this row. We store a copy of /// the data on the CPU side to improve upload batching. cpu_blocks: Box<[GpuBlockData; MAX_VERTEX_TEXTURE_WIDTH]>, - /// True if this row is dirty. - is_dirty: bool, + /// The first offset in this row that is dirty. + min_dirty: u16, + /// The last offset in this row that is dirty. + max_dirty: u16, } impl CacheRow { fn new() -> Self { CacheRow { cpu_blocks: Box::new([GpuBlockData::EMPTY; MAX_VERTEX_TEXTURE_WIDTH]), - is_dirty: false, + min_dirty: MAX_VERTEX_TEXTURE_WIDTH as _, + max_dirty: 0, } } + + fn is_dirty(&self) -> bool { + return self.min_dirty < self.max_dirty; + } + + fn clear_dirty(&mut self) { + self.min_dirty = MAX_VERTEX_TEXTURE_WIDTH as _; + self.max_dirty = 0; + } + + fn add_dirty(&mut self, block_offset: usize, block_count: usize) { + self.min_dirty = self.min_dirty.min(block_offset as _); + self.max_dirty = self.max_dirty.max((block_offset + block_count) as _); + } + + fn dirty_blocks(&self) -> &[GpuBlockData] { + return &self.cpu_blocks[self.min_dirty as usize .. self.max_dirty as usize]; + } } /// The bus over which CPU and GPU versions of the GPU cache @@ -1506,15 +1527,15 @@ impl GpuCacheTexture { rows.push(CacheRow::new()); } - // This row is dirty (needs to be updated in GPU texture). - rows[row].is_dirty = true; - // Copy the blocks from the patch array in the shadow CPU copy. let block_offset = address.u as usize; let data = &mut rows[row].cpu_blocks; for i in 0 .. block_count { data[block_offset + i] = updates.blocks[block_index + i]; } + + // This row is dirty (needs to be updated in GPU texture). + rows[row].add_dirty(block_offset, block_count); } } } @@ -1561,7 +1582,7 @@ impl GpuCacheTexture { GpuCacheBus::PixelBuffer { ref buffer, ref mut rows } => { let rows_dirty = rows .iter() - .filter(|row| row.is_dirty) + .filter(|row| row.is_dirty()) .count(); if rows_dirty == 0 { return 0 @@ -1579,18 +1600,19 @@ impl GpuCacheTexture { ); for (row_index, row) in rows.iter_mut().enumerate() { - if !row.is_dirty { + if !row.is_dirty() { continue; } + let blocks = row.dirty_blocks(); let rect = DeviceIntRect::new( - DeviceIntPoint::new(0, row_index as i32), - DeviceIntSize::new(MAX_VERTEX_TEXTURE_WIDTH as i32, 1), + DeviceIntPoint::new(row.min_dirty as i32, row_index as i32), + DeviceIntSize::new(blocks.len() as i32, 1), ); - uploader.upload(rect, 0, None, None, row.cpu_blocks.as_ptr(), row.cpu_blocks.len()); + uploader.upload(rect, 0, None, None, blocks.as_ptr(), blocks.len()); - row.is_dirty = false; + row.clear_dirty(); } rows_dirty @@ -1707,12 +1729,15 @@ impl VertexDataTexture { // (like Intel iGPUs) that prefers power-of-two sizes of textures ([1]). // // [1] https://software.intel.com/en-us/articles/opengl-performance-tips-power-of-two-textures-have-better-performance - let logical_width = - (MAX_VERTEX_TEXTURE_WIDTH - (MAX_VERTEX_TEXTURE_WIDTH % texels_per_item)) as i32; + let logical_width = if needed_height == 1 { + data.len() * texels_per_item + } else { + MAX_VERTEX_TEXTURE_WIDTH - (MAX_VERTEX_TEXTURE_WIDTH % texels_per_item) + }; let rect = DeviceIntRect::new( DeviceIntPoint::zero(), - DeviceIntSize::new(logical_width, needed_height), + DeviceIntSize::new(logical_width as i32, needed_height), ); debug_assert!(len <= data.capacity(), "CPU copy will read out of bounds"); @@ -1720,9 +1745,11 @@ impl VertexDataTexture { rect.size, self.texture().get_format(), ); - device - .upload_texture(self.texture(), &self.pbo, upload_size) - .upload(rect, 0, None, None, data.as_ptr(), len); + if upload_size > 0 { + device + .upload_texture(self.texture(), &self.pbo, upload_size) + .upload(rect, 0, None, None, data.as_ptr(), len); + } } fn deinit(mut self, device: &mut Device) { @@ -2006,6 +2033,16 @@ impl Renderer { start_size: DeviceIntSize, ) -> Result<(Self, RenderApiSender), RendererError> { if !wr_has_been_initialized() { + // If the profiler feature is enabled, try to load the profiler shared library + // if the path was provided. + #[cfg(feature = "profiler")] + unsafe { + if let Ok(ref tracy_path) = std::env::var("WR_TRACY_PATH") { + let ok = tracy_rs::load(tracy_path); + println!("Load tracy from {} -> {}", tracy_path, ok); + } + } + register_thread_with_profiler("Compositor".to_owned()); } @@ -2241,6 +2278,7 @@ impl Renderer { background_color: options.clear_color, compositor_kind, tile_size_override: None, + max_depth_ids: device.max_depth_ids(), }; info!("WR {:?}", config); @@ -2960,7 +2998,7 @@ impl Renderer { GpuCacheBus::PixelBuffer { ref mut rows, .. } => { info!("Invalidating GPU caches"); for row in rows { - row.is_dirty = true; + row.add_dirty(0, MAX_VERTEX_TEXTURE_WIDTH); } } GpuCacheBus::Scatter { .. } => { @@ -3023,6 +3061,8 @@ impl Renderer { // event. Otherwise they would just pile up in this vector forever. self.notifications.clear(); + tracy_frame_marker!(); + result } @@ -4314,8 +4354,8 @@ impl Renderer { surface_size.width as f32, 0.0, surface_size.height as f32, - ORTHO_NEAR_PLANE, - ORTHO_FAR_PLANE, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), ); // Bind an appropriate YUV shader for the texture format kind @@ -5097,8 +5137,8 @@ impl Renderer { target_size.width as f32, 0.0, target_size.height as f32, - ORTHO_NEAR_PLANE, - ORTHO_FAR_PLANE, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), ) }; @@ -5559,8 +5599,8 @@ impl Renderer { offset.x + size.width, bottom, top, - ORTHO_NEAR_PLANE, - ORTHO_FAR_PLANE, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), ); let fb_scale = Scale::<_, _, FramebufferPixel>::new(1i32); @@ -5697,8 +5737,8 @@ impl Renderer { draw_target.dimensions().width as f32, 0.0, draw_target.dimensions().height as f32, - ORTHO_NEAR_PLANE, - ORTHO_FAR_PLANE, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), ); self.draw_picture_cache_target( @@ -5738,8 +5778,8 @@ impl Renderer { draw_target.dimensions().width as f32, 0.0, draw_target.dimensions().height as f32, - ORTHO_NEAR_PLANE, - ORTHO_FAR_PLANE, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), ); self.draw_alpha_target( @@ -5764,8 +5804,8 @@ impl Renderer { draw_target.dimensions().width as f32, 0.0, draw_target.dimensions().height as f32, - ORTHO_NEAR_PLANE, - ORTHO_FAR_PLANE, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), ); let clear_depth = if target.needs_depth() { @@ -5910,10 +5950,6 @@ impl Renderer { self.debug_flags = flags; } - pub fn save_cpu_profile(&self, filename: &str) { - write_profile(filename); - } - fn draw_frame_debug_items(&mut self, items: &[DebugItem]) { if items.is_empty() { return; @@ -6312,6 +6348,7 @@ impl Renderer { if self.debug_overlay_state.current_size.is_some() { compositor.destroy_surface(NativeSurfaceId::DEBUG_OVERLAY); } + compositor.deinit(); } self.gpu_cache_texture.deinit(&mut self.device); if let Some(dither_matrix_texture) = self.dither_matrix_texture { @@ -6511,7 +6548,8 @@ pub trait AsyncPropertySampler { /// This is called for each transaction with the generate_frame flag set /// (i.e. that will trigger a render). The list of frame messages returned /// are processed as though they were part of the original transaction. - fn sample(&self, document_id: DocumentId) -> Vec; + fn sample(&self, document_id: DocumentId, + doc: &FastHashMap) -> Vec; /// This is called exactly once, when the render backend thread is about to /// terminate. fn deregister(&self); diff --git a/webrender/src/scene.rs b/webrender/src/scene.rs index 0416ec8f68..0f468b494f 100644 --- a/webrender/src/scene.rs +++ b/webrender/src/scene.rs @@ -296,6 +296,7 @@ impl BuiltScene { background_color: None, compositor_kind: CompositorKind::default(), tile_size_override: None, + max_depth_ids: 0, }, } } diff --git a/webrender/src/scene_builder_thread.rs b/webrender/src/scene_builder_thread.rs index 186e622233..4827c5a6a3 100644 --- a/webrender/src/scene_builder_thread.rs +++ b/webrender/src/scene_builder_thread.rs @@ -320,6 +320,8 @@ impl SceneBuilderThread { } loop { + tracy_begin_frame!("scene_builder_thread"); + match self.rx.recv() { Ok(SceneBuilderRequest::WakeUp) => {} Ok(SceneBuilderRequest::Flush(tx)) => { @@ -379,6 +381,8 @@ impl SceneBuilderThread { if let Some(ref hooks) = self.hooks { hooks.poke(); } + + tracy_end_frame!("scene_builder_thread"); } if let Some(ref hooks) = self.hooks { diff --git a/webrender/src/shade.rs b/webrender/src/shade.rs index 9c15b8828c..585917b14f 100644 --- a/webrender/src/shade.rs +++ b/webrender/src/shade.rs @@ -421,16 +421,20 @@ impl TextShader { features: &[&'static str], precache_flags: ShaderPrecacheFlags, ) -> Result { + let mut simple_features = features.to_vec(); + simple_features.push("ALPHA_PASS"); + let simple = LazilyCompiledShader::new( ShaderKind::Text, name, - features, + &simple_features, device, precache_flags, )?; let mut glyph_transform_features = features.to_vec(); glyph_transform_features.push("GLYPH_TRANSFORM"); + glyph_transform_features.push("ALPHA_PASS"); let glyph_transform = LazilyCompiledShader::new( ShaderKind::Text, @@ -957,7 +961,7 @@ impl Shaders { } fn get_yuv_shader_index(buffer_kind: ImageBufferKind) -> usize { - (buffer_kind as usize) + buffer_kind as usize } pub fn get_composite_shader( diff --git a/webrender_api/src/api.rs b/webrender_api/src/api.rs index c2bf8c4faf..ad57931a73 100644 --- a/webrender_api/src/api.rs +++ b/webrender_api/src/api.rs @@ -6,7 +6,7 @@ extern crate serde_bytes; -use crate::channel::{self, MsgSender, Payload, PayloadSender}; +use crate::channel::{self, MsgReceiver, MsgSender, Payload, PayloadSender}; use peek_poke::PeekPoke; use std::cell::Cell; use std::fmt; @@ -1684,13 +1684,14 @@ impl RenderApi { } /// Synchronously request an object that can perform fast hit testing queries. - pub fn request_hit_tester(&self, document_id: DocumentId) -> Arc { + pub fn request_hit_tester(&self, document_id: DocumentId) -> HitTesterRequest { let (tx, rx) = channel::msg_channel().unwrap(); self.send_frame_msg( document_id, FrameMsg::RequestHitTester(tx) ); - rx.recv().unwrap() + + HitTesterRequest { rx } } /// Setup the output region in the framebuffer for a given document. @@ -1781,6 +1782,20 @@ impl Drop for RenderApi { } } +/// A hit tester requested to the render backend thread but not necessarily ready yet. +/// +/// The request should be resolved as late as possible to reduce the likelihood of blocking. +pub struct HitTesterRequest { + rx: MsgReceiver>, +} + +impl HitTesterRequest { + /// Block until the hit tester is available and return it, consuming teh request. + pub fn resolve(self) -> Arc { + self.rx.recv().unwrap() + } +} + /// #[derive(Clone, Deserialize, Serialize)] pub struct ScrollNodeState { diff --git a/webrender_api/src/channel.rs b/webrender_api/src/channel.rs index 4bf5436af3..2bc4a5f16b 100644 --- a/webrender_api/src/channel.rs +++ b/webrender_api/src/channel.rs @@ -79,8 +79,7 @@ pub struct MsgReceiver { impl MsgReceiver { pub fn recv(&self) -> Result { - use std::error::Error; - self.rx.recv().map_err(|e| io::Error::new(ErrorKind::Other, e.description())) + self.rx.recv().map_err(|e| io::Error::new(ErrorKind::Other, e.to_string())) } pub fn to_mpsc_receiver(self) -> mpsc::Receiver { diff --git a/wrench/Cargo.toml b/wrench/Cargo.toml index 247057b179..5f16b129d0 100644 --- a/wrench/Cargo.toml +++ b/wrench/Cargo.toml @@ -12,7 +12,7 @@ bincode = "1.0" byteorder = "1.0" env_logger = { version = "0.5", optional = true } euclid = "0.20" -gleam = "0.9.2" +gleam = "0.10.0" glutin = "0.21" app_units = "0.7" clap = { version = "2", features = ["yaml"] } @@ -29,6 +29,7 @@ webrender_api = {path = "../webrender_api", features=["serialize","deserialize"] winit = "0.19" serde = {version = "1.0", features = ["derive"] } semver = "0.9.0" +swgl = { path = "../swgl", optional = true } [dependencies.image] version = "0.23" @@ -42,6 +43,7 @@ core-foundation = "0.6.4" [features] default = [ "env_logger" ] headless = [ "osmesa-sys", "osmesa-src" ] +software = [ "swgl" ] [target.'cfg(target_os = "windows")'.dependencies] dwrote = "0.9" diff --git a/wrench/reftests/image/occlusion.png b/wrench/reftests/image/occlusion.png new file mode 100644 index 0000000000..c62a58cdec Binary files /dev/null and b/wrench/reftests/image/occlusion.png differ diff --git a/wrench/reftests/image/occlusion.yaml b/wrench/reftests/image/occlusion.yaml new file mode 100644 index 0000000000..4e89a7765b --- /dev/null +++ b/wrench/reftests/image/occlusion.yaml @@ -0,0 +1,15 @@ +# Ensure that the clip rect of a primitive that is promoted to a compositor +# surface is correctly applied when registering it as an occlusion plane. + +root: + items: + - type: rect + color: red + bounds: [50, 50, 200, 200] + + - type: yuv-image + format: interleaved + src: spacex-yuv.png + bounds: [50, 50, 200, 200] + prefer-compositor-surface: true + clip-rect: [75, 75, 150, 150] diff --git a/wrench/reftests/image/reftest.list b/wrench/reftests/image/reftest.list index 733eff8cd2..c41eb8b3c9 100644 --- a/wrench/reftests/image/reftest.list +++ b/wrench/reftests/image/reftest.list @@ -9,3 +9,4 @@ platform(linux,mac) == yuv.yaml yuv.png skip_on(android,device) == tiled-clip-chain.yaml tiled-clip-chain-ref.yaml skip_on(android,device) == tiled-complex-clip.yaml tiled-complex-clip-ref.yaml platform(linux,mac) == texture-rect.yaml texture-rect-ref.yaml +platform(linux) == occlusion.yaml occlusion.png diff --git a/wrench/reftests/mask/reftest.list b/wrench/reftests/mask/reftest.list index 77e336f339..7a18b4a010 100644 --- a/wrench/reftests/mask/reftest.list +++ b/wrench/reftests/mask/reftest.list @@ -10,7 +10,7 @@ platform(linux,mac) == rounded-corners.yaml rounded-corners.png platform(linux,mac) fuzzy(1,17500) color_targets(3) alpha_targets(1) == mask-atomicity.yaml mask-atomicity-ref.yaml platform(linux,mac) fuzzy(1,17500) == mask-atomicity-tiling.yaml mask-atomicity-ref.yaml platform(linux,mac) == mask-perspective.yaml mask-perspective.png -== fuzzy(1,7) mask-perspective-tiling.yaml mask-perspective.yaml +== fuzzy(1,11) mask-perspective-tiling.yaml mask-perspective.yaml platform(linux,mac) == checkerboard.yaml checkerboard.png skip_on(android,device) == checkerboard.yaml checkerboard-tiling.yaml # Fails on a Pixel2 == missing-mask.yaml missing-mask-ref.yaml diff --git a/wrench/src/args.yaml b/wrench/src/args.yaml index 1567e994cc..7080b1bbdb 100644 --- a/wrench/src/args.yaml +++ b/wrench/src/args.yaml @@ -46,6 +46,9 @@ args: - angle: long: angle help: Enable ANGLE rendering (on Windows only) + - software: + long: software + help: Enable software rendering - dp_ratio: short: p long: device-pixel-ratio diff --git a/wrench/src/main.rs b/wrench/src/main.rs index cb2fafef71..4ffd0eddc2 100644 --- a/wrench/src/main.rs +++ b/wrench/src/main.rs @@ -30,6 +30,8 @@ mod cgfont_to_data; use crate::binary_frame_reader::BinaryFrameReader; use gleam::gl; +#[cfg(feature = "software")] +use gleam::gl::Gl; use crate::perf::PerfHarness; use crate::png::save_flipped; use crate::rawtest::RawtestHarness; @@ -44,6 +46,8 @@ use std::path::{Path, PathBuf}; use std::process; use std::ptr; use std::rc::Rc; +#[cfg(feature = "software")] +use std::slice; use std::sync::mpsc::{channel, Sender, Receiver}; use webrender::DebugFlags; use webrender::api::*; @@ -129,22 +133,57 @@ impl HeadlessContext { } } +#[cfg(not(feature = "software"))] +mod swgl { + pub struct Context; +} + pub enum WindowWrapper { - WindowedContext(glutin::WindowedContext, Rc), - Angle(winit::Window, angle::Context, Rc), - Headless(HeadlessContext, Rc), + WindowedContext(glutin::WindowedContext, Rc, Option), + Angle(winit::Window, angle::Context, Rc, Option), + Headless(HeadlessContext, Rc, Option), } pub struct HeadlessEventIterater; impl WindowWrapper { + #[cfg(feature = "software")] + fn upload_software_to_native(&self) { + match *self { + WindowWrapper::Headless(..) => return, + _ => {} + } + let swgl = match self.software_gl() { + Some(swgl) => swgl, + None => return, + }; + swgl.finish(); + let gl = self.native_gl(); + let tex = gl.gen_textures(1)[0]; + gl.bind_texture(gl::TEXTURE_2D, tex); + let (data_ptr, w, h) = swgl.get_color_buffer(0, true); + let buffer = unsafe { slice::from_raw_parts(data_ptr as *const u8, w as usize * h as usize * 4) }; + gl.tex_image_2d(gl::TEXTURE_2D, 0, gl::RGBA8 as gl::GLint, w, h, 0, gl::BGRA, gl::UNSIGNED_BYTE, Some(buffer)); + let fb = gl.gen_framebuffers(1)[0]; + gl.bind_framebuffer(gl::READ_FRAMEBUFFER, fb); + gl.framebuffer_texture_2d(gl::READ_FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, tex, 0); + gl.blit_framebuffer(0, 0, w, h, 0, 0, w, h, gl::COLOR_BUFFER_BIT, gl::NEAREST); + gl.delete_framebuffers(&[fb]); + gl.delete_textures(&[tex]); + gl.finish(); + } + + #[cfg(not(feature = "software"))] + fn upload_software_to_native(&self) { + } + fn swap_buffers(&self) { match *self { - WindowWrapper::WindowedContext(ref windowed_context, _) => { + WindowWrapper::WindowedContext(ref windowed_context, _, _) => { windowed_context.swap_buffers().unwrap() } - WindowWrapper::Angle(_, ref context, _) => context.swap_buffers().unwrap(), - WindowWrapper::Headless(_, _) => {} + WindowWrapper::Angle(_, ref context, _, _) => context.swap_buffers().unwrap(), + WindowWrapper::Headless(_, _, _) => {} } } @@ -157,62 +196,122 @@ impl WindowWrapper { DeviceIntSize::new(size.width as i32, size.height as i32) } match *self { - WindowWrapper::WindowedContext(ref windowed_context, _) => { + WindowWrapper::WindowedContext(ref windowed_context, ..) => { inner_size(windowed_context.window()) } WindowWrapper::Angle(ref window, ..) => inner_size(window), - WindowWrapper::Headless(ref context, _) => DeviceIntSize::new(context.width, context.height), + WindowWrapper::Headless(ref context, ..) => DeviceIntSize::new(context.width, context.height), } } fn hidpi_factor(&self) -> f32 { match *self { - WindowWrapper::WindowedContext(ref windowed_context, _) => { + WindowWrapper::WindowedContext(ref windowed_context, ..) => { windowed_context.window().get_hidpi_factor() as f32 } WindowWrapper::Angle(ref window, ..) => window.get_hidpi_factor() as f32, - WindowWrapper::Headless(_, _) => 1.0, + WindowWrapper::Headless(..) => 1.0, } } fn resize(&mut self, size: DeviceIntSize) { match *self { - WindowWrapper::WindowedContext(ref mut windowed_context, _) => { + WindowWrapper::WindowedContext(ref mut windowed_context, ..) => { windowed_context.window() .set_inner_size(LogicalSize::new(size.width as f64, size.height as f64)) }, WindowWrapper::Angle(ref mut window, ..) => { window.set_inner_size(LogicalSize::new(size.width as f64, size.height as f64)) }, - WindowWrapper::Headless(_, _) => unimplemented!(), // requites Glutin update + WindowWrapper::Headless(..) => unimplemented!(), // requites Glutin update } } fn set_title(&mut self, title: &str) { match *self { - WindowWrapper::WindowedContext(ref windowed_context, _) => { + WindowWrapper::WindowedContext(ref windowed_context, ..) => { windowed_context.window().set_title(title) } WindowWrapper::Angle(ref window, ..) => window.set_title(title), - WindowWrapper::Headless(_, _) => (), + WindowWrapper::Headless(..) => (), } } - pub fn gl(&self) -> &dyn gl::Gl { + #[cfg(feature = "software")] + pub fn software_gl(&self) -> Option<&swgl::Context> { + match *self { + WindowWrapper::WindowedContext(_, _, ref swgl) | + WindowWrapper::Angle(_, _, _, ref swgl) | + WindowWrapper::Headless(_, _, ref swgl) => swgl.as_ref(), + } + } + + pub fn native_gl(&self) -> &dyn gl::Gl { match *self { - WindowWrapper::WindowedContext(_, ref gl) | - WindowWrapper::Angle(_, _, ref gl) | - WindowWrapper::Headless(_, ref gl) => &**gl, + WindowWrapper::WindowedContext(_, ref gl, _) | + WindowWrapper::Angle(_, _, ref gl, _) | + WindowWrapper::Headless(_, ref gl, _) => &**gl, } } + #[cfg(feature = "software")] + pub fn gl(&self) -> &dyn gl::Gl { + if let Some(swgl) = self.software_gl() { + swgl + } else { + self.native_gl() + } + } + + #[cfg(not(feature = "software"))] + pub fn gl(&self) -> &dyn gl::Gl { + self.native_gl() + } + pub fn clone_gl(&self) -> Rc { match *self { - WindowWrapper::WindowedContext(_, ref gl) | - WindowWrapper::Angle(_, _, ref gl) | - WindowWrapper::Headless(_, ref gl) => gl.clone(), + WindowWrapper::WindowedContext(_, ref gl, ref swgl) | + WindowWrapper::Angle(_, _, ref gl, ref swgl) | + WindowWrapper::Headless(_, ref gl, ref swgl) => { + match swgl { + #[cfg(feature = "software")] + Some(ref swgl) => Rc::new(swgl.clone()), + None => gl.clone(), + _ => panic!(), + } + } } } + + + #[cfg(feature = "software")] + fn update_software(&self, dim: DeviceIntSize) { + if let Some(swgl) = self.software_gl() { + swgl.init_default_framebuffer(dim.width, dim.height); + } + } + + #[cfg(not(feature = "software"))] + fn update_software(&self, _dim: DeviceIntSize) { + } + + fn update(&self, wrench: &mut Wrench) { + let dim = self.get_inner_size(); + self.update_software(dim); + wrench.update(dim); + } +} + +#[cfg(feature = "software")] +fn make_software_context() -> Option { + let ctx = swgl::Context::create(); + ctx.make_current(); + Some(ctx) +} + +#[cfg(not(feature = "software"))] +fn make_software_context() -> Option { + None } fn make_window( @@ -222,7 +321,14 @@ fn make_window( events_loop: &Option, angle: bool, gl_request: glutin::GlRequest, + software: bool, ) -> WindowWrapper { + let sw_ctx = if software { + make_software_context() + } else { + None + }; + let wrapper = match *events_loop { Some(ref events_loop) => { let context_builder = glutin::ContextBuilder::new() @@ -254,7 +360,7 @@ fn make_window( glutin::Api::WebGl => unimplemented!(), }; - WindowWrapper::Angle(_window, _context, gl) + WindowWrapper::Angle(_window, _context, gl, sw_ctx) } else { let windowed_context = context_builder .build_windowed(window_builder, events_loop) @@ -280,30 +386,39 @@ fn make_window( glutin::Api::WebGl => unimplemented!(), }; - WindowWrapper::WindowedContext(windowed_context, gl) + WindowWrapper::WindowedContext(windowed_context, gl, sw_ctx) } } None => { - let gl = match gl::GlType::default() { - gl::GlType::Gl => unsafe { - gl::GlFns::load_with(|symbol| { - HeadlessContext::get_proc_address(symbol) as *const _ - }) - }, - gl::GlType::Gles => unsafe { - gl::GlesFns::load_with(|symbol| { - HeadlessContext::get_proc_address(symbol) as *const _ - }) - }, + let gl = match sw_ctx { + #[cfg(feature = "software")] + Some(ref sw_ctx) => Rc::new(sw_ctx.clone()), + None => { + match gl::GlType::default() { + gl::GlType::Gl => unsafe { + gl::GlFns::load_with(|symbol| { + HeadlessContext::get_proc_address(symbol) as *const _ + }) + }, + gl::GlType::Gles => unsafe { + gl::GlesFns::load_with(|symbol| { + HeadlessContext::get_proc_address(symbol) as *const _ + }) + }, + } + } + _ => panic!(), }; - WindowWrapper::Headless(HeadlessContext::new(size.width, size.height), gl) + WindowWrapper::Headless(HeadlessContext::new(size.width, size.height), gl, sw_ctx) } }; - wrapper.gl().clear_color(0.3, 0.0, 0.0, 1.0); + let gl = wrapper.gl(); + + gl.clear_color(0.3, 0.0, 0.0, 1.0); - let gl_version = wrapper.gl().get_string(gl::VERSION); - let gl_renderer = wrapper.gl().get_string(gl::RENDERER); + let gl_version = gl.get_string(gl::VERSION); + let gl_renderer = gl.get_string(gl::RENDERER); let dp_ratio = dp_ratio.unwrap_or(wrapper.hidpi_factor()); println!("OpenGL version {}, {}", gl_version, gl_renderer); @@ -512,6 +627,8 @@ fn main() { } }; + let software = args.is_present("software"); + let mut window = make_window( size, dp_ratio, @@ -519,6 +636,7 @@ fn main() { &events_loop, args.is_present("angle"), gl_request, + software, ); let dp_ratio = dp_ratio.unwrap_or(window.hidpi_factor()); let dim = window.get_inner_size(); @@ -553,6 +671,7 @@ fn main() { dump_shader_source, notifier, ); + window.update(&mut wrench); if let Some(window_title) = wrench.take_title() { if !cfg!(windows) { @@ -645,11 +764,9 @@ fn render<'a>( let mut show_help = false; let mut do_loop = false; - let mut cpu_profile_index = 0; let mut cursor_position = WorldPoint::zero(); - let dim = window.get_inner_size(); - wrench.update(dim); + window.update(wrench); thing.do_frame(wrench); let mut debug_flags = DebugFlags::empty(); @@ -779,11 +896,6 @@ fn render<'a>( show_help = !show_help; do_render = true; } - VirtualKeyCode::T => { - let file_name = format!("profile-{}.json", cpu_profile_index); - wrench.renderer.save_cpu_profile(&file_name); - cpu_profile_index += 1; - } VirtualKeyCode::C => { let path = PathBuf::from("../captures/wrench"); wrench.api.save_capture(path, CaptureBits::all()); @@ -847,8 +959,7 @@ fn render<'a>( } } - let dim = window.get_inner_size(); - wrench.update(dim); + window.update(wrench); if do_frame { let frame_num = thing.do_frame(wrench); @@ -863,6 +974,7 @@ fn render<'a>( } wrench.render(); + window.upload_software_to_native(); window.swap_buffers(); if do_loop {