diff --git a/Cargo.lock b/Cargo.lock index 66c793b..1f80fa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "ansi_term" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi", ] @@ -48,7 +48,7 @@ version = "0.33.3+1.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc4f1d82f164f838ae413296d1131aa6fa79b917d25bebaa7033d25620c09219" dependencies = [ - "libloading 0.7.1", + "libloading 0.7.3", ] [[package]] @@ -58,7 +58,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f91ce4c6be1a2ba99d3d6cd57d5bae9ac6d6f903b5ae53d6b1dee2edf872af" dependencies = [ "ash", - "raw-window-handle", + "raw-window-handle 0.3.4", "raw-window-metal", ] @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" @@ -93,18 +93,18 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "bytemuck" -version = "1.7.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" +checksum = "562e382481975bc61d11275ac5e62a19abd00b0547d99516a415336f183dcd0e" dependencies = [ "proc-macro2", "quote", @@ -128,10 +128,29 @@ dependencies = [ ] [[package]] -name = "cc" -version = "1.0.72" +name = "cbindgen" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "51e3973b165dc0f435831a9e426de67e894de532754ff7a3f307c03ee5dec7dc" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" @@ -147,9 +166,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.33.3" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", @@ -169,7 +188,7 @@ dependencies = [ "bitflags", "block", "cocoa-foundation", - "core-foundation 0.9.2", + "core-foundation 0.9.3", "core-graphics 0.22.3", "foreign-types", "libc", @@ -184,7 +203,7 @@ checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" dependencies = [ "bitflags", "block", - "core-foundation 0.9.2", + "core-foundation 0.9.3", "core-graphics-types", "foreign-types", "libc", @@ -203,9 +222,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys 0.8.3", "libc", @@ -242,7 +261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ "bitflags", - "core-foundation 0.9.2", + "core-foundation 0.9.3", "core-graphics-types", "foreign-types", "libc", @@ -255,7 +274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" dependencies = [ "bitflags", - "core-foundation 0.9.2", + "core-foundation 0.9.3", "foreign-types", "libc", ] @@ -275,9 +294,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] @@ -298,9 +317,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -319,10 +338,11 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" dependencies = [ + "autocfg", "cfg-if 1.0.0", "crossbeam-utils", "lazy_static", @@ -332,9 +352,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -342,14 +362,20 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", "lazy_static", ] +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + [[package]] name = "darling" version = "0.10.2" @@ -395,31 +421,20 @@ dependencies = [ "byteorder", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "dirs" -version = "3.0.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -447,7 +462,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" dependencies = [ - "libloading 0.7.1", + "libloading 0.7.3", ] [[package]] @@ -456,6 +471,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "fnv" version = "1.0.7" @@ -490,9 +514,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if 1.0.0", "libc", @@ -505,6 +529,21 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -520,6 +559,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -529,6 +578,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "jni-sys" version = "0.3.0" @@ -552,9 +607,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.107" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "libloading" @@ -568,9 +623,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -578,18 +633,19 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if 1.0.0", ] @@ -611,9 +667,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" @@ -626,9 +682,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -677,9 +733,9 @@ dependencies = [ [[package]] name = "mio-misc" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ddf05411bb159cdb5801bb10002afb66cb4572be656044315e363460ce69dc2" +checksum = "b47412f3a52115b936ff2a229b803498c7b4d332adeb87c2f1498c9da54c398c" dependencies = [ "crossbeam", "crossbeam-queue", @@ -696,6 +752,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "moscato" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8372f6cdc8b2c431750a9c4edbc8d9c511ef1a68472aaa02500493414a407c64" +dependencies = [ + "pinot", +] + [[package]] name = "ndk" version = "0.3.0" @@ -737,9 +802,9 @@ dependencies = [ [[package]] name = "ndk-sys" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" [[package]] name = "nix" @@ -767,41 +832,39 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi", ] [[package]] name = "num_enum" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" dependencies = [ - "derivative", "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -828,9 +891,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "owned_ttf_parser" @@ -872,6 +935,19 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pgpu-render" +version = "0.1.0" +dependencies = [ + "cbindgen", + "cocoa", + "metal", + "objc", + "piet-gpu", + "piet-gpu-hal", + "piet-scene", +] + [[package]] name = "piet" version = "0.2.0" @@ -896,7 +972,7 @@ dependencies = [ "piet-gpu-types", "png", "rand", - "raw-window-handle", + "raw-window-handle 0.3.4", "roxmltree", "swash", "winit", @@ -924,7 +1000,7 @@ dependencies = [ "foreign-types", "metal", "objc", - "raw-window-handle", + "raw-window-handle 0.3.4", "smallvec", "winapi", "wio", @@ -951,10 +1027,26 @@ dependencies = [ ] [[package]] -name = "pkg-config" -version = "0.3.22" +name = "piet-scene" +version = "0.1.0" +dependencies = [ + "bytemuck", + "moscato", + "pinot", + "smallvec", +] + +[[package]] +name = "pinot" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" +checksum = "6ba3013ff85036c414a4a3cf826135db204de2bd80d684728550e7130421809a" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "png" @@ -970,9 +1062,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro-crate" @@ -985,9 +1077,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", "toml", @@ -995,18 +1087,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.10" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -1054,11 +1146,21 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" +checksum = "e28f55143d0548dad60bb4fbdc835a3d7ac6acc3324506450c5fdd6e42903a76" dependencies = [ "libc", + "raw-window-handle 0.4.3", +] + +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", ] [[package]] @@ -1070,26 +1172,36 @@ dependencies = [ "cocoa", "core-graphics 0.22.3", "objc", - "raw-window-handle", + "raw-window-handle 0.3.4", ] [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.6", "redox_syscall", + "thiserror", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", ] [[package]] @@ -1111,6 +1223,12 @@ dependencies = [ "owned_ttf_parser", ] +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + [[package]] name = "same-file" version = "1.0.6" @@ -1134,15 +1252,40 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.130" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] [[package]] name = "smallvec" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smithay-client-toolkit" @@ -1187,15 +1330,29 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.81" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -1207,18 +1364,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -1227,9 +1384,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -1291,6 +1448,12 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + [[package]] name = "unicode-width" version = "0.1.9" @@ -1309,12 +1472,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - [[package]] name = "walkdir" version = "2.3.2" @@ -1450,7 +1607,7 @@ checksum = "79610794594d5e86be473ef7763f604f2159cbac8c94debd00df8fb41e86c2f8" dependencies = [ "bitflags", "cocoa", - "core-foundation 0.9.2", + "core-foundation 0.9.3", "core-graphics 0.22.3", "core-video-sys", "dispatch", @@ -1466,7 +1623,7 @@ dependencies = [ "objc", "parking_lot", "percent-encoding", - "raw-window-handle", + "raw-window-handle 0.3.4", "scopeguard", "smithay-client-toolkit", "wayland-client", @@ -1505,9 +1662,9 @@ dependencies = [ [[package]] name = "xdg" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a23fe958c70412687039c86f578938b4a0bb50ec788e96bce4d6ab00ddd5803" +checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" dependencies = [ "dirs", ] diff --git a/Cargo.toml b/Cargo.toml index bfa0030..88d9611 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,11 @@ [workspace] members = [ + "pgpu-render", "piet-gpu", "piet-gpu-derive", "piet-gpu-hal", "piet-gpu-types", + "piet-scene", "tests" ] diff --git a/pgpu-render/Cargo.toml b/pgpu-render/Cargo.toml new file mode 100644 index 0000000..8581d82 --- /dev/null +++ b/pgpu-render/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pgpu-render" +version = "0.1.0" +description = "C interface for glyph rendering using piet-gpu." +license = "MIT/Apache-2.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +piet-gpu = { path = "../piet-gpu" } +piet-gpu-hal = { path = "../piet-gpu-hal" } +piet-scene = { path = "../piet-scene" } + +metal = "0.22" +cocoa = "0.24.0" +objc = "0.2.5" + +[build-dependencies] +cbindgen = "0.20.0" diff --git a/pgpu-render/build.rs b/pgpu-render/build.rs new file mode 100644 index 0000000..f565804 --- /dev/null +++ b/pgpu-render/build.rs @@ -0,0 +1,29 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +extern crate cbindgen; + +use std::env; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_header("/** Automatically generated from pgpu-render/src/lib.rs with cbindgen. **/") + .generate() + .expect("Unable to generate bindings") + .write_to_file("pgpu.h"); +} diff --git a/pgpu-render/pgpu.h b/pgpu-render/pgpu.h new file mode 100644 index 0000000..a72a161 --- /dev/null +++ b/pgpu-render/pgpu.h @@ -0,0 +1,142 @@ +/** Automatically generated from pgpu-render/src/lib.rs with cbindgen. **/ + +#include +#include +#include +#include +#include + +/// Encoded (possibly color) outline for a glyph. +struct PgpuGlyph; + +/// Context for loading and scaling glyphs. +struct PgpuGlyphContext; + +/// Context for loading a scaling glyphs from a specific font. +struct PgpuGlyphProvider; + +/// State and resources for rendering a scene. +struct PgpuRenderer; + +/// Encoded streams and resources describing a vector graphics scene. +struct PgpuScene; + +/// Builder for constructing an encoded scene. +struct PgpuSceneBuilder; + +/// Tag and value for a font variation axis. +struct PgpuFontVariation { + /// Tag that specifies the axis. + uint32_t tag; + /// Requested setting for the axis. + float value; +}; + +/// Description of a font. +struct PgpuFontDesc { + /// Pointer to the context of the font file. + const uint8_t *data; + /// Size of the font file data in bytes. + uintptr_t data_len; + /// Index of the requested font in the font file. + uint32_t index; + /// Unique identifier for the font. + uint64_t unique_id; + /// Requested size in pixels per em unit. Set to 0.0 for + /// unscaled outlines. + float ppem; + /// Pointer to array of font variation settings. + const PgpuFontVariation *variations; + /// Number of font variation settings. + uintptr_t variations_len; +}; + +/// Rectangle defined by minimum and maximum points. +struct PgpuRect { + float x0; + float y0; + float x1; + float y1; +}; + +extern "C" { + +/// Creates a new piet-gpu renderer for the specified Metal device and +/// command queue. +/// +/// device: MTLDevice* +/// queue: MTLCommandQueue* +PgpuRenderer *pgpu_renderer_new(void *device, void *queue); + +/// Renders a prepared scene into a texture target. Commands for rendering are +/// recorded into the specified command buffer. Returns an id representing +/// resources that may have been allocated during this process. After the +/// command buffer has been retired, call `pgpu_renderer_release` with this id +/// to drop any associated resources. +/// +/// target: MTLTexture* +/// cmdbuf: MTLCommandBuffer* +uint32_t pgpu_renderer_render(PgpuRenderer *renderer, + const PgpuScene *scene, + void *target, + void *cmdbuf); + +/// Releases the internal resources associated with the specified id from a +/// previous render operation. +void pgpu_renderer_release(PgpuRenderer *renderer, uint32_t id); + +/// Destroys the piet-gpu renderer. +void pgpu_renderer_destroy(PgpuRenderer *renderer); + +/// Creates a new, empty piet-gpu scene. +PgpuScene *pgpu_scene_new(); + +/// Destroys the piet-gpu scene. +void pgpu_scene_destroy(PgpuScene *scene); + +/// Creates a new builder for filling a piet-gpu scene. The specified scene +/// should not be accessed while the builder is live. +PgpuSceneBuilder *pgpu_scene_builder_new(PgpuScene *scene); + +/// Adds a glyph with the specified transform to the underlying scene. +void pgpu_scene_builder_add_glyph(PgpuSceneBuilder *builder, + const PgpuGlyph *glyph, + const float (*transform)[6]); + +/// Finalizes the scene builder, making the underlying scene ready for +/// rendering. This takes ownership and consumes the builder. +void pgpu_scene_builder_finish(PgpuSceneBuilder *builder); + +/// Creates a new context for loading glyph outlines. +PgpuGlyphContext *pgpu_glyph_context_new(); + +/// Destroys the glyph context. +void pgpu_glyph_context_destroy(PgpuGlyphContext *gcx); + +/// Creates a new glyph provider for the specified glyph context and font +/// descriptor. May return nullptr if the font data is invalid. Only one glyph +/// provider may be live for a glyph context. +PgpuGlyphProvider *pgpu_glyph_provider_new(PgpuGlyphContext *gcx, const PgpuFontDesc *font); + +/// Returns an encoded outline for the specified glyph provider and glyph id. +/// May return nullptr if the requested glyph is not available. +PgpuGlyph *pgpu_glyph_provider_get(PgpuGlyphProvider *provider, uint16_t gid); + +/// Returns an encoded color outline for the specified glyph provider, color +/// palette index and glyph id. May return nullptr if the requested glyph is +/// not available. +PgpuGlyph *pgpu_glyph_provider_get_color(PgpuGlyphProvider *provider, + uint16_t palette_index, + uint16_t gid); + +/// Destroys the glyph provider. +void pgpu_glyph_provider_destroy(PgpuGlyphProvider *provider); + +/// Computes the bounding box for the glyph after applying the specified +/// transform. +PgpuRect pgpu_glyph_bbox(const PgpuGlyph *glyph, const float (*transform)[6]); + +/// Destroys the glyph. +void pgpu_glyph_destroy(PgpuGlyph *glyph); + +} // extern "C" diff --git a/pgpu-render/src/lib.rs b/pgpu-render/src/lib.rs new file mode 100644 index 0000000..7d4c60b --- /dev/null +++ b/pgpu-render/src/lib.rs @@ -0,0 +1,233 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +mod render; + +use render::*; +use std::ffi::c_void; +use std::mem::transmute; + +/// Creates a new piet-gpu renderer for the specified Metal device and +/// command queue. +/// +/// device: MTLDevice* +/// queue: MTLCommandQueue* +#[no_mangle] +pub unsafe extern "C" fn pgpu_renderer_new( + device: *mut c_void, + queue: *mut c_void, +) -> *mut PgpuRenderer { + let device: &metal::DeviceRef = transmute(device); + let queue: &metal::CommandQueueRef = transmute(queue); + Box::into_raw(Box::new(PgpuRenderer::new(device, queue))) +} + +/// Renders a prepared scene into a texture target. Commands for rendering are +/// recorded into the specified command buffer. Returns an id representing +/// resources that may have been allocated during this process. After the +/// command buffer has been retired, call `pgpu_renderer_release` with this id +/// to drop any associated resources. +/// +/// target: MTLTexture* +/// cmdbuf: MTLCommandBuffer* +#[no_mangle] +pub unsafe extern "C" fn pgpu_renderer_render( + renderer: *mut PgpuRenderer, + scene: *const PgpuScene, + target: *mut c_void, + cmdbuf: *mut c_void, +) -> u32 { + let cmdbuf: &metal::CommandBufferRef = transmute(cmdbuf); + let target: &metal::TextureRef = transmute(target); + (*renderer).render(&*scene, cmdbuf, target) +} + +/// Releases the internal resources associated with the specified id from a +/// previous render operation. +#[no_mangle] +pub unsafe extern "C" fn pgpu_renderer_release(renderer: *mut PgpuRenderer, id: u32) { + (*renderer).release(id); +} + +/// Destroys the piet-gpu renderer. +#[no_mangle] +pub unsafe extern "C" fn pgpu_renderer_destroy(renderer: *mut PgpuRenderer) { + Box::from_raw(renderer); +} + +/// Creates a new, empty piet-gpu scene. +#[no_mangle] +pub unsafe extern "C" fn pgpu_scene_new() -> *mut PgpuScene { + Box::into_raw(Box::new(PgpuScene::new())) +} + +/// Destroys the piet-gpu scene. +#[no_mangle] +pub unsafe extern "C" fn pgpu_scene_destroy(scene: *mut PgpuScene) { + Box::from_raw(scene); +} + +/// Creates a new builder for filling a piet-gpu scene. The specified scene +/// should not be accessed while the builder is live. +#[no_mangle] +pub unsafe extern "C" fn pgpu_scene_builder_new( + scene: *mut PgpuScene, +) -> *mut PgpuSceneBuilder<'static> { + Box::into_raw(Box::new((*scene).build())) +} + +/// Adds a glyph with the specified transform to the underlying scene. +#[no_mangle] +pub unsafe extern "C" fn pgpu_scene_builder_add_glyph( + builder: *mut PgpuSceneBuilder<'static>, + glyph: *const PgpuGlyph, + transform: &[f32; 6], +) { + let transform = piet_scene::geometry::Affine::new(transform); + (*builder).add_glyph(&*glyph, &transform); +} + +/// Finalizes the scene builder, making the underlying scene ready for +/// rendering. This takes ownership and consumes the builder. +#[no_mangle] +pub unsafe extern "C" fn pgpu_scene_builder_finish(builder: *mut PgpuSceneBuilder<'static>) { + let builder = Box::from_raw(builder); + builder.finish(); +} + +/// Creates a new context for loading glyph outlines. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_context_new() -> *mut PgpuGlyphContext { + Box::into_raw(Box::new(PgpuGlyphContext::new())) +} + +/// Destroys the glyph context. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_context_destroy(gcx: *mut PgpuGlyphContext) { + Box::from_raw(gcx); +} + +/// Description of a font. +#[derive(Copy, Clone)] +#[repr(C)] +pub struct PgpuFontDesc { + /// Pointer to the context of the font file. + data: *const u8, + /// Size of the font file data in bytes. + data_len: usize, + /// Index of the requested font in the font file. + index: u32, + /// Unique identifier for the font. + unique_id: u64, + /// Requested size in pixels per em unit. Set to 0.0 for + /// unscaled outlines. + ppem: f32, + /// Pointer to array of font variation settings. + variations: *const PgpuFontVariation, + /// Number of font variation settings. + variations_len: usize, +} + +/// Creates a new glyph provider for the specified glyph context and font +/// descriptor. May return nullptr if the font data is invalid. Only one glyph +/// provider may be live for a glyph context. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_provider_new( + gcx: *mut PgpuGlyphContext, + font: *const PgpuFontDesc, +) -> *mut PgpuGlyphProvider<'static> { + let font = &*font; + let font_data = std::slice::from_raw_parts(font.data, font.data_len); + let variations = std::slice::from_raw_parts(font.variations, font.variations_len); + if let Some(provider) = (*gcx).new_provider( + font_data, + font.index, + font.unique_id, + font.ppem, + false, + variations, + ) { + Box::into_raw(Box::new(provider)) + } else { + std::ptr::null_mut() + } +} + +/// Returns an encoded outline for the specified glyph provider and glyph id. +/// May return nullptr if the requested glyph is not available. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_provider_get( + provider: *mut PgpuGlyphProvider, + gid: u16, +) -> *mut PgpuGlyph { + if let Some(glyph) = (*provider).get(gid) { + Box::into_raw(Box::new(glyph)) + } else { + std::ptr::null_mut() + } +} + +/// Returns an encoded color outline for the specified glyph provider, color +/// palette index and glyph id. May return nullptr if the requested glyph is +/// not available. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_provider_get_color( + provider: *mut PgpuGlyphProvider, + palette_index: u16, + gid: u16, +) -> *mut PgpuGlyph { + if let Some(glyph) = (*provider).get_color(palette_index, gid) { + Box::into_raw(Box::new(glyph)) + } else { + std::ptr::null_mut() + } +} + +/// Destroys the glyph provider. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_provider_destroy(provider: *mut PgpuGlyphProvider) { + Box::from_raw(provider); +} + +/// Rectangle defined by minimum and maximum points. +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct PgpuRect { + pub x0: f32, + pub y0: f32, + pub x1: f32, + pub y1: f32, +} + +/// Computes the bounding box for the glyph after applying the specified +/// transform. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_bbox(glyph: *const PgpuGlyph, transform: &[f32; 6]) -> PgpuRect { + let transform = piet_scene::geometry::Affine::new(transform); + let rect = (*glyph).bbox(Some(transform)); + PgpuRect { + x0: rect.min.x, + y0: rect.min.y, + x1: rect.max.x, + y1: rect.max.y, + } +} + +/// Destroys the glyph. +#[no_mangle] +pub unsafe extern "C" fn pgpu_glyph_destroy(glyph: *mut PgpuGlyph) { + Box::from_raw(glyph); +} diff --git a/pgpu-render/src/render.rs b/pgpu-render/src/render.rs new file mode 100644 index 0000000..361ef42 --- /dev/null +++ b/pgpu-render/src/render.rs @@ -0,0 +1,222 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +use piet_gpu::{EncodedSceneRef, PixelFormat, RenderConfig}; +use piet_gpu_hal::{QueryPool, Session}; +use piet_scene::glyph::pinot::{types::Tag, FontDataRef}; +use piet_scene::geometry::{Affine, Rect}; +use piet_scene::glyph::{GlyphContext, GlyphProvider}; +use piet_scene::resource::ResourceContext; +use piet_scene::scene::{Fragment, Scene}; + +/// State and resources for rendering a scene. +pub struct PgpuRenderer { + session: Session, + pgpu_renderer: Option, + query_pool: QueryPool, + width: u32, + height: u32, + is_color: bool, +} + +impl PgpuRenderer { + pub fn new(device: &metal::DeviceRef, queue: &metal::CommandQueueRef) -> Self { + let piet_device = piet_gpu_hal::Device::new_from_raw_mtl(device, &queue); + let session = Session::new(piet_device); + let query_pool = session.create_query_pool(12).unwrap(); + Self { + session, + pgpu_renderer: None, + query_pool, + width: 0, + height: 0, + is_color: false, + } + } + + pub fn render( + &mut self, + scene: &PgpuScene, + cmdbuf: &metal::CommandBufferRef, + target: &metal::TextureRef, + ) -> u32 { + let is_color = target.pixel_format() != metal::MTLPixelFormat::A8Unorm; + let width = target.width() as u32; + let height = target.height() as u32; + if self.pgpu_renderer.is_none() + || self.width != width + || self.height != height + || self.is_color != is_color + { + self.width = width; + self.height = height; + self.is_color = is_color; + let format = if is_color { + PixelFormat::Rgba8 + } else { + PixelFormat::A8 + }; + let config = RenderConfig::new(width as usize, height as usize).pixel_format(format); + unsafe { + self.pgpu_renderer = + piet_gpu::Renderer::new_from_config(&self.session, config, 1).ok(); + } + } + unsafe { + let mut cmd_buf = self.session.cmd_buf_from_raw_mtl(cmdbuf); + let dst_image = self + .session + .image_from_raw_mtl(target, self.width, self.height); + if let Some(renderer) = &mut self.pgpu_renderer { + renderer.upload_scene(&scene.encoded_scene(), 0).unwrap(); + renderer.record(&mut cmd_buf, &self.query_pool, 0); + // TODO later: we can bind the destination image and avoid the copy. + cmd_buf.blit_image(&renderer.image_dev, &dst_image); + } + } + 0 + } + + pub fn release(&mut self, _id: u32) { + // TODO: worry about freeing resources / managing overlapping submits + } +} + +/// Encoded streams and resources describing a vector graphics scene. +pub struct PgpuScene { + scene: Scene, + rcx: ResourceContext, +} + +impl PgpuScene { + pub fn new() -> Self { + Self { + scene: Scene::default(), + rcx: ResourceContext::new(), + } + } + + pub fn build(&mut self) -> PgpuSceneBuilder { + self.rcx.advance(); + PgpuSceneBuilder(piet_scene::scene::build_scene( + &mut self.scene, + &mut self.rcx, + )) + } + + fn encoded_scene<'a>(&'a self) -> EncodedSceneRef<'a, piet_scene::geometry::Affine> { + let d = self.scene.data(); + EncodedSceneRef { + transform_stream: &d.transform_stream, + tag_stream: &d.tag_stream, + pathseg_stream: &d.pathseg_stream, + linewidth_stream: &d.linewidth_stream, + drawtag_stream: &d.drawtag_stream, + drawdata_stream: &d.drawdata_stream, + n_path: d.n_path, + n_pathseg: d.n_pathseg, + n_clip: d.n_clip, + ramp_data: self.rcx.ramp_data(), + } + } +} + +/// Builder for constructing an encoded scene. +pub struct PgpuSceneBuilder<'a>(piet_scene::scene::Builder<'a>); + +impl<'a> PgpuSceneBuilder<'a> { + pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::geometry::Affine) { + self.0.push_transform(*transform); + self.0.append(&glyph.fragment); + self.0.pop_transform(); + } + + pub fn finish(self) { + self.0.finish(); + } +} + +/// Tag and value for a font variation axis. +#[derive(Copy, Clone)] +#[repr(C)] +pub struct PgpuFontVariation { + /// Tag that specifies the axis. + pub tag: u32, + /// Requested setting for the axis. + pub value: f32, +} + +/// Context for loading and scaling glyphs. +pub struct PgpuGlyphContext(GlyphContext); + +impl PgpuGlyphContext { + pub fn new() -> Self { + Self(GlyphContext::new()) + } + + pub fn new_provider<'a>( + &'a mut self, + font_data: &'a [u8], + font_index: u32, + font_id: u64, + ppem: f32, + hint: bool, + variations: &[PgpuFontVariation], + ) -> Option { + let font = FontDataRef::new(font_data).and_then(|f| f.get(font_index))?; + Some(PgpuGlyphProvider( + self.0.new_provider( + &font, + Some(font_id), + ppem, + hint, + variations + .iter() + .map(|variation| (Tag(variation.tag), variation.value)), + ), + )) + } +} + +/// Context for loading a scaling glyphs from a specific font. +pub struct PgpuGlyphProvider<'a>(GlyphProvider<'a>); + +impl<'a> PgpuGlyphProvider<'a> { + pub fn get(&mut self, gid: u16) -> Option { + let fragment = self.0.get(gid)?; + Some(PgpuGlyph { fragment }) + } + + pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option { + let fragment = self.0.get_color(palette_index, gid)?; + Some(PgpuGlyph { fragment }) + } +} + +/// Encoded (possibly color) outline for a glyph. +pub struct PgpuGlyph { + fragment: Fragment, +} + +impl PgpuGlyph { + pub fn bbox(&self, transform: Option) -> Rect { + if let Some(transform) = &transform { + Rect::from_points(self.fragment.points().iter().map(|p| p.transform(transform))) + } else { + Rect::from_points(self.fragment.points()) + } + } +} diff --git a/piet-gpu-hal/src/metal.rs b/piet-gpu-hal/src/metal.rs index 307def8..03a23b0 100644 --- a/piet-gpu-hal/src/metal.rs +++ b/piet-gpu-hal/src/metal.rs @@ -350,7 +350,7 @@ impl crate::backend::Device for MtlDevice { //desc.set_mipmap_level_count(1); let mtl_format = match format { ImageFormat::A8 => metal::MTLPixelFormat::R8Unorm, - ImageFormat::Rgba8 => metal::MTLPixelFormat::RGBA8Unorm, + ImageFormat::Rgba8 => metal::MTLPixelFormat::BGRA8Unorm, }; desc.set_pixel_format(mtl_format); desc.set_usage(metal::MTLTextureUsage::ShaderRead | metal::MTLTextureUsage::ShaderWrite); diff --git a/piet-gpu/src/encoder.rs b/piet-gpu/src/encoder.rs index 2f4b85e..a24ddbc 100644 --- a/piet-gpu/src/encoder.rs +++ b/piet-gpu/src/encoder.rs @@ -37,6 +37,147 @@ pub struct Encoder { n_clip: u32, } +#[derive(Copy, Clone, Debug)] +pub struct EncodedSceneRef<'a, T: Copy + Pod> { + pub transform_stream: &'a [T], + pub tag_stream: &'a [u8], + pub pathseg_stream: &'a [u8], + pub linewidth_stream: &'a [f32], + pub drawtag_stream: &'a [u32], + pub drawdata_stream: &'a [u8], + pub n_path: u32, + pub n_pathseg: u32, + pub n_clip: u32, + pub ramp_data: &'a [u32], +} + +impl<'a, T: Copy + Pod> EncodedSceneRef<'a, T> { + /// Return a config for the element processing pipeline. + /// + /// This does not include further pipeline processing. Also returns the + /// beginning of free memory. + pub fn stage_config(&self) -> (Config, usize) { + // Layout of scene buffer + let drawtag_offset = 0; + let n_drawobj = self.n_drawobj(); + let n_drawobj_padded = align_up(n_drawobj, DRAW_PART_SIZE as usize); + let drawdata_offset = drawtag_offset + n_drawobj_padded * DRAWTAG_SIZE; + let trans_offset = drawdata_offset + self.drawdata_stream.len(); + let n_trans = self.transform_stream.len(); + let n_trans_padded = align_up(n_trans, TRANSFORM_PART_SIZE as usize); + let linewidth_offset = trans_offset + n_trans_padded * TRANSFORM_SIZE; + let n_linewidth = self.linewidth_stream.len(); + let pathtag_offset = linewidth_offset + n_linewidth * LINEWIDTH_SIZE; + let n_pathtag = self.tag_stream.len(); + let n_pathtag_padded = align_up(n_pathtag, PATHSEG_PART_SIZE as usize); + let pathseg_offset = pathtag_offset + n_pathtag_padded; + + // Layout of memory + let mut alloc = 0; + let trans_alloc = alloc; + alloc += trans_alloc + n_trans_padded * TRANSFORM_SIZE; + let pathseg_alloc = alloc; + alloc += pathseg_alloc + self.n_pathseg as usize * PATHSEG_SIZE; + let path_bbox_alloc = alloc; + let n_path = self.n_path as usize; + alloc += path_bbox_alloc + n_path * PATH_BBOX_SIZE; + let drawmonoid_alloc = alloc; + alloc += n_drawobj_padded * DRAWMONOID_SIZE; + let anno_alloc = alloc; + alloc += n_drawobj * ANNOTATED_SIZE; + let clip_alloc = alloc; + let n_clip = self.n_clip as usize; + const CLIP_SIZE: usize = 4; + alloc += n_clip * CLIP_SIZE; + let clip_bic_alloc = alloc; + const CLIP_BIC_SIZE: usize = 8; + // This can round down, as we only reduce the prefix + alloc += (n_clip / CLIP_PART_SIZE as usize) * CLIP_BIC_SIZE; + let clip_stack_alloc = alloc; + const CLIP_EL_SIZE: usize = 20; + alloc += n_clip * CLIP_EL_SIZE; + let clip_bbox_alloc = alloc; + const CLIP_BBOX_SIZE: usize = 16; + alloc += align_up(n_clip as usize, CLIP_PART_SIZE as usize) * CLIP_BBOX_SIZE; + let draw_bbox_alloc = alloc; + alloc += n_drawobj * DRAW_BBOX_SIZE; + let drawinfo_alloc = alloc; + // TODO: not optimized; it can be accumulated during encoding or summed from drawtags + const MAX_DRAWINFO_SIZE: usize = 44; + alloc += n_drawobj * MAX_DRAWINFO_SIZE; + + let config = Config { + n_elements: n_drawobj as u32, + n_pathseg: self.n_pathseg, + pathseg_alloc: pathseg_alloc as u32, + anno_alloc: anno_alloc as u32, + trans_alloc: trans_alloc as u32, + path_bbox_alloc: path_bbox_alloc as u32, + drawmonoid_alloc: drawmonoid_alloc as u32, + clip_alloc: clip_alloc as u32, + clip_bic_alloc: clip_bic_alloc as u32, + clip_stack_alloc: clip_stack_alloc as u32, + clip_bbox_alloc: clip_bbox_alloc as u32, + draw_bbox_alloc: draw_bbox_alloc as u32, + drawinfo_alloc: drawinfo_alloc as u32, + n_trans: n_trans as u32, + n_path: self.n_path, + n_clip: self.n_clip, + trans_offset: trans_offset as u32, + linewidth_offset: linewidth_offset as u32, + pathtag_offset: pathtag_offset as u32, + pathseg_offset: pathseg_offset as u32, + drawtag_offset: drawtag_offset as u32, + drawdata_offset: drawdata_offset as u32, + ..Default::default() + }; + (config, alloc) + } + + pub fn write_scene(&self, buf: &mut BufWrite) { + buf.extend_slice(&self.drawtag_stream); + let n_drawobj = self.drawtag_stream.len(); + buf.fill_zero(padding(n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE); + buf.extend_slice(&self.drawdata_stream); + buf.extend_slice(&self.transform_stream); + let n_trans = self.transform_stream.len(); + buf.fill_zero(padding(n_trans, TRANSFORM_PART_SIZE as usize) * TRANSFORM_SIZE); + buf.extend_slice(&self.linewidth_stream); + buf.extend_slice(&self.tag_stream); + let n_pathtag = self.tag_stream.len(); + buf.fill_zero(padding(n_pathtag, PATHSEG_PART_SIZE as usize)); + buf.extend_slice(&self.pathseg_stream); + } + + /// The number of draw objects in the draw object stream. + pub(crate) fn n_drawobj(&self) -> usize { + self.drawtag_stream.len() + } + + /// The number of paths. + pub(crate) fn n_path(&self) -> u32 { + self.n_path + } + + /// The number of path segments. + pub(crate) fn n_pathseg(&self) -> u32 { + self.n_pathseg + } + + pub(crate) fn n_transform(&self) -> usize { + self.transform_stream.len() + } + + /// The number of tags in the path stream. + pub(crate) fn n_pathtag(&self) -> usize { + self.tag_stream.len() + } + + pub(crate) fn n_clip(&self) -> u32 { + self.n_clip + } +} + /// A scene fragment encoding a glyph. /// /// This is a reduced version of the full encoder. diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs index 773007d..d32a9c5 100644 --- a/piet-gpu/src/lib.rs +++ b/piet-gpu/src/lib.rs @@ -8,9 +8,11 @@ pub mod stages; pub mod test_scenes; mod text; +use bytemuck::Pod; use std::convert::TryInto; pub use blend::{Blend, BlendMode, CompositionMode}; +pub use encoder::EncodedSceneRef; pub use render_ctx::PietGpuRenderContext; pub use gradient::Colrv1RadialGradient; @@ -406,6 +408,61 @@ impl Renderer { Ok(()) } + pub fn upload_scene( + &mut self, + scene: &EncodedSceneRef, + buf_ix: usize, + ) -> Result<(), Error> { + let (mut config, mut alloc) = scene.stage_config(); + let n_drawobj = scene.n_drawobj(); + // TODO: be more consistent in size types + let n_path = scene.n_path() as usize; + self.n_paths = n_path; + self.n_transform = scene.n_transform(); + self.n_drawobj = scene.n_drawobj(); + self.n_pathseg = scene.n_pathseg() as usize; + self.n_pathtag = scene.n_pathtag(); + self.n_clip = scene.n_clip(); + + // These constants depend on encoding and may need to be updated. + // Perhaps we can plumb these from piet-gpu-derive? + const PATH_SIZE: usize = 12; + const BIN_SIZE: usize = 8; + let width_in_tiles = self.width / TILE_W; + let height_in_tiles = self.height / TILE_H; + let tile_base = alloc; + alloc += ((n_path + 3) & !3) * PATH_SIZE; + let bin_base = alloc; + alloc += ((n_drawobj + 255) & !255) * BIN_SIZE; + let ptcl_base = alloc; + alloc += width_in_tiles * height_in_tiles * PTCL_INITIAL_ALLOC; + + config.width_in_tiles = width_in_tiles as u32; + config.height_in_tiles = height_in_tiles as u32; + config.tile_alloc = tile_base as u32; + config.bin_alloc = bin_base as u32; + config.ptcl_alloc = ptcl_base as u32; + unsafe { + // TODO: reallocate scene buffer if size is inadequate + { + let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?; + scene.write_scene(&mut mapped_scene); + } + self.config_bufs[buf_ix].write(&[config])?; + self.memory_buf_host[buf_ix].write(&[alloc as u32, 0 /* Overflow flag */])?; + + // Upload gradient data. + if !scene.ramp_data.is_empty() { + assert!( + self.gradient_bufs[buf_ix].size() as usize + >= std::mem::size_of_val(&*scene.ramp_data) + ); + self.gradient_bufs[buf_ix].write(scene.ramp_data)?; + } + } + Ok(()) + } + pub unsafe fn record(&self, cmd_buf: &mut CmdBuf, query_pool: &QueryPool, buf_ix: usize) { cmd_buf.copy_buffer(&self.config_bufs[buf_ix], &self.config_buf); cmd_buf.copy_buffer(&self.memory_buf_host[buf_ix], &self.memory_buf_dev); diff --git a/piet-scene/Cargo.toml b/piet-scene/Cargo.toml new file mode 100644 index 0000000..df66483 --- /dev/null +++ b/piet-scene/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "piet-scene" +version = "0.1.0" +license = "MIT/Apache-2.0" +edition = "2021" + +[dependencies] +bytemuck = { version = "1.7.2", features = ["derive"] } +smallvec = "1.8.0" +pinot = "0.1.5" +moscato = "0.1.2" diff --git a/piet-scene/src/brush/color.rs b/piet-scene/src/brush/color.rs new file mode 100644 index 0000000..a377888 --- /dev/null +++ b/piet-scene/src/brush/color.rs @@ -0,0 +1,41 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub struct Color { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl Color { + pub const fn rgb8(r: u8, g: u8, b: u8) -> Self { + Self { r, g, b, a: 255 } + } + + pub const fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Self { + Self { r, g, b, a } + } + + pub fn to_premul_u32(self) -> u32 { + let a = self.a as f64 * (1.0 / 255.0); + let r = (self.r as f64 * a).round() as u32; + let g = (self.g as f64 * a).round() as u32; + let b = (self.b as f64 * a).round() as u32; + (r << 24) | (g << 16) | (b << 8) | self.a as u32 + } +} diff --git a/piet-scene/src/brush/gradient.rs b/piet-scene/src/brush/gradient.rs new file mode 100644 index 0000000..161604b --- /dev/null +++ b/piet-scene/src/brush/gradient.rs @@ -0,0 +1,78 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +use super::color::Color; +use crate::geometry::Point; +use smallvec::SmallVec; +use std::hash::{Hash, Hasher}; + +#[derive(Copy, Clone, PartialOrd, Default, Debug)] +pub struct Stop { + pub offset: f32, + pub color: Color, +} + +impl Hash for Stop { + fn hash(&self, state: &mut H) { + self.offset.to_bits().hash(state); + self.color.hash(state); + } +} + +// Override PartialEq to use to_bits for the offset to match with the Hash impl +impl std::cmp::PartialEq for Stop { + fn eq(&self, other: &Self) -> bool { + self.offset.to_bits() == other.offset.to_bits() && self.color == other.color + } +} + +impl std::cmp::Eq for Stop {} + +pub type StopVec = SmallVec<[Stop; 4]>; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Extend { + Pad, + Repeat, + Reflect, +} + +#[derive(Clone, Debug)] +pub struct LinearGradient { + pub start: Point, + pub end: Point, + pub stops: StopVec, + pub extend: Extend, +} + +#[derive(Clone, Debug)] +pub struct RadialGradient { + pub center0: Point, + pub radius0: f32, + pub center1: Point, + pub radius1: f32, + pub stops: StopVec, + pub extend: Extend, +} + +#[derive(Clone, Debug)] +pub struct SweepGradient { + pub center: Point, + pub start_angle: f32, + pub end_angle: f32, + pub stops: StopVec, + pub extend: Extend, +} diff --git a/piet-scene/src/brush/image.rs b/piet-scene/src/brush/image.rs new file mode 100644 index 0000000..07157e7 --- /dev/null +++ b/piet-scene/src/brush/image.rs @@ -0,0 +1,96 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +use std::result::Result; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Format { + A8, + Rgba8, +} + +impl Format { + pub fn data_size(self, width: u32, height: u32) -> Option { + (width as usize) + .checked_mul(height as usize) + .and_then(|size| { + size.checked_mul(match self { + Self::A8 => 1, + Self::Rgba8 => 4, + }) + }) + } +} + +#[derive(Clone, Debug)] +pub struct Image(Arc); + +#[derive(Clone, Debug)] +struct Inner { + id: u64, + format: Format, + width: u32, + height: u32, + data: Vec, +} + +impl Image { + pub fn new( + format: Format, + width: u32, + height: u32, + mut data: Vec, + ) -> Result { + let data_size = format.data_size(width, height).ok_or(DataSizeError)?; + if data.len() < data_size { + return Err(DataSizeError); + } + data.truncate(data_size); + static ID: AtomicU64 = AtomicU64::new(1); + Ok(Self(Arc::new(Inner { + id: ID.fetch_add(1, Ordering::Relaxed), + format, + width, + height, + data, + }))) + } + + pub fn id(&self) -> u64 { + self.0.id + } + + pub fn format(&self) -> Format { + self.0.format + } + + pub fn width(&self) -> u32 { + self.0.width + } + + pub fn height(&self) -> u32 { + self.0.height + } + + pub fn data(&self) -> &[u8] { + &self.0.data + } +} + +#[derive(Clone, Debug)] +pub struct DataSizeError; diff --git a/piet-scene/src/brush/mod.rs b/piet-scene/src/brush/mod.rs new file mode 100644 index 0000000..9cde1fb --- /dev/null +++ b/piet-scene/src/brush/mod.rs @@ -0,0 +1,35 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +mod color; +mod gradient; +mod image; + +pub use color::Color; +pub use gradient::*; +pub use image::*; + +use crate::resource::PersistentBrush; + +#[derive(Clone, Debug)] +pub enum Brush { + Solid(Color), + LinearGradient(LinearGradient), + RadialGradient(RadialGradient), + SweepGradient(SweepGradient), + Image(Image), + Persistent(PersistentBrush), +} diff --git a/piet-scene/src/geometry.rs b/piet-scene/src/geometry.rs new file mode 100644 index 0000000..1ea8f33 --- /dev/null +++ b/piet-scene/src/geometry.rs @@ -0,0 +1,227 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +// This module is based in part on kurbo (https://github.com/linebender/kurbo) + +use bytemuck::{Pod, Zeroable}; +use core::borrow::Borrow; +use core::hash::{Hash, Hasher}; + +/// Two dimensional point. +#[derive(Copy, Clone, PartialEq, PartialOrd, Default, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Point { + pub x: f32, + pub y: f32, +} + +impl Hash for Point { + fn hash(&self, state: &mut H) { + self.x.to_bits().hash(state); + self.y.to_bits().hash(state); + } +} + +impl Point { + pub const fn new(x: f32, y: f32) -> Self { + Self { x, y } + } + + pub fn transform(&self, affine: &Affine) -> Self { + Self { + x: self.x * affine.xx + self.y * affine.yx + affine.dx, + y: self.y * affine.yy + self.y * affine.xy + affine.dy, + } + } +} + +impl From<[f32; 2]> for Point { + fn from(value: [f32; 2]) -> Self { + Self::new(value[0], value[1]) + } +} + +impl From<(f32, f32)> for Point { + fn from(value: (f32, f32)) -> Self { + Self::new(value.0, value.1) + } +} + +/// Affine transformation matrix. +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Affine { + pub xx: f32, + pub yx: f32, + pub xy: f32, + pub yy: f32, + pub dx: f32, + pub dy: f32, +} + +impl Affine { + pub const IDENTITY: Self = Self { + xx: 1.0, + yx: 0.0, + xy: 0.0, + yy: 1.0, + dx: 0.0, + dy: 0.0, + }; + + pub const fn new(elements: &[f32; 6]) -> Self { + Self { + xx: elements[0], + yx: elements[1], + xy: elements[2], + yy: elements[3], + dx: elements[4], + dy: elements[5], + } + } + + /// Creates a new affine transform representing the specified scale along the + /// x and y axes. + pub fn scale(x: f32, y: f32) -> Self { + Self::new(&[x, 0., 0., y, 0., 0.]) + } + + /// Creates a new affine transform representing the specified translation. + pub fn translate(x: f32, y: f32) -> Self { + Self::new(&[1., 0., 0., 1., x, y]) + } + + /// Creates a new affine transform representing a counter-clockwise + /// rotation for the specified angle in radians. + pub fn rotate(th: f32) -> Self { + let (s, c) = th.sin_cos(); + Self::new(&[c, s, -s, c, 0., 0.]) + } + + /// Creates a new skew transform + pub fn skew(x: f32, y: f32) -> Self { + Self::new(&[1., x.tan(), y.tan(), 1., 0., 0.]) + } + + pub fn around_center(&self, x: f32, y: f32) -> Self { + Self::translate(x, y) * *self * Self::translate(-x, -y) + } + + /// Transforms the specified point. + pub fn transform_point(&self, point: Point) -> Point { + Point { + x: point.x * self.xx + point.y * self.yx + self.dx, + y: point.y * self.yy + point.y * self.xy + self.dy, + } + } + + /// Compute the determinant of this transform. + pub fn determinant(self) -> f32 { + self.xx * self.yy - self.yx * self.xy + } + + /// Compute the inverse transform. + /// + /// Produces NaN values when the determinant is zero. + pub fn inverse(self) -> Self { + let inv_det = self.determinant().recip(); + Self::new(&[ + inv_det * self.yy, + -inv_det * self.yx, + -inv_det * self.xy, + inv_det * self.xx, + inv_det * (self.xy * self.dy - self.yy * self.dx), + inv_det * (self.yx * self.dx - self.xx * self.dy), + ]) + } +} + +impl Default for Affine { + fn default() -> Self { + Self::IDENTITY + } +} + +impl std::ops::Mul for Affine { + type Output = Self; + fn mul(self, other: Self) -> Self { + Self::new(&[ + self.xx * other.xx + self.xy * other.yx, + self.yx * other.xx + self.yy * other.yx, + self.xx * other.xy + self.xy * other.yy, + self.yx * other.xy + self.yy * other.yy, + self.xx * other.dx + self.xy * other.dy + self.dx, + self.yx * other.dx + self.yy * other.dy + self.dy, + ]) + } +} + +/// Axis-aligned rectangle represented as minimum and maximum points. +#[derive(Copy, Clone, Default, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Rect { + pub min: Point, + pub max: Point, +} + +impl Rect { + /// Creates a new rectangle that encloses the specified collection of + /// points. + pub fn from_points(points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + let mut rect = Self { + min: Point::new(f32::MAX, f32::MAX), + max: Point::new(f32::MIN, f32::MIN), + }; + let mut count = 0; + for point in points { + rect.add(*point.borrow()); + count += 1; + } + if count != 0 { + rect + } else { + Self::default() + } + } + + /// Returns the width of the rectangle. + pub fn width(&self) -> f32 { + self.max.x - self.min.x + } + + /// Returns the height of the rectangle. + pub fn height(&self) -> f32 { + self.max.y - self.min.y + } + + /// Extends the rectangle to include the specified point. + pub fn add(&mut self, point: Point) { + self.min.x = self.min.x.min(point.x); + self.min.y = self.min.y.min(point.y); + self.max.x = self.max.x.max(point.x); + self.max.y = self.max.y.max(point.y); + } + + /// Returns a new rectangle that encloses the minimum and maximum points + /// of this rectangle after applying the specified transform to each. + pub fn transform(&self, affine: &Affine) -> Self { + Self::from_points([self.min.transform(affine), self.max.transform(affine)]) + } +} diff --git a/piet-scene/src/glyph/mod.rs b/piet-scene/src/glyph/mod.rs new file mode 100644 index 0000000..3bfa36c --- /dev/null +++ b/piet-scene/src/glyph/mod.rs @@ -0,0 +1,322 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +pub use pinot; + +use crate::brush::{Brush, Color}; +use crate::geometry::Affine; +use crate::path::Element; +use crate::scene::{build_fragment, Fill, Fragment}; + +use moscato::{Context, Scaler}; +use pinot::{types::Tag, FontRef}; + +use smallvec::SmallVec; + +/// General context for creating scene fragments for glyph outlines. +pub struct GlyphContext { + ctx: Context, +} + +impl GlyphContext { + /// Creates a new context. + pub fn new() -> Self { + Self { + ctx: Context::new(), + } + } + + /// Creates a new provider for generating scene fragments for glyphs from + /// the specified font and settings. + pub fn new_provider<'a, V>( + &'a mut self, + font: &FontRef<'a>, + font_id: Option, + ppem: f32, + hint: bool, + variations: V, + ) -> GlyphProvider<'a> + where + V: IntoIterator, + V::Item: Into<(Tag, f32)>, + { + let scaler = if let Some(font_id) = font_id { + self.ctx + .new_scaler_with_id(font, font_id) + .size(ppem) + .hint(hint) + .variations(variations) + .build() + } else { + self.ctx + .new_scaler(font) + .size(ppem) + .hint(hint) + .variations(variations) + .build() + }; + GlyphProvider { scaler } + } +} + +/// Generator for scene fragments containing glyph outlines for a specific +/// font. +pub struct GlyphProvider<'a> { + scaler: Scaler<'a>, +} + +impl<'a> GlyphProvider<'a> { + /// Returns a scene fragment containing the commands to render the + /// specified glyph. + pub fn get(&mut self, gid: u16) -> Option { + let glyph = self.scaler.glyph(gid)?; + let path = glyph.path(0)?; + let mut fragment = Fragment::default(); + let mut builder = build_fragment(&mut fragment); + builder.fill( + Fill::NonZero, + &Brush::Solid(Color::rgb8(255, 255, 255)), + None, + convert_path(path.elements()), + ); + Some(fragment) + } + + /// Returns a scene fragment containing the commands and resources to + /// render the specified color glyph. + pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option { + use crate::geometry::*; + use moscato::Command; + let glyph = self.scaler.color_glyph(palette_index, gid)?; + let mut fragment = Fragment::default(); + let mut builder = build_fragment(&mut fragment); + let mut xform_stack: SmallVec<[Affine; 8]> = SmallVec::new(); + for command in glyph.commands() { + match command { + Command::PushTransform(xform) => { + let xform = if let Some(parent) = xform_stack.last() { + convert_transform(xform) * *parent + } else { + convert_transform(xform) + }; + xform_stack.push(xform); + } + Command::PopTransform => { xform_stack.pop(); }, + Command::PushClip(path_index) => { + let path = glyph.path(*path_index)?; + if let Some(xform) = xform_stack.last() { + builder.push_layer( + Default::default(), + convert_transformed_path(path.elements(), xform), + ); + } else { + builder.push_layer(Default::default(), convert_path(path.elements())); + } + } + Command::PopClip => builder.pop_layer(), + Command::PushLayer(bounds) => { + let mut rect = Rect { + min: Point::new(bounds.min.x, bounds.min.y), + max: Point::new(bounds.max.x, bounds.max.y), + }; + if let Some(xform) = xform_stack.last() { + rect = rect.transform(xform); + } + builder.push_layer(Default::default(), rect.elements()); + } + Command::PopLayer => builder.pop_layer(), + Command::BeginBlend(bounds, mode) => { + let mut rect = Rect { + min: Point::new(bounds.min.x, bounds.min.y), + max: Point::new(bounds.max.x, bounds.max.y), + }; + if let Some(xform) = xform_stack.last() { + rect = rect.transform(xform); + } + builder.push_layer(convert_blend(*mode), rect.elements()) + } + Command::EndBlend => builder.pop_layer(), + Command::SimpleFill(path_index, brush, brush_xform) => { + let path = glyph.path(*path_index)?; + let brush = convert_brush(brush); + let brush_xform = brush_xform.map(|xform| convert_transform(&xform)); + if let Some(xform) = xform_stack.last() { + builder.fill( + Fill::NonZero, + &brush, + brush_xform.map(|x| x * *xform), + convert_transformed_path(path.elements(), xform), + ); + } else { + builder.fill( + Fill::NonZero, + &brush, + brush_xform, + convert_path(path.elements()), + ); + } + } + Command::Fill(brush, brush_xform) => { + // TODO: this needs to compute a bounding box for + // the parent clips + } + } + } + Some(fragment) + } +} + +fn convert_path( + path: impl Iterator + Clone, +) -> impl Iterator + Clone { + use crate::geometry::Point; + path.map(|el| match el { + moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y)), + moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y)), + moscato::Element::QuadTo(p0, p1) => { + Element::QuadTo(Point::new(p0.x, p0.y), Point::new(p1.x, p1.y)) + } + moscato::Element::CurveTo(p0, p1, p2) => Element::CurveTo( + Point::new(p0.x, p0.y), + Point::new(p1.x, p1.y), + Point::new(p2.x, p2.y), + ), + moscato::Element::Close => Element::Close, + }) +} + +fn convert_transformed_path( + path: impl Iterator + Clone, + xform: &Affine, +) -> impl Iterator + Clone { + use crate::geometry::Point; + let xform = *xform; + path.map(move |el| match el { + moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y).transform(&xform)), + moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y).transform(&xform)), + moscato::Element::QuadTo(p0, p1) => Element::QuadTo( + Point::new(p0.x, p0.y).transform(&xform), + Point::new(p1.x, p1.y).transform(&xform), + ), + moscato::Element::CurveTo(p0, p1, p2) => Element::CurveTo( + Point::new(p0.x, p0.y).transform(&xform), + Point::new(p1.x, p1.y).transform(&xform), + Point::new(p2.x, p2.y).transform(&xform), + ), + moscato::Element::Close => Element::Close, + }) +} + +fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::Blend { + use crate::scene::{Blend, Compose, Mix}; + use moscato::CompositeMode; + let mut mix = Mix::Normal; + let mut compose = Compose::SrcOver; + match mode { + CompositeMode::Clear => compose = Compose::Clear, + CompositeMode::Src => compose = Compose::Copy, + CompositeMode::Dest => compose = Compose::Dest, + CompositeMode::SrcOver => {} + CompositeMode::DestOver => compose = Compose::DestOver, + CompositeMode::SrcIn => compose = Compose::SrcIn, + CompositeMode::DestIn => compose = Compose::DestIn, + CompositeMode::SrcOut => compose = Compose::SrcOut, + CompositeMode::DestOut => compose = Compose::DestOut, + CompositeMode::SrcAtop => compose = Compose::SrcAtop, + CompositeMode::DestAtop => compose = Compose::DestAtop, + CompositeMode::Xor => compose = Compose::Xor, + CompositeMode::Plus => compose = Compose::Plus, + CompositeMode::Screen => mix = Mix::Screen, + CompositeMode::Overlay => mix = Mix::Overlay, + CompositeMode::Darken => mix = Mix::Darken, + CompositeMode::Lighten => mix = Mix::Lighten, + CompositeMode::ColorDodge => mix = Mix::ColorDodge, + CompositeMode::ColorBurn => mix = Mix::ColorBurn, + CompositeMode::HardLight => mix = Mix::HardLight, + CompositeMode::SoftLight => mix = Mix::SoftLight, + CompositeMode::Difference => mix = Mix::Difference, + CompositeMode::Exclusion => mix = Mix::Exclusion, + CompositeMode::Multiply => mix = Mix::Multiply, + CompositeMode::HslHue => mix = Mix::Hue, + CompositeMode::HslSaturation => mix = Mix::Saturation, + CompositeMode::HslColor => mix = Mix::Color, + CompositeMode::HslLuminosity => mix = Mix::Luminosity, + } + Blend { mix, compose } +} + +fn convert_transform(xform: &moscato::Transform) -> crate::geometry::Affine { + crate::geometry::Affine { + xx: xform.xx, + yx: xform.yx, + xy: xform.xy, + yy: xform.yy, + dx: xform.dx, + dy: xform.dy, + } +} + +fn convert_brush(brush: &moscato::Brush) -> crate::brush::Brush { + use crate::brush::*; + use crate::geometry::*; + match brush { + moscato::Brush::Solid(color) => Brush::Solid(Color { + r: color.r, + g: color.g, + b: color.b, + a: color.a, + }), + moscato::Brush::LinearGradient(grad) => Brush::LinearGradient(LinearGradient { + start: Point::new(grad.start.x, grad.start.y), + end: Point::new(grad.end.x, grad.end.y), + stops: convert_stops(&grad.stops), + extend: convert_extend(grad.extend), + }), + moscato::Brush::RadialGradient(grad) => Brush::RadialGradient(RadialGradient { + center0: Point::new(grad.center0.x, grad.center0.y), + center1: Point::new(grad.center1.x, grad.center1.y), + radius0: grad.radius0, + radius1: grad.radius1, + stops: convert_stops(&grad.stops), + extend: convert_extend(grad.extend), + }), + } +} + +fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::StopVec { + use crate::brush::Stop; + stops + .iter() + .map(|stop| Stop { + offset: stop.offset, + color: Color { + r: stop.color.r, + g: stop.color.g, + b: stop.color.b, + a: stop.color.a, + }, + }) + .collect() +} + +fn convert_extend(extend: moscato::ExtendMode) -> crate::brush::Extend { + use crate::brush::Extend::*; + match extend { + moscato::ExtendMode::Pad => Pad, + moscato::ExtendMode::Repeat => Repeat, + moscato::ExtendMode::Reflect => Reflect, + } +} diff --git a/piet-scene/src/lib.rs b/piet-scene/src/lib.rs new file mode 100644 index 0000000..a72ff54 --- /dev/null +++ b/piet-scene/src/lib.rs @@ -0,0 +1,22 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +pub mod brush; +pub mod geometry; +pub mod glyph; +pub mod path; +pub mod resource; +pub mod scene; diff --git a/piet-scene/src/path.rs b/piet-scene/src/path.rs new file mode 100644 index 0000000..3ad0ac1 --- /dev/null +++ b/piet-scene/src/path.rs @@ -0,0 +1,63 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +use super::geometry::{Point, Rect}; + +/// Action of a path element. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Verb { + MoveTo, + LineTo, + QuadTo, + CurveTo, + Close, +} + +/// Element of a path represented by a verb and its associated points. +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Element { + MoveTo(Point), + LineTo(Point), + QuadTo(Point, Point), + CurveTo(Point, Point, Point), + Close, +} + +impl Element { + /// Returns the verb that describes the action of the path element. + pub fn verb(&self) -> Verb { + match self { + Self::MoveTo(..) => Verb::MoveTo, + Self::LineTo(..) => Verb::LineTo, + Self::QuadTo(..) => Verb::QuadTo, + Self::CurveTo(..) => Verb::CurveTo, + Self::Close => Verb::Close, + } + } +} + +impl Rect { + pub fn elements(&self) -> impl Iterator + Clone { + let elements = [ + Element::MoveTo((self.min.x, self.min.y).into()), + Element::LineTo((self.max.x, self.min.y).into()), + Element::LineTo((self.max.x, self.max.y).into()), + Element::LineTo((self.min.x, self.max.y).into()), + Element::Close, + ]; + (0..5).map(move |i| elements[i]) + } +} diff --git a/piet-scene/src/resource/gradient.rs b/piet-scene/src/resource/gradient.rs new file mode 100644 index 0000000..39b7f40 --- /dev/null +++ b/piet-scene/src/resource/gradient.rs @@ -0,0 +1,138 @@ +use crate::brush::{Color, Stop, StopVec}; +use std::collections::HashMap; + +const N_SAMPLES: usize = 512; +const RETAINED_COUNT: usize = 64; + +#[derive(Default)] +pub struct RampCache { + epoch: u64, + map: HashMap, + data: Vec, +} + +impl RampCache { + pub fn new() -> Self { + Self::default() + } + + pub fn advance(&mut self) { + self.epoch += 1; + if self.map.len() > RETAINED_COUNT { + self.map + .retain(|_key, value| value.0 < RETAINED_COUNT as u32); + self.data.truncate(RETAINED_COUNT * N_SAMPLES); + } + } + + pub fn clear(&mut self) { + self.epoch = 0; + self.map.clear(); + self.data.clear(); + } + + pub fn add(&mut self, stops: &[Stop]) -> u32 { + if let Some(entry) = self.map.get_mut(stops) { + entry.1 = self.epoch; + entry.0 + } else if self.map.len() < RETAINED_COUNT { + let id = (self.data.len() / N_SAMPLES) as u32; + self.data.extend(make_ramp(stops)); + self.map.insert(stops.into(), (id, self.epoch)); + id + } else { + let mut reuse = None; + for (stops, (id, epoch)) in &self.map { + if *epoch + 2 < self.epoch { + reuse = Some((stops.to_owned(), *id)); + break; + } + } + if let Some((old_stops, id)) = reuse { + self.map.remove(&old_stops); + let start = id as usize * N_SAMPLES; + for (dst, src) in self.data[start..start + N_SAMPLES] + .iter_mut() + .zip(make_ramp(stops)) + { + *dst = src; + } + self.map.insert(stops.into(), (id, self.epoch)); + id + } else { + let id = (self.data.len() / N_SAMPLES) as u32; + self.data.extend(make_ramp(stops)); + self.map.insert(stops.into(), (id, self.epoch)); + id + } + } + } + + pub fn data(&self) -> &[u32] { + &self.data + } +} + +fn make_ramp<'a>(stops: &'a [Stop]) -> impl Iterator + 'a { + let mut last_u = 0.0; + let mut last_c = ColorF64::from_color(stops[0].color); + let mut this_u = last_u; + let mut this_c = last_c; + let mut j = 0; + (0..N_SAMPLES).map(move |i| { + let u = (i as f64) / (N_SAMPLES - 1) as f64; + while u > this_u { + last_u = this_u; + last_c = this_c; + if let Some(s) = stops.get(j + 1) { + this_u = s.offset as f64; + this_c = ColorF64::from_color(s.color); + j += 1; + } else { + break; + } + } + let du = this_u - last_u; + let c = if du < 1e-9 { + this_c + } else { + last_c.lerp(&this_c, (u - last_u) / du) + }; + c.to_premul_u32() + }) +} + +#[derive(Copy, Clone, Debug)] +struct ColorF64([f64; 4]); + +impl ColorF64 { + fn from_color(color: Color) -> Self { + Self([ + color.r as f64 / 255.0, + color.g as f64 / 255.0, + color.b as f64 / 255.0, + color.a as f64 / 255.0, + ]) + } + + fn lerp(&self, other: &Self, a: f64) -> Self { + fn l(x: f64, y: f64, a: f64) -> f64 { + x * (1.0 - a) + y * a + } + Self([ + l(self.0[0], other.0[0], a), + l(self.0[1], other.0[1], a), + l(self.0[2], other.0[2], a), + l(self.0[3], other.0[3], a), + ]) + } + + fn to_premul_u32(&self) -> u32 { + let a = self.0[3].min(1.0).max(0.0); + let r = ((self.0[0] * a).min(1.0).max(0.0) * 255.0) as u32; + let g = ((self.0[1] * a).min(1.0).max(0.0) * 255.0) as u32; + let b = ((self.0[2] * a).min(1.0).max(0.0) * 255.0) as u32; + let a = (a * 255.0) as u32; + b | (g << 8) | (r << 16) | (a << 24) + } +} diff --git a/piet-scene/src/resource/mod.rs b/piet-scene/src/resource/mod.rs new file mode 100644 index 0000000..a1ea58b --- /dev/null +++ b/piet-scene/src/resource/mod.rs @@ -0,0 +1,56 @@ +mod gradient; + +use crate::brush::{Brush, Stop}; +use gradient::RampCache; +use std::collections::HashMap; + +/// Context for caching resources across rendering operations. +#[derive(Default)] +pub struct ResourceContext { + ramps: RampCache, + persistent_map: HashMap, +} + +impl ResourceContext { + pub fn new() -> Self { + Self::default() + } + + pub fn advance(&mut self) { + self.ramps.advance(); + } + + pub fn clear(&mut self) { + self.ramps.clear(); + self.persistent_map.clear(); + } + + pub fn add_ramp(&mut self, stops: &[Stop]) -> u32 { + self.ramps.add(stops) + } + + pub fn create_brush(&mut self, brush: &Brush) -> PersistentBrush { + match brush { + Brush::Persistent(dup) => return *dup, + _ => {} + } + PersistentBrush { kind: 0, id: 0 } + } + + pub fn destroy_brush(&mut self, brush: PersistentBrush) {} + + pub fn ramp_data(&self) -> &[u32] { + &self.ramps.data() + } +} + +/// Handle for a brush that is managed by the resource context. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct PersistentBrush { + kind: u8, + id: u64, +} + +struct PersistentBrushData { + brush: Brush, +} diff --git a/piet-scene/src/scene/blend.rs b/piet-scene/src/scene/blend.rs new file mode 100644 index 0000000..7edc6cd --- /dev/null +++ b/piet-scene/src/scene/blend.rs @@ -0,0 +1,102 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +/// Defines the color mixing function for a blend operation. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] +pub enum Mix { + Normal = 0, + Multiply = 1, + Screen = 2, + Overlay = 3, + Darken = 4, + Lighten = 5, + ColorDodge = 6, + ColorBurn = 7, + HardLight = 8, + SoftLight = 9, + Difference = 10, + Exclusion = 11, + Hue = 12, + Saturation = 13, + Color = 14, + Luminosity = 15, +} + +/// Defines the layer composition function for a blend operation. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(C)] +pub enum Compose { + Clear = 0, + Copy = 1, + Dest = 2, + SrcOver = 3, + DestOver = 4, + SrcIn = 5, + DestIn = 6, + SrcOut = 7, + DestOut = 8, + SrcAtop = 9, + DestAtop = 10, + Xor = 11, + Plus = 12, + PlusDarker = 13, + PlusLighter = 14, +} + +/// Blend mode consisting of mixing and composition functions. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Blend { + pub mix: Mix, + pub compose: Compose, +} + +impl Blend { + pub fn new(mix: Mix, compose: Compose) -> Self { + Self { mix, compose } + } + + pub fn pack(&self) -> u32 { + (self.mix as u32) << 8 | self.compose as u32 + } +} + +impl Default for Blend { + fn default() -> Self { + Self { + mix: Mix::Normal, + compose: Compose::SrcOver, + } + } +} + +impl From for Blend { + fn from(mix: Mix) -> Self { + Self { + mix, + compose: Compose::SrcOver, + } + } +} + +impl From for Blend { + fn from(compose: Compose) -> Self { + Self { + mix: Mix::Normal, + compose, + } + } +} diff --git a/piet-scene/src/scene/builder.rs b/piet-scene/src/scene/builder.rs new file mode 100644 index 0000000..85d75b2 --- /dev/null +++ b/piet-scene/src/scene/builder.rs @@ -0,0 +1,554 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +use super::style::{Fill, Stroke}; +use super::{Affine, Blend, Element, Fragment, FragmentResources, ResourcePatch, Scene, SceneData}; +use crate::brush::*; +use crate::resource::ResourceContext; +use bytemuck::{Pod, Zeroable}; +use core::borrow::Borrow; + +const MAX_BLEND_STACK: usize = 256; + +/// Creates a new builder for constructing a scene. +pub fn build_scene<'a>(scene: &'a mut Scene, resources: &'a mut ResourceContext) -> Builder<'a> { + Builder::new(&mut scene.data, ResourceData::Scene(resources)) +} + +/// Creates a new builder for construction a scene fragment. +pub fn build_fragment<'a>(fragment: &'a mut Fragment) -> Builder<'a> { + Builder::new( + &mut fragment.data, + ResourceData::Fragment(&mut fragment.resources), + ) +} + +/// Builder for constructing a scene or scene fragment. +pub struct Builder<'a> { + scene: &'a mut SceneData, + resources: ResourceData<'a>, + layers: Vec, + transforms: Vec, +} + +impl<'a> Builder<'a> { + /// Creates a new builder for constructing a scene. + fn new(scene: &'a mut SceneData, mut resources: ResourceData<'a>) -> Self { + let is_fragment = match resources { + ResourceData::Fragment(_) => true, + _ => false, + }; + scene.reset(is_fragment); + resources.clear(); + Self { + scene, + resources, + layers: vec![], + transforms: vec![], + } + } + + /// Pushes a transform matrix onto the stack. + pub fn push_transform(&mut self, transform: Affine) { + self.transform(transform); + self.transforms.push(transform); + } + + /// Pops the current transform matrix. + pub fn pop_transform(&mut self) { + if let Some(transform) = self.transforms.pop() { + self.transform(transform.inverse()); + } + } + + /// Pushes a new layer bound by the specifed shape and composed with + /// previous layers using the specified blend mode. + pub fn push_layer<'s, E>(&mut self, blend: Blend, elements: E) + where + E: IntoIterator, + E::IntoIter: Clone, + E::Item: Borrow, + { + self.linewidth(-1.0); + let elements = elements.into_iter(); + self.encode_path(elements, true); + self.begin_clip(Some(blend)); + if self.layers.len() >= MAX_BLEND_STACK { + panic!("Maximum clip/blend stack size {} exceeded", MAX_BLEND_STACK); + } + self.layers.push(blend); + } + + /// Pops the current layer. + pub fn pop_layer(&mut self) { + if let Some(layer) = self.layers.pop() { + self.end_clip(Some(layer)); + } + } + + /// Fills a shape using the specified style and brush. + pub fn fill<'s, E>( + &mut self, + style: Fill, + brush: &Brush, + brush_transform: Option, + elements: E, + ) where + E: IntoIterator, + E::IntoIter: Clone, + E::Item: Borrow, + { + self.linewidth(-1.0); + let elements = elements.into_iter(); + self.encode_path(elements, true); + if let Some(brush_transform) = brush_transform { + self.transform(brush_transform); + self.swap_last_tags(); + self.encode_brush(brush); + self.transform(brush_transform.inverse()); + } else { + self.encode_brush(brush); + } + } + + /// Strokes a shape using the specified style and brush. + pub fn stroke<'s, D, E>( + &mut self, + style: &Stroke, + brush: &Brush, + brush_transform: Option, + elements: E, + ) where + D: Borrow<[f32]>, + E: IntoIterator, + E::IntoIter: Clone, + E::Item: Borrow, + { + self.linewidth(style.width); + let elements = elements.into_iter(); + self.encode_path(elements, false); + if let Some(brush_transform) = brush_transform { + self.transform(brush_transform); + self.swap_last_tags(); + self.encode_brush(brush); + self.transform(brush_transform.inverse()); + } else { + self.encode_brush(brush); + } + } + + /// Appends a fragment to the scene. + pub fn append(&mut self, fragment: &Fragment) { + let drawdata_base = self.scene.drawdata_stream.len(); + self.scene.append(&fragment.data); + match &mut self.resources { + ResourceData::Scene(res) => { + for patch in &fragment.resources.patches { + match patch { + ResourcePatch::Ramp { + drawdata_offset, + stops, + } => { + let stops = &fragment.resources.stops[stops.clone()]; + let ramp_id = res.add_ramp(stops); + let patch_base = *drawdata_offset + drawdata_base; + (&mut self.scene.drawdata_stream[patch_base..patch_base + 4]) + .copy_from_slice(bytemuck::bytes_of(&ramp_id)); + } + } + } + } + ResourceData::Fragment(res) => { + let stops_base = res.stops.len(); + res.stops.extend_from_slice(&fragment.resources.stops); + res.patches.extend(fragment.resources.patches.iter().map( + |pending| match pending { + ResourcePatch::Ramp { + drawdata_offset, + stops, + } => ResourcePatch::Ramp { + drawdata_offset: drawdata_offset + drawdata_base, + stops: stops.start + stops_base..stops.end + stops_base, + }, + }, + )); + } + } + } + + /// Completes construction and finalizes the underlying scene. + pub fn finish(mut self) { + while let Some(layer) = self.layers.pop() { + self.end_clip(Some(layer)); + } + match self.resources { + ResourceData::Fragment(_) => { + // Make sure the transform state is invariant for fragments + while !self.transforms.is_empty() { + self.pop_transform(); + } + } + _ => {} + } + } +} + +impl<'a> Builder<'a> { + fn encode_path(&mut self, elements: E, is_fill: bool) + where + E: Iterator, + E::Item: Borrow, + { + if is_fill { + self.encode_path_inner( + elements + .map(|el| *el.borrow()) + .flat_map(|el| { + match el { + Element::MoveTo(..) => Some(Element::Close), + _ => None, + } + .into_iter() + .chain(Some(el)) + }) + .chain(Some(Element::Close)), + ) + } else { + self.encode_path_inner(elements.map(|el| *el.borrow())) + } + } + + fn encode_path_inner(&mut self, elements: impl Iterator) { + let mut b = PathBuilder::new(&mut self.scene.tag_stream, &mut self.scene.pathseg_stream); + for el in elements { + match el { + Element::MoveTo(p0) => b.move_to(p0.x, p0.y), + Element::LineTo(p0) => b.line_to(p0.x, p0.y), + Element::QuadTo(p0, p1) => b.quad_to(p0.x, p0.y, p1.x, p1.y), + Element::CurveTo(p0, p1, p2) => b.cubic_to(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y), + Element::Close => b.close_path(), + } + } + b.path(); + let n_pathseg = b.n_pathseg(); + self.scene.n_path += 1; + self.scene.n_pathseg += n_pathseg; + } + + fn transform(&mut self, transform: Affine) { + self.scene.tag_stream.push(0x20); + self.scene.transform_stream.push(transform); + } + + // Swap the last two tags in the tag stream; used for transformed + // gradients. + fn swap_last_tags(&mut self) { + let len = self.scene.tag_stream.len(); + self.scene.tag_stream.swap(len - 1, len - 2); + } + + // -1.0 means "fill" + fn linewidth(&mut self, linewidth: f32) { + self.scene.tag_stream.push(0x40); + self.scene.linewidth_stream.push(linewidth); + } + + fn encode_brush(&mut self, brush: &Brush) { + match brush { + Brush::Solid(color) => { + self.scene.drawtag_stream.push(DRAWTAG_FILLCOLOR); + let rgba_color = color.to_premul_u32(); + self.scene + .drawdata_stream + .extend(bytemuck::bytes_of(&FillColor { rgba_color })); + } + Brush::LinearGradient(gradient) => { + let index = self.add_ramp(&gradient.stops); + self.scene.drawtag_stream.push(DRAWTAG_FILLLINGRADIENT); + self.scene + .drawdata_stream + .extend(bytemuck::bytes_of(&FillLinGradient { + index, + p0: [gradient.start.x, gradient.start.y], + p1: [gradient.end.x, gradient.end.y], + })); + } + Brush::RadialGradient(gradient) => { + let index = self.add_ramp(&gradient.stops); + self.scene.drawtag_stream.push(DRAWTAG_FILLRADGRADIENT); + self.scene + .drawdata_stream + .extend(bytemuck::bytes_of(&FillRadGradient { + index, + p0: [gradient.center0.x, gradient.center0.y], + p1: [gradient.center1.x, gradient.center1.y], + r0: gradient.radius0, + r1: gradient.radius1, + })); + } + Brush::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"), + Brush::Image(_image) => todo!("images aren't done yet!"), + Brush::Persistent(_) => todo!("persistent brushes aren't done yet!"), + } + } + + fn add_ramp(&mut self, stops: &[Stop]) -> u32 { + match &mut self.resources { + ResourceData::Scene(res) => res.add_ramp(stops), + ResourceData::Fragment(res) => { + let stops_start = res.stops.len(); + res.stops.extend_from_slice(stops); + let id = res.patches.len() as u32; + res.patches.push(ResourcePatch::Ramp { + drawdata_offset: self.scene.drawdata_stream.len(), + stops: stops_start..stops_start + stops.len(), + }); + id + } + } + } + + /// Start a clip. + fn begin_clip(&mut self, blend: Option) { + self.scene.drawtag_stream.push(DRAWTAG_BEGINCLIP); + let element = Clip { + blend: blend.unwrap_or(Blend::default()).pack(), + }; + self.scene + .drawdata_stream + .extend(bytemuck::bytes_of(&element)); + self.scene.n_clip += 1; + } + + fn end_clip(&mut self, blend: Option) { + self.scene.drawtag_stream.push(DRAWTAG_ENDCLIP); + let element = Clip { + blend: blend.unwrap_or(Blend::default()).pack(), + }; + self.scene + .drawdata_stream + .extend(bytemuck::bytes_of(&element)); + // This is a dummy path, and will go away with the new clip impl. + self.scene.tag_stream.push(0x10); + self.scene.n_path += 1; + self.scene.n_clip += 1; + } +} + +enum ResourceData<'a> { + Fragment(&'a mut FragmentResources), + Scene(&'a mut ResourceContext), +} + +impl ResourceData<'_> { + fn clear(&mut self) { + match self { + Self::Fragment(res) => { + res.patches.clear(); + res.stops.clear(); + } + _ => {} + } + } +} + +// Tags for draw objects. See shader/drawtag.h for the authoritative source. +const DRAWTAG_FILLCOLOR: u32 = 0x44; +const DRAWTAG_FILLLINGRADIENT: u32 = 0x114; +const DRAWTAG_FILLRADGRADIENT: u32 = 0x2dc; +const DRAWTAG_BEGINCLIP: u32 = 0x05; +const DRAWTAG_ENDCLIP: u32 = 0x25; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] +pub struct FillColor { + rgba_color: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] +pub struct FillLinGradient { + index: u32, + p0: [f32; 2], + p1: [f32; 2], +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] +pub struct FillRadGradient { + index: u32, + p0: [f32; 2], + p1: [f32; 2], + r0: f32, + r1: f32, +} + +#[allow(unused)] +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] +pub struct FillImage { + index: u32, + // [i16; 2] + offset: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] +pub struct Clip { + blend: u32, +} + +struct PathBuilder<'a> { + tag_stream: &'a mut Vec, + // If we're never going to use the i16 encoding, it might be + // slightly faster to store this as Vec, we'd get aligned + // stores on ARM etc. + pathseg_stream: &'a mut Vec, + first_pt: [f32; 2], + state: PathState, + n_pathseg: u32, +} + +#[derive(PartialEq)] +enum PathState { + Start, + MoveTo, + NonemptySubpath, +} + +impl<'a> PathBuilder<'a> { + pub fn new(tags: &'a mut Vec, pathsegs: &'a mut Vec) -> PathBuilder<'a> { + PathBuilder { + tag_stream: tags, + pathseg_stream: pathsegs, + first_pt: [0.0, 0.0], + state: PathState::Start, + n_pathseg: 0, + } + } + + pub fn move_to(&mut self, x: f32, y: f32) { + let buf = [x, y]; + let bytes = bytemuck::bytes_of(&buf); + self.first_pt = buf; + if self.state == PathState::MoveTo { + let new_len = self.pathseg_stream.len() - 8; + self.pathseg_stream.truncate(new_len); + } + if self.state == PathState::NonemptySubpath { + if let Some(tag) = self.tag_stream.last_mut() { + *tag |= 4; + } + } + self.pathseg_stream.extend_from_slice(bytes); + self.state = PathState::MoveTo; + } + + pub fn line_to(&mut self, x: f32, y: f32) { + if self.state == PathState::Start { + // should warn or error + return; + } + let buf = [x, y]; + let bytes = bytemuck::bytes_of(&buf); + self.pathseg_stream.extend_from_slice(bytes); + self.tag_stream.push(9); + self.state = PathState::NonemptySubpath; + self.n_pathseg += 1; + } + + pub fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) { + if self.state == PathState::Start { + return; + } + let buf = [x1, y1, x2, y2]; + let bytes = bytemuck::bytes_of(&buf); + self.pathseg_stream.extend_from_slice(bytes); + self.tag_stream.push(10); + self.state = PathState::NonemptySubpath; + self.n_pathseg += 1; + } + + pub fn cubic_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) { + if self.state == PathState::Start { + return; + } + let buf = [x1, y1, x2, y2, x3, y3]; + let bytes = bytemuck::bytes_of(&buf); + self.pathseg_stream.extend_from_slice(bytes); + self.tag_stream.push(11); + self.state = PathState::NonemptySubpath; + self.n_pathseg += 1; + } + + pub fn close_path(&mut self) { + match self.state { + PathState::Start => return, + PathState::MoveTo => { + let new_len = self.pathseg_stream.len() - 8; + self.pathseg_stream.truncate(new_len); + self.state = PathState::Start; + return; + } + PathState::NonemptySubpath => (), + } + let len = self.pathseg_stream.len(); + if len < 8 { + // can't happen + return; + } + let first_bytes = bytemuck::bytes_of(&self.first_pt); + if &self.pathseg_stream[len - 8..len] != first_bytes { + self.pathseg_stream.extend_from_slice(first_bytes); + self.tag_stream.push(13); + self.n_pathseg += 1; + } else { + if let Some(tag) = self.tag_stream.last_mut() { + *tag |= 4; + } + } + self.state = PathState::Start; + } + + fn finish(&mut self) { + if self.state == PathState::MoveTo { + let new_len = self.pathseg_stream.len() - 8; + self.pathseg_stream.truncate(new_len); + } + if let Some(tag) = self.tag_stream.last_mut() { + *tag |= 4; + } + } + + /// Finish encoding a path. + /// + /// Encode this after encoding path segments. + pub fn path(&mut self) { + self.finish(); + // maybe don't encode if path is empty? might throw off sync though + self.tag_stream.push(0x10); + } + + /// Get the number of path segments. + /// + /// This is the number of path segments that will be written by the + /// path stage; use this for allocating the output buffer. + /// + /// Also note: it takes `self` for lifetime reasons. + pub fn n_pathseg(self) -> u32 { + self.n_pathseg + } +} diff --git a/piet-scene/src/scene/mod.rs b/piet-scene/src/scene/mod.rs new file mode 100644 index 0000000..9f7be2f --- /dev/null +++ b/piet-scene/src/scene/mod.rs @@ -0,0 +1,115 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +mod blend; +mod builder; +mod style; + +pub use blend::{Blend, Compose, Mix}; +pub use builder::{build_fragment, build_scene, Builder}; +pub use style::*; + +use super::brush::*; +use super::geometry::{Affine, Point, Rect}; +use super::path::Element; + +use core::ops::Range; + +#[derive(Default)] +pub struct SceneData { + pub transform_stream: Vec, + pub tag_stream: Vec, + pub pathseg_stream: Vec, + pub linewidth_stream: Vec, + pub drawtag_stream: Vec, + pub drawdata_stream: Vec, + pub n_path: u32, + pub n_pathseg: u32, + pub n_clip: u32, +} + +impl SceneData { + fn reset(&mut self, is_fragment: bool) { + self.transform_stream.clear(); + self.tag_stream.clear(); + self.pathseg_stream.clear(); + self.linewidth_stream.clear(); + self.drawtag_stream.clear(); + self.drawdata_stream.clear(); + self.n_path = 0; + self.n_pathseg = 0; + self.n_clip = 0; + if !is_fragment { + self.transform_stream + .push(Affine::new(&[1.0, 0.0, 0.0, 1.0, 0.0, 0.0])); + self.linewidth_stream.push(-1.0); + } + } + + fn append(&mut self, other: &SceneData) { + self.transform_stream + .extend_from_slice(&other.transform_stream); + self.tag_stream.extend_from_slice(&other.tag_stream); + self.pathseg_stream.extend_from_slice(&other.pathseg_stream); + self.linewidth_stream + .extend_from_slice(&other.linewidth_stream); + self.drawtag_stream.extend_from_slice(&other.drawtag_stream); + self.drawdata_stream + .extend_from_slice(&other.drawdata_stream); + self.n_path += other.n_path; + self.n_pathseg += other.n_pathseg; + self.n_clip += other.n_clip; + } +} + +/// Encoded definition of a scene that is ready for rendering when paired with +/// an associated resource context. +#[derive(Default)] +pub struct Scene { + data: SceneData, +} + +impl Scene { + pub fn data(&self) -> &SceneData { + &self.data + } +} + +/// Encoded definition of a scene fragment and associated resources. +#[derive(Default)] +pub struct Fragment { + data: SceneData, + resources: FragmentResources, +} + +impl Fragment { + pub fn points(&self) -> &[Point] { + bytemuck::cast_slice(&self.data.pathseg_stream) + } +} + +#[derive(Default)] +struct FragmentResources { + patches: Vec, + stops: Vec, +} + +enum ResourcePatch { + Ramp { + drawdata_offset: usize, + stops: Range, + }, +} diff --git a/piet-scene/src/scene/style.rs b/piet-scene/src/scene/style.rs new file mode 100644 index 0000000..0aded61 --- /dev/null +++ b/piet-scene/src/scene/style.rs @@ -0,0 +1,71 @@ +// Copyright 2022 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +use core::borrow::Borrow; + +/// Describes the winding rule that determines the interior portion of a path. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Fill { + NonZero, + EvenOdd, +} + +/// Defines the connection between two segments of a stroke. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Join { + /// A straight line connecting the segments. + Bevel, + /// The segments are extended to their natural intersection point. + Miter, + /// An arc between the segments. + Round, +} + +/// Defines the shape to be drawn at the ends of a stroke. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Cap { + /// Flat cap. + Butt, + /// Square cap with dimensions equal to half the stroke width. + Square, + /// Rounded cap with radius equal to half the stroke width. + Round, +} + +/// Describes the visual style of a stroke. +#[derive(Copy, Clone, Debug)] +pub struct Stroke +where + D: Borrow<[f32]>, +{ + /// Width of the stroke. + pub width: f32, + /// Style for connecting segments of the stroke. + pub join: Join, + /// Limit for miter joins. + pub miter_limit: f32, + /// Style for capping the beginning of an open subpath. + pub start_cap: Cap, + /// Style for capping the end of an open subpath. + pub end_cap: Cap, + /// Lengths of dashes in alternating on/off order. + pub dash_pattern: D, + /// Offset of the first dash. + pub dash_offset: f32, + /// True if the stroke width should be affected by the scale of a + /// transform. + pub scale: bool, +}