Merge pull request #190 from dfrg/api2

Remove piet API and replace with scene fragments
This commit is contained in:
Chad Brokaw 2022-10-19 16:02:21 -04:00 committed by GitHub
commit 4edea5fbc9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1189 additions and 2817 deletions

451
Cargo.lock generated
View file

@ -17,12 +17,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.5.2" version = "0.5.2"
@ -86,9 +80,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.11.0" version = "3.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
@ -193,15 +187,6 @@ dependencies = [
"os_str_bytes", "os_str_bytes",
] ]
[[package]]
name = "cmake"
version = "0.1.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "cocoa" name = "cocoa"
version = "0.24.0" version = "0.24.0"
@ -213,7 +198,7 @@ dependencies = [
"cocoa-foundation", "cocoa-foundation",
"core-foundation", "core-foundation",
"core-graphics", "core-graphics",
"foreign-types 0.3.2", "foreign-types",
"libc", "libc",
"objc", "objc",
] ]
@ -228,7 +213,7 @@ dependencies = [
"block", "block",
"core-foundation", "core-foundation",
"core-graphics-types", "core-graphics-types",
"foreign-types 0.3.2", "foreign-types",
"libc", "libc",
"objc", "objc",
] ]
@ -258,7 +243,7 @@ dependencies = [
"bitflags", "bitflags",
"core-foundation", "core-foundation",
"core-graphics-types", "core-graphics-types",
"foreign-types 0.3.2", "foreign-types",
"libc", "libc",
] ]
@ -270,19 +255,7 @@ checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"core-foundation", "core-foundation",
"foreign-types 0.3.2", "foreign-types",
"libc",
]
[[package]]
name = "core-text"
version = "19.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25"
dependencies = [
"core-foundation",
"core-graphics",
"foreign-types 0.3.2",
"libc", "libc",
] ]
@ -295,29 +268,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "crossfont"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f66b1c1979c4362323f03ab6bf7fb522902bfc418e0c37319ab347f9561d980f"
dependencies = [
"cocoa",
"core-foundation",
"core-foundation-sys",
"core-graphics",
"core-text",
"dwrote",
"foreign-types 0.5.0",
"freetype-rs",
"libc",
"log",
"objc",
"once_cell",
"pkg-config",
"servo-fontconfig",
"winapi",
]
[[package]] [[package]]
name = "cty" name = "cty"
version = "0.2.2" version = "0.2.2"
@ -415,30 +365,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dwrote"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b"
dependencies = [
"lazy_static",
"libc",
"serde",
"serde_derive",
"winapi",
"wio",
]
[[package]]
name = "expat-sys"
version = "2.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa"
dependencies = [
"cmake",
"pkg-config",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.8.0" version = "1.8.0"
@ -470,28 +396,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [ dependencies = [
"foreign-types-shared 0.1.1", "foreign-types-shared",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
"foreign-types-shared 0.3.1",
]
[[package]]
name = "foreign-types-macros"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8469d0d40519bc608ec6863f1cc88f3f1deee15913f2f3b3e573d81ed38cccc"
dependencies = [
"proc-macro2",
"quote",
"syn",
] ]
[[package]] [[package]]
@ -500,34 +405,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "foreign-types-shared"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
name = "freetype-rs"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74eadec9d0a5c28c54bb9882e54787275152a4e36ce206b45d7451384e5bf5fb"
dependencies = [
"bitflags",
"freetype-sys",
"libc",
]
[[package]]
name = "freetype-sys"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a"
dependencies = [
"cmake",
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.1.16" version = "0.1.16"
@ -610,9 +487,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
[[package]] [[package]]
name = "jni-sys" name = "jni-sys"
@ -655,9 +532,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.133" version = "0.2.135"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -697,12 +574,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -727,20 +598,6 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "metal"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c12e48c737ee9a55e8bb2352bcde588f79ae308d3529ee888f7cc0f469b5777"
dependencies = [
"bitflags",
"block",
"cocoa-foundation",
"foreign-types 0.3.2",
"log",
"objc",
]
[[package]] [[package]]
name = "metal" name = "metal"
version = "0.24.0" version = "0.24.0"
@ -750,7 +607,7 @@ dependencies = [
"bitflags", "bitflags",
"block", "block",
"core-graphics-types", "core-graphics-types",
"foreign-types 0.3.2", "foreign-types",
"log", "log",
"objc", "objc",
] ]
@ -779,7 +636,7 @@ dependencies = [
"libc", "libc",
"log", "log",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys", "windows-sys 0.36.1",
] ]
[[package]] [[package]]
@ -979,15 +836,15 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.3" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-sys", "windows-sys 0.42.0",
] ]
[[package]] [[package]]
@ -1002,41 +859,31 @@ version = "0.1.0"
dependencies = [ dependencies = [
"cbindgen", "cbindgen",
"cocoa", "cocoa",
"metal 0.22.0", "metal",
"objc", "objc",
"piet-gpu", "piet-gpu",
"piet-gpu-hal", "piet-gpu-hal",
"piet-scene", "piet-scene",
] ]
[[package]]
name = "piet"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00543608fb5ee6063f5ff1259246ae23073c1a5e413e643d0469da3d4b7b4de"
dependencies = [
"kurbo 0.7.1",
"unic-bidi",
]
[[package]] [[package]]
name = "piet-gpu" name = "piet-gpu"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"clap 3.2.22", "clap 3.2.22",
"kurbo 0.8.3",
"ndk 0.3.0", "ndk 0.3.0",
"ndk-glue 0.3.0", "ndk-glue 0.3.0",
"ndk-sys 0.2.2", "ndk-sys 0.2.2",
"piet",
"piet-gpu-hal", "piet-gpu-hal",
"piet-gpu-types", "piet-gpu-types",
"piet-scene",
"png", "png",
"rand 0.8.5", "rand 0.8.5",
"raw-window-handle 0.3.4", "raw-window-handle 0.3.4",
"raw-window-handle 0.5.0", "raw-window-handle 0.5.0",
"roxmltree", "roxmltree",
"swash",
"winit", "winit",
] ]
@ -1060,8 +907,8 @@ dependencies = [
"bytemuck", "bytemuck",
"cocoa-foundation", "cocoa-foundation",
"core-graphics-types", "core-graphics-types",
"foreign-types 0.3.2", "foreign-types",
"metal 0.24.0", "metal",
"objc", "objc",
"raw-window-handle 0.5.0", "raw-window-handle 0.5.0",
"smallvec", "smallvec",
@ -1151,9 +998,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.43" version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1311,15 +1158,6 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "safe_arch"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.0" version = "1.0.0"
@ -1332,32 +1170,20 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sctk-adwaita"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04b7c47a572f73de28bee5b5060d085b42b6ce1e4ee2b49c956ea7b25e94b6f0"
dependencies = [
"crossfont",
"log",
"smithay-client-toolkit",
"tiny-skia",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.144" version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.144" version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1366,36 +1192,15 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.85" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde",
] ]
[[package]]
name = "servo-fontconfig"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c"
dependencies = [
"libc",
"servo-fontconfig-sys",
]
[[package]]
name = "servo-fontconfig-sys"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388"
dependencies = [
"expat-sys",
"freetype-sys",
"pkg-config",
]
[[package]] [[package]]
name = "slotmap" name = "slotmap"
version = "1.0.6" version = "1.0.6"
@ -1407,9 +1212,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]] [[package]]
name = "smithay-client-toolkit" name = "smithay-client-toolkit"
@ -1448,21 +1253,11 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "swash"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1bdb2004b76b5f5f3afe722d70b1aea160d2362cb7e40716a7106197ee7f82d"
dependencies = [
"yazi",
"zeno",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.100" version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1509,49 +1304,24 @@ checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.35" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.35" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "tiny-skia"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "642680569bb895b16e4b9d181c60be1ed136fa0c9c7f11d004daf053ba89bf82"
dependencies = [
"arrayref",
"arrayvec 0.5.2",
"bytemuck",
"cfg-if",
"png",
"safe_arch",
"tiny-skia-path",
]
[[package]]
name = "tiny-skia-path"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c114d32f0c2ee43d585367cb013dfaba967ab9f62b90d9af0d696e955e70fa6c"
dependencies = [
"arrayref",
"bytemuck",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.5.9" version = "0.5.9"
@ -1561,62 +1331,11 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "unic-bidi"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1356b759fb6a82050666f11dce4b6fe3571781f1449f3ef78074e408d468ec09"
dependencies = [
"matches",
"unic-ucd-bidi",
]
[[package]]
name = "unic-char-property"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
dependencies = [
"unic-char-range",
]
[[package]]
name = "unic-char-range"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[package]]
name = "unic-common"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-ucd-bidi"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c"
dependencies = [
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-version"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
dependencies = [
"unic-common",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.4" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
@ -1828,37 +1547,88 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [ dependencies = [
"windows_aarch64_msvc", "windows_aarch64_msvc 0.36.1",
"windows_i686_gnu", "windows_i686_gnu 0.36.1",
"windows_i686_msvc", "windows_i686_msvc 0.36.1",
"windows_x86_64_gnu", "windows_x86_64_gnu 0.36.1",
"windows_x86_64_msvc", "windows_x86_64_msvc 0.36.1",
] ]
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.42.0",
"windows_i686_gnu 0.42.0",
"windows_i686_msvc 0.42.0",
"windows_x86_64_gnu 0.42.0",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.42.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.36.1" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.36.1" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.36.1" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.36.1" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.36.1" version = "0.36.1"
@ -1866,10 +1636,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]] [[package]]
name = "winit" name = "windows_x86_64_msvc"
version = "0.27.3" version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a22e94ba35ca3ff11820044bfa0dc48b95a3a15569c0068555566a12ef41c9e5" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "winit"
version = "0.27.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37f64802920c4c35d12a53dad5e0c55bbc3004d8dc4f2e4dd64ad02c5665d7aa"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cocoa", "cocoa",
@ -1888,13 +1664,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
"raw-window-handle 0.4.3", "raw-window-handle 0.4.3",
"raw-window-handle 0.5.0", "raw-window-handle 0.5.0",
"sctk-adwaita",
"smithay-client-toolkit", "smithay-client-toolkit",
"wasm-bindgen", "wasm-bindgen",
"wayland-client", "wayland-client",
"wayland-protocols", "wayland-protocols",
"web-sys", "web-sys",
"windows-sys", "windows-sys 0.36.1",
"x11-dl", "x11-dl",
] ]
@ -1935,18 +1710,6 @@ checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
[[package]] [[package]]
name = "xmlparser" name = "xmlparser"
version = "0.13.3" version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd"
[[package]]
name = "yazi"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03b3e19c937b5b9bd8e52b1c88f30cce5c0d33d676cf174866175bb794ff658"
[[package]]
name = "zeno"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c110ba09c9b3a43edd4803d570df0da2414fed6e822e22b976a4e3ef50860701"

View file

@ -14,9 +14,9 @@ piet-gpu-hal = { path = "../piet-gpu-hal" }
piet-scene = { path = "../piet-scene" } piet-scene = { path = "../piet-scene" }
[target.'cfg(all(not(target_arch = "wasm32"), any(target_os = "ios", target_os = "macos")))'.dependencies] [target.'cfg(all(not(target_arch = "wasm32"), any(target_os = "ios", target_os = "macos")))'.dependencies]
metal = "0.22" metal = "0.24"
objc = "0.2.7"
cocoa = "0.24.0" cocoa = "0.24.0"
objc = "0.2.5"
[build-dependencies] [build-dependencies]
cbindgen = "0.20.0" cbindgen = "0.20.0"

View file

@ -26,9 +26,7 @@
mod render; mod render;
use piet_scene::brush::{Brush, Color}; use piet_scene::{Brush, Color, Fill, PathElement};
use piet_scene::path::Element;
use piet_scene::scene::Fill;
use render::*; use render::*;
use std::ffi::c_void; use std::ffi::c_void;
use std::mem::transmute; use std::mem::transmute;
@ -199,7 +197,7 @@ pub struct PgpuTransform {
pub dy: f32, pub dy: f32,
} }
impl From<PgpuTransform> for PgpuAffine { impl From<PgpuTransform> for piet_scene::Affine {
fn from(xform: PgpuTransform) -> Self { fn from(xform: PgpuTransform) -> Self {
Self { Self {
xx: xform.xx, xx: xform.xx,
@ -212,8 +210,6 @@ impl From<PgpuTransform> for PgpuAffine {
} }
} }
pub type PgpuAffine = piet_scene::geometry::Affine;
/// Creates a new builder for filling a piet-gpu scene. The specified scene /// Creates a new builder for filling a piet-gpu scene. The specified scene
/// should not be accessed while the builder is live. /// should not be accessed while the builder is live.
#[no_mangle] #[no_mangle]
@ -243,7 +239,7 @@ pub unsafe extern "C" fn pgpu_scene_builder_add_glyph(
} }
impl Iterator for PgpuPathIter { impl Iterator for PgpuPathIter {
type Item = piet_scene::path::Element; type Item = PathElement;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut el = PgpuPathElement { let mut el = PgpuPathElement {
@ -253,17 +249,17 @@ impl Iterator for PgpuPathIter {
if (self.next_element)(self.context, &mut el as _) { if (self.next_element)(self.context, &mut el as _) {
let p = &el.points; let p = &el.points;
Some(match el.verb { Some(match el.verb {
PgpuPathVerb::MoveTo => Element::MoveTo((p[0].x, p[0].y).into()), PgpuPathVerb::MoveTo => PathElement::MoveTo((p[0].x, p[0].y).into()),
PgpuPathVerb::LineTo => Element::LineTo((p[0].x, p[0].y).into()), PgpuPathVerb::LineTo => PathElement::LineTo((p[0].x, p[0].y).into()),
PgpuPathVerb::QuadTo => { PgpuPathVerb::QuadTo => {
Element::QuadTo((p[0].x, p[0].y).into(), (p[1].x, p[1].y).into()) PathElement::QuadTo((p[0].x, p[0].y).into(), (p[1].x, p[1].y).into())
} }
PgpuPathVerb::CurveTo => Element::CurveTo( PgpuPathVerb::CurveTo => PathElement::CurveTo(
(p[0].x, p[0].y).into(), (p[0].x, p[0].y).into(),
(p[1].x, p[1].y).into(), (p[1].x, p[1].y).into(),
(p[2].x, p[2].y).into(), (p[2].x, p[2].y).into(),
), ),
PgpuPathVerb::Close => Element::Close, PgpuPathVerb::Close => PathElement::Close,
}) })
} else { } else {
None None
@ -277,8 +273,8 @@ pub unsafe extern "C" fn pgpu_scene_builder_transform(
builder: *mut PgpuSceneBuilder<'static>, builder: *mut PgpuSceneBuilder<'static>,
transform: *const PgpuTransform, transform: *const PgpuTransform,
) { ) {
if !transform.is_null() { if let Some(transform) = transform.as_ref() {
(*builder).0.transform((*transform).into()) (*builder).transform = (*transform).into();
} }
} }
@ -312,9 +308,13 @@ pub unsafe extern "C" fn pgpu_scene_builder_fill_path(
} else { } else {
Some((*brush_transform).into()) Some((*brush_transform).into())
}; };
(*builder) (*builder).builder.fill(
.0 fill,
.fill(fill, &brush, brush_transform, (*path).clone()); (*builder).transform,
&brush,
brush_transform,
(*path).clone(),
);
} }
/// Appends a scene fragment to the underlying scene or fragment. The /// Appends a scene fragment to the underlying scene or fragment. The
@ -333,7 +333,7 @@ pub unsafe extern "C" fn pgpu_scene_builder_append_fragment(
} else { } else {
Some((*transform).into()) Some((*transform).into())
}; };
(*builder).0.append(&(*fragment).0, transform); (*builder).builder.append(&(*fragment).0, transform);
} }
/// Finalizes the scene builder, making the underlying scene ready for /// Finalizes the scene builder, making the underlying scene ready for
@ -445,7 +445,7 @@ pub unsafe extern "C" fn pgpu_glyph_bbox(
glyph: *const PgpuGlyph, glyph: *const PgpuGlyph,
transform: &[f32; 6], transform: &[f32; 6],
) -> PgpuRect { ) -> PgpuRect {
let transform = piet_scene::geometry::Affine::new(transform); let transform = piet_scene::Affine::new(transform);
let rect = (*glyph).bbox(Some(transform)); let rect = (*glyph).bbox(Some(transform));
PgpuRect { PgpuRect {
x0: rect.min.x, x0: rect.min.x,

View file

@ -14,13 +14,11 @@
// //
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
use piet_gpu::{EncodedSceneRef, PixelFormat, RenderConfig}; use piet_gpu::{PixelFormat, RenderConfig};
use piet_gpu_hal::{QueryPool, Session}; use piet_gpu_hal::{QueryPool, Session};
use piet_scene::geometry::{Affine, Rect};
use piet_scene::glyph::pinot::{types::Tag, FontDataRef}; use piet_scene::glyph::pinot::{types::Tag, FontDataRef};
use piet_scene::glyph::{GlyphContext, GlyphProvider}; use piet_scene::glyph::{GlyphContext, GlyphProvider};
use piet_scene::resource::ResourceContext; use piet_scene::{Affine, Rect, Scene, SceneFragment};
use piet_scene::scene::{Fragment, Scene};
/// State and resources for rendering a scene. /// State and resources for rendering a scene.
pub struct PgpuRenderer { pub struct PgpuRenderer {
@ -89,7 +87,7 @@ impl PgpuRenderer {
.session .session
.image_from_raw_mtl(target, self.width, self.height); .image_from_raw_mtl(target, self.width, self.height);
if let Some(renderer) = &mut self.pgpu_renderer { if let Some(renderer) = &mut self.pgpu_renderer {
renderer.upload_scene(&scene.encoded_scene(), 0).unwrap(); renderer.upload_scene(&scene.0, 0).unwrap();
renderer.record(&mut cmd_buf, &self.query_pool, 0); renderer.record(&mut cmd_buf, &self.query_pool, 0);
// TODO later: we can bind the destination image and avoid the copy. // TODO later: we can bind the destination image and avoid the copy.
cmd_buf.blit_image(&renderer.image_dev, &dst_image); cmd_buf.blit_image(&renderer.image_dev, &dst_image);
@ -105,67 +103,50 @@ impl PgpuRenderer {
} }
/// Encoded streams and resources describing a vector graphics scene. /// Encoded streams and resources describing a vector graphics scene.
pub struct PgpuScene { pub struct PgpuScene(pub Scene);
scene: Scene,
rcx: ResourceContext,
}
impl PgpuScene { impl PgpuScene {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self(Scene::default())
scene: Scene::default(),
rcx: ResourceContext::new(),
}
} }
pub fn builder(&mut self) -> PgpuSceneBuilder { pub fn builder(&mut self) -> PgpuSceneBuilder {
self.rcx.advance(); PgpuSceneBuilder {
PgpuSceneBuilder(piet_scene::scene::build_scene( builder: piet_scene::SceneBuilder::for_scene(&mut self.0),
&mut self.scene, transform: Affine::IDENTITY,
&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(),
} }
} }
} }
/// Encoded streams and resources describing a vector graphics scene fragment. /// Encoded streams and resources describing a vector graphics scene fragment.
pub struct PgpuSceneFragment(pub Fragment); pub struct PgpuSceneFragment(pub SceneFragment);
impl PgpuSceneFragment { impl PgpuSceneFragment {
pub fn new() -> Self { pub fn new() -> Self {
Self(Fragment::default()) Self(SceneFragment::default())
} }
pub fn builder(&mut self) -> PgpuSceneBuilder { pub fn builder(&mut self) -> PgpuSceneBuilder {
PgpuSceneBuilder(piet_scene::scene::build_fragment(&mut self.0)) PgpuSceneBuilder {
builder: piet_scene::SceneBuilder::for_fragment(&mut self.0),
transform: Affine::IDENTITY,
}
} }
} }
/// Builder for constructing an encoded scene. /// Builder for constructing an encoded scene.
pub struct PgpuSceneBuilder<'a>(pub piet_scene::scene::Builder<'a>); pub struct PgpuSceneBuilder<'a> {
pub builder: piet_scene::SceneBuilder<'a>,
pub transform: Affine,
}
impl<'a> PgpuSceneBuilder<'a> { impl<'a> PgpuSceneBuilder<'a> {
pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::geometry::Affine) { pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::Affine) {
self.0.append(&glyph.fragment, Some(*transform)); self.builder.append(&glyph.fragment, Some(*transform));
} }
pub fn finish(self) { pub fn finish(self) {
self.0.finish(); self.builder.finish();
} }
} }
@ -216,7 +197,7 @@ pub struct PgpuGlyphProvider<'a>(GlyphProvider<'a>);
impl<'a> PgpuGlyphProvider<'a> { impl<'a> PgpuGlyphProvider<'a> {
pub fn get(&mut self, gid: u16) -> Option<PgpuGlyph> { pub fn get(&mut self, gid: u16) -> Option<PgpuGlyph> {
let fragment = self.0.get(gid)?; let fragment = self.0.get(gid, None)?;
Some(PgpuGlyph { fragment }) Some(PgpuGlyph { fragment })
} }
@ -228,7 +209,7 @@ impl<'a> PgpuGlyphProvider<'a> {
/// Encoded (possibly color) outline for a glyph. /// Encoded (possibly color) outline for a glyph.
pub struct PgpuGlyph { pub struct PgpuGlyph {
fragment: Fragment, fragment: SceneFragment,
} }
impl PgpuGlyph { impl PgpuGlyph {

View file

@ -331,7 +331,7 @@ impl crate::backend::Device for Dx12Device {
) -> Result<Self::Image, Error> { ) -> Result<Self::Image, Error> {
let format = match format { let format = match format {
ImageFormat::A8 => winapi::shared::dxgiformat::DXGI_FORMAT_R8_UNORM, ImageFormat::A8 => winapi::shared::dxgiformat::DXGI_FORMAT_R8_UNORM,
ImageFormat::Rgba8 => winapi::shared::dxgiformat::DXGI_FORMAT_R8G8B8A8_UNORM, ImageFormat::Rgba8 | ImageFormat::Surface => winapi::shared::dxgiformat::DXGI_FORMAT_R8G8B8A8_UNORM,
}; };
let resource = self let resource = self
.device .device

View file

@ -98,6 +98,8 @@ pub enum ImageFormat {
A8, A8,
// 8 bit per pixel RGBA // 8 bit per pixel RGBA
Rgba8, Rgba8,
// Format that matches the target surface
Surface,
} }
bitflags! { bitflags! {

View file

@ -351,7 +351,8 @@ impl crate::backend::Device for MtlDevice {
//desc.set_mipmap_level_count(1); //desc.set_mipmap_level_count(1);
let mtl_format = match format { let mtl_format = match format {
ImageFormat::A8 => metal::MTLPixelFormat::R8Unorm, ImageFormat::A8 => metal::MTLPixelFormat::R8Unorm,
ImageFormat::Rgba8 => metal::MTLPixelFormat::BGRA8Unorm, ImageFormat::Rgba8 => metal::MTLPixelFormat::RGBA8Unorm,
ImageFormat::Surface => metal::MTLPixelFormat::BGRA8Unorm,
}; };
desc.set_pixel_format(mtl_format); desc.set_pixel_format(mtl_format);
desc.set_usage(metal::MTLTextureUsage::ShaderRead | metal::MTLTextureUsage::ShaderWrite); desc.set_usage(metal::MTLTextureUsage::ShaderRead | metal::MTLTextureUsage::ShaderWrite);

View file

@ -588,7 +588,7 @@ impl crate::backend::Device for VkDevice {
| vk::ImageUsageFlags::TRANSFER_DST; | vk::ImageUsageFlags::TRANSFER_DST;
let vk_format = match format { let vk_format = match format {
ImageFormat::A8 => vk::Format::R8_UNORM, ImageFormat::A8 => vk::Format::R8_UNORM,
ImageFormat::Rgba8 => vk::Format::R8G8B8A8_UNORM, ImageFormat::Rgba8 | ImageFormat::Surface => vk::Format::R8G8B8A8_UNORM,
}; };
let image = device.create_image( let image = device.create_image(
&vk::ImageCreateInfo::builder() &vk::ImageCreateInfo::builder()

View file

@ -26,16 +26,19 @@ path = "../piet-gpu-hal"
[dependencies.piet-gpu-types] [dependencies.piet-gpu-types]
path = "../piet-gpu-types" path = "../piet-gpu-types"
[dependencies.piet-scene]
path = "../piet-scene"
features = ["kurbo"]
[dependencies] [dependencies]
piet = "0.2.0"
png = "0.17.6" png = "0.17.6"
rand = "0.8.5" rand = "0.8.5"
roxmltree = "0.13" roxmltree = "0.13"
winit = {version = "0.27.3", default-features = false, features = ["x11", "wayland", "wayland-dlopen"]} winit = {version = "0.27.3", default-features = false, features = ["x11", "wayland", "wayland-dlopen"]}
raw-window-handle = "0.5" raw-window-handle = "0.5"
clap = "3.2.22" clap = "3.2.22"
swash = "0.1.4"
bytemuck = { version = "1.7.2", features = ["derive"] } bytemuck = { version = "1.7.2", features = ["derive"] }
kurbo = "0.8.3"
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
ndk = "0.3" ndk = "0.3"

View file

@ -16,10 +16,8 @@ use piet_gpu_hal::{
Error, ImageLayout, Instance, InstanceFlags, Semaphore, Session, Surface, Swapchain, Error, ImageLayout, Instance, InstanceFlags, Semaphore, Session, Surface, Swapchain,
}; };
use piet::kurbo::Point; use piet_gpu::{samples, RenderDriver, Renderer, SimpleText};
use piet::{RenderContext, Text, TextAttribute, TextLayoutBuilder}; use piet_scene::{Scene, SceneBuilder};
use piet_gpu::{test_scenes, PietGpuRenderContext, RenderDriver, Renderer};
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] #[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
fn main() { fn main() {
@ -115,14 +113,14 @@ impl GfxState {
info_string = stats.short_summary(); info_string = stats.short_summary();
println!("{}", info_string); println!("{}", info_string);
} }
let mut ctx = PietGpuRenderContext::new(); let mut text = SimpleText::new();
test_scenes::render_anim_frame(&mut ctx, self.current_frame); let mut scene = Scene::default();
//test_scenes::render_tiger(&mut ctx); let mut builder = SceneBuilder::for_scene(&mut scene);
render_info_string(&mut ctx, &info_string); samples::render_anim_frame(&mut builder, self.current_frame);
if let Err(e) = self //samples::render_tiger(&mut builder, false);
.render_driver render_info(&mut text, &mut builder, &info_string);
.upload_render_ctx(&self.session, &mut ctx) builder.finish();
{ if let Err(e) = self.render_driver.upload_scene(&self.session, &scene) {
println!("error in uploading: {}", e); println!("error in uploading: {}", e);
} }
let (image_idx, acquisition_semaphore) = self.swapchain.next().unwrap(); let (image_idx, acquisition_semaphore) = self.swapchain.next().unwrap();
@ -154,12 +152,13 @@ impl GfxState {
} }
} }
fn render_info_string(rc: &mut impl RenderContext, info: &str) { fn render_info(simple_text: &mut SimpleText, sb: &mut SceneBuilder, info: &str) {
let layout = rc simple_text.add(
.text() sb,
.new_text_layout(info.to_string()) None,
.default_attribute(TextAttribute::FontSize(60.0)) 60.0,
.build() None,
.unwrap(); piet_scene::Affine::translate(110.0, 120.0),
rc.draw_text(&layout, Point::new(110.0, 120.0)); info,
);
} }

View file

@ -6,7 +6,8 @@ use clap::{App, Arg};
use piet_gpu_hal::{BufferUsage, Error, Instance, InstanceFlags, Session}; use piet_gpu_hal::{BufferUsage, Error, Instance, InstanceFlags, Session};
use piet_gpu::{test_scenes, PicoSvg, PietGpuRenderContext, RenderDriver, Renderer}; use piet_gpu::{samples, PicoSvg, RenderDriver, Renderer};
use piet_scene::{Scene, SceneBuilder};
const WIDTH: usize = 2048; const WIDTH: usize = 2048;
const HEIGHT: usize = 1536; const HEIGHT: usize = 1536;
@ -227,11 +228,11 @@ fn main() -> Result<(), Error> {
) )
.get_matches(); .get_matches();
let instance = Instance::new(InstanceFlags::default())?; let instance = Instance::new(InstanceFlags::default())?;
let mut scene = Scene::default();
unsafe { unsafe {
let device = instance.device()?; let device = instance.device()?;
let session = Session::new(device); let session = Session::new(device);
let mut builder = SceneBuilder::for_scene(&mut scene);
let mut ctx = PietGpuRenderContext::new();
if let Some(input) = matches.value_of("INPUT") { if let Some(input) = matches.value_of("INPUT") {
let mut scale = matches let mut scale = matches
.value_of("scale") .value_of("scale")
@ -244,16 +245,17 @@ fn main() -> Result<(), Error> {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
let svg = PicoSvg::load(&xml_str, scale).unwrap(); let svg = PicoSvg::load(&xml_str, scale).unwrap();
println!("parsing time: {:?}", start.elapsed()); println!("parsing time: {:?}", start.elapsed());
test_scenes::render_svg(&mut ctx, &svg); samples::render_svg(&mut builder, &svg, true);
} else { } else {
//test_scenes::render_scene(&mut ctx); //test_scenes::render_scene(&mut ctx);
test_scenes::render_blend_grid(&mut ctx); samples::render_blend_grid(&mut builder);
} }
builder.finish();
let renderer = Renderer::new(&session, WIDTH, HEIGHT, 1)?; let renderer = Renderer::new(&session, WIDTH, HEIGHT, 1)?;
let mut render_driver = RenderDriver::new(&session, 1, renderer); let mut render_driver = RenderDriver::new(&session, 1, renderer);
let start = std::time::Instant::now(); let start = std::time::Instant::now();
render_driver.upload_render_ctx(&session, &mut ctx)?; render_driver.upload_scene(&session, &scene)?;
let image_usage = BufferUsage::MAP_READ | BufferUsage::COPY_DST; let image_usage = BufferUsage::MAP_READ | BufferUsage::COPY_DST;
let image_buf = session.create_buffer((WIDTH * HEIGHT * 4) as u64, image_usage)?; let image_buf = session.create_buffer((WIDTH * HEIGHT * 4) as u64, image_usage)?;

View file

@ -1,8 +1,6 @@
use piet::kurbo::Point; use piet_gpu::{samples, PicoSvg, RenderDriver, Renderer, SimpleText};
use piet::{RenderContext, Text, TextAttribute, TextLayoutBuilder};
use piet_gpu_hal::{Error, ImageLayout, Instance, InstanceFlags, Session}; use piet_gpu_hal::{Error, ImageLayout, Instance, InstanceFlags, Session};
use piet_scene::{Scene, SceneBuilder};
use piet_gpu::{test_scenes, PicoSvg, PietGpuRenderContext, RenderDriver, Renderer};
use clap::{App, Arg}; use clap::{App, Arg};
@ -61,6 +59,8 @@ fn main() -> Result<(), Error> {
let instance = Instance::new(InstanceFlags::default())?; let instance = Instance::new(InstanceFlags::default())?;
let mut info_string = "info".to_string(); let mut info_string = "info".to_string();
let mut scene = Scene::default();
let mut simple_text = piet_gpu::SimpleText::new();
unsafe { unsafe {
let display_handle = window.raw_display_handle(); let display_handle = window.raw_display_handle();
let window_handle = window.raw_window_handle(); let window_handle = window.raw_window_handle();
@ -76,7 +76,7 @@ fn main() -> Result<(), Error> {
let renderer = Renderer::new(&session, WIDTH, HEIGHT, NUM_FRAMES)?; let renderer = Renderer::new(&session, WIDTH, HEIGHT, NUM_FRAMES)?;
let mut render_driver = RenderDriver::new(&session, NUM_FRAMES, renderer); let mut render_driver = RenderDriver::new(&session, NUM_FRAMES, renderer);
let mut mode = 0usize; let mut sample_index = 0usize;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll; // `ControlFlow::Wait` if only re-render on event *control_flow = ControlFlow::Poll; // `ControlFlow::Wait` if only re-render on event
@ -91,8 +91,12 @@ fn main() -> Result<(), Error> {
WindowEvent::KeyboardInput { input, .. } => { WindowEvent::KeyboardInput { input, .. } => {
if input.state == ElementState::Pressed { if input.state == ElementState::Pressed {
match input.virtual_keycode { match input.virtual_keycode {
Some(VirtualKeyCode::Left) => mode = mode.wrapping_sub(1), Some(VirtualKeyCode::Left) => {
Some(VirtualKeyCode::Right) => mode = mode.wrapping_add(1), sample_index = sample_index.saturating_sub(1)
}
Some(VirtualKeyCode::Right) => {
sample_index = sample_index.saturating_add(1)
}
_ => {} _ => {}
} }
} }
@ -111,52 +115,34 @@ fn main() -> Result<(), Error> {
info_string = stats.short_summary(); info_string = stats.short_summary();
} }
let mut ctx = PietGpuRenderContext::new();
let test_blend = false;
if let Some(svg) = &svg { if let Some(svg) = &svg {
test_scenes::render_svg(&mut ctx, svg); let mut builder = SceneBuilder::for_scene(&mut scene);
} else if test_blend { samples::render_svg(&mut builder, svg, false);
use piet_gpu::{Blend, BlendMode::*, CompositionMode::*}; render_info(&mut simple_text, &mut builder, &info_string);
let blends = [ builder.finish();
Blend::new(Normal, SrcOver), if let Err(e) = render_driver.upload_scene(&session, &scene) {
Blend::new(Multiply, SrcOver), println!("error in uploading: {}", e);
Blend::new(Screen, SrcOver), }
Blend::new(Overlay, SrcOver),
Blend::new(Darken, SrcOver),
Blend::new(Lighten, SrcOver),
Blend::new(ColorDodge, SrcOver),
Blend::new(ColorBurn, SrcOver),
Blend::new(HardLight, SrcOver),
Blend::new(SoftLight, SrcOver),
Blend::new(Difference, SrcOver),
Blend::new(Exclusion, SrcOver),
Blend::new(Hue, SrcOver),
Blend::new(Saturation, SrcOver),
Blend::new(Color, SrcOver),
Blend::new(Luminosity, SrcOver),
Blend::new(Normal, Clear),
Blend::new(Normal, Copy),
Blend::new(Normal, Dest),
Blend::new(Normal, SrcOver),
Blend::new(Normal, DestOver),
Blend::new(Normal, SrcIn),
Blend::new(Normal, DestIn),
Blend::new(Normal, SrcOut),
Blend::new(Normal, DestOut),
Blend::new(Normal, SrcAtop),
Blend::new(Normal, DestAtop),
Blend::new(Normal, Xor),
Blend::new(Normal, Plus),
];
let blend = blends[mode % blends.len()];
test_scenes::render_blend_test(&mut ctx, current_frame, blend);
info_string = format!("{:?}", blend);
} else { } else {
test_scenes::render_anim_frame(&mut ctx, current_frame); let mut builder = SceneBuilder::for_scene(&mut scene);
}
render_info_string(&mut ctx, &info_string); const N_SAMPLES: usize = 5;
if let Err(e) = render_driver.upload_render_ctx(&session, &mut ctx) { match sample_index % N_SAMPLES {
println!("error in uploading: {}", e); 0 => samples::render_anim_frame(
&mut builder,
&mut simple_text,
current_frame,
),
1 => samples::render_blend_grid(&mut builder),
2 => samples::render_tiger(&mut builder, false),
3 => samples::render_brush_transform(&mut builder, current_frame),
_ => samples::render_scene(&mut builder),
}
render_info(&mut simple_text, &mut builder, &info_string);
builder.finish();
if let Err(e) = render_driver.upload_scene(&session, &scene) {
println!("error in uploading: {}", e);
}
} }
let (image_idx, acquisition_semaphore) = swapchain.next().unwrap(); let (image_idx, acquisition_semaphore) = swapchain.next().unwrap();
@ -197,12 +183,13 @@ fn main() -> Result<(), Error> {
} }
} }
fn render_info_string(rc: &mut impl RenderContext, info: &str) { fn render_info(simple_text: &mut SimpleText, sb: &mut SceneBuilder, info: &str) {
let layout = rc simple_text.add(
.text() sb,
.new_text_layout(info.to_string()) None,
.default_attribute(TextAttribute::FontSize(40.0)) 40.0,
.build() None,
.unwrap(); piet_scene::Affine::translate(110.0, 50.0),
rc.draw_text(&layout, Point::new(110.0, 50.0)); info,
);
} }

View file

@ -1,103 +0,0 @@
// 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, Debug)]
#[repr(C)]
pub enum BlendMode {
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,
// Clip is the same as normal, but doesn't always push a blend group.
Clip = 128,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(C)]
pub enum CompositionMode {
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,
PlusLighter = 13,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Blend {
pub mode: BlendMode,
pub composition_mode: CompositionMode,
}
impl Blend {
pub fn new(mode: BlendMode, composition_mode: CompositionMode) -> Self {
Self {
mode,
composition_mode,
}
}
pub(crate) fn pack(&self) -> u32 {
(self.mode as u32) << 8 | self.composition_mode as u32
}
}
impl Default for Blend {
fn default() -> Self {
Self {
mode: BlendMode::Clip,
composition_mode: CompositionMode::SrcOver,
}
}
}
impl From<BlendMode> for Blend {
fn from(mode: BlendMode) -> Self {
Self {
mode,
composition_mode: CompositionMode::SrcOver,
}
}
}
impl From<CompositionMode> for Blend {
fn from(mode: CompositionMode) -> Self {
Self {
mode: BlendMode::Normal,
composition_mode: mode,
}
}
}

View file

@ -1,301 +0,0 @@
// Copyright 2021 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.
//! Low-level scene encoding.
use crate::{Blend, SceneStats, DRAWTAG_SIZE, TRANSFORM_SIZE};
use bytemuck::{Pod, Zeroable};
use piet_gpu_hal::BufWrite;
use crate::stages::{self, PathEncoder, Transform, DRAW_PART_SIZE, PATHSEG_PART_SIZE};
pub struct Encoder {
transform_stream: Vec<stages::Transform>,
tag_stream: Vec<u8>,
pathseg_stream: Vec<u8>,
linewidth_stream: Vec<f32>,
drawtag_stream: Vec<u32>,
drawdata_stream: Vec<u8>,
n_path: u32,
n_pathseg: u32,
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> {
pub(crate) fn stats(&self) -> SceneStats {
SceneStats {
n_drawobj: self.drawtag_stream.len(),
drawdata_len: self.drawdata_stream.len(),
n_transform: self.transform_stream.len(),
linewidth_len: std::mem::size_of_val(self.linewidth_stream),
pathseg_len: self.pathseg_stream.len(),
n_pathtag: self.tag_stream.len(),
n_path: self.n_path,
n_pathseg: self.n_pathseg,
n_clip: self.n_clip,
}
}
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);
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);
}
}
/// A scene fragment encoding a glyph.
///
/// This is a reduced version of the full encoder.
#[derive(Default)]
pub struct GlyphEncoder {
tag_stream: Vec<u8>,
pathseg_stream: Vec<u8>,
drawtag_stream: Vec<u32>,
drawdata_stream: Vec<u8>,
n_path: u32,
n_pathseg: u32,
}
// 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,
}
impl Encoder {
pub fn new() -> Encoder {
Encoder {
transform_stream: vec![Transform::IDENTITY],
tag_stream: Vec::new(),
pathseg_stream: Vec::new(),
linewidth_stream: vec![-1.0],
drawtag_stream: Vec::new(),
drawdata_stream: Vec::new(),
n_path: 0,
n_pathseg: 0,
n_clip: 0,
}
}
pub fn path_encoder(&mut self) -> PathEncoder {
PathEncoder::new(&mut self.tag_stream, &mut self.pathseg_stream)
}
pub fn finish_path(&mut self, n_pathseg: u32) {
self.n_path += 1;
self.n_pathseg += n_pathseg;
}
pub fn transform(&mut self, transform: Transform) {
self.tag_stream.push(0x20);
self.transform_stream.push(transform);
}
// Swap the last two tags in the tag stream; used for transformed
// gradients.
pub fn swap_last_tags(&mut self) {
let len = self.tag_stream.len();
self.tag_stream.swap(len - 1, len - 2);
}
// -1.0 means "fill"
pub fn linewidth(&mut self, linewidth: f32) {
self.tag_stream.push(0x40);
self.linewidth_stream.push(linewidth);
}
/// Encode a fill color draw object.
///
/// This should be encoded after a path.
pub fn fill_color(&mut self, rgba_color: u32) {
self.drawtag_stream.push(DRAWTAG_FILLCOLOR);
let element = FillColor { rgba_color };
self.drawdata_stream.extend(bytemuck::bytes_of(&element));
}
/// Encode a fill linear gradient draw object.
///
/// This should be encoded after a path.
pub fn fill_lin_gradient(&mut self, index: u32, p0: [f32; 2], p1: [f32; 2]) {
self.drawtag_stream.push(DRAWTAG_FILLLINGRADIENT);
let element = FillLinGradient { index, p0, p1 };
self.drawdata_stream.extend(bytemuck::bytes_of(&element));
}
/// Encode a fill radial gradient draw object.
///
/// This should be encoded after a path.
pub fn fill_rad_gradient(&mut self, index: u32, p0: [f32; 2], p1: [f32; 2], r0: f32, r1: f32) {
self.drawtag_stream.push(DRAWTAG_FILLRADGRADIENT);
let element = FillRadGradient {
index,
p0,
p1,
r0,
r1,
};
self.drawdata_stream.extend(bytemuck::bytes_of(&element));
}
/// Start a clip.
pub fn begin_clip(&mut self, blend: Option<Blend>) {
self.drawtag_stream.push(DRAWTAG_BEGINCLIP);
let element = Clip {
blend: blend.unwrap_or(Blend::default()).pack(),
};
self.drawdata_stream.extend(bytemuck::bytes_of(&element));
self.n_clip += 1;
}
pub fn end_clip(&mut self, blend: Option<Blend>) {
self.drawtag_stream.push(DRAWTAG_ENDCLIP);
let element = Clip {
blend: blend.unwrap_or(Blend::default()).pack(),
};
self.drawdata_stream.extend(bytemuck::bytes_of(&element));
// This is a dummy path, and will go away with the new clip impl.
self.tag_stream.push(0x10);
self.n_path += 1;
self.n_clip += 1;
}
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);
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);
}
pub(crate) fn stats(&self) -> SceneStats {
SceneStats {
n_drawobj: self.drawtag_stream.len(),
drawdata_len: self.drawdata_stream.len(),
n_transform: self.transform_stream.len(),
linewidth_len: std::mem::size_of_val(&*self.linewidth_stream),
n_pathtag: self.tag_stream.len(),
pathseg_len: self.pathseg_stream.len(),
n_path: self.n_path,
n_pathseg: self.n_pathseg,
n_clip: self.n_clip,
}
}
pub(crate) fn encode_glyph(&mut self, glyph: &GlyphEncoder) {
self.tag_stream.extend(&glyph.tag_stream);
self.pathseg_stream.extend(&glyph.pathseg_stream);
self.drawtag_stream.extend(&glyph.drawtag_stream);
self.drawdata_stream.extend(&glyph.drawdata_stream);
self.n_path += glyph.n_path;
self.n_pathseg += glyph.n_pathseg;
}
}
fn padding(x: usize, align: usize) -> usize {
x.wrapping_neg() & (align - 1)
}
impl GlyphEncoder {
pub(crate) fn path_encoder(&mut self) -> PathEncoder {
PathEncoder::new(&mut self.tag_stream, &mut self.pathseg_stream)
}
pub(crate) fn finish_path(&mut self, n_pathseg: u32) {
self.n_path += 1;
self.n_pathseg += n_pathseg;
}
/// Encode a fill color draw object.
///
/// This should be encoded after a path.
pub(crate) fn fill_color(&mut self, rgba_color: u32) {
self.drawtag_stream.push(DRAWTAG_FILLCOLOR);
let element = FillColor { rgba_color };
self.drawdata_stream.extend(bytemuck::bytes_of(&element));
}
pub(crate) fn is_color(&self) -> bool {
!self.drawtag_stream.is_empty()
}
}

View file

@ -1,87 +0,0 @@
// 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.
//! An experimental API for glyph rendering.
use piet::{kurbo::Affine, RenderContext};
use swash::{scale::ScaleContext, CacheKey, FontDataRef};
use crate::{encoder::GlyphEncoder, PietGpuRenderContext};
pub struct GlyphRenderer {
pub render_ctx: PietGpuRenderContext,
scale_context: ScaleContext,
}
#[repr(transparent)]
pub struct FontId(CacheKey);
impl GlyphRenderer {
pub fn new() -> GlyphRenderer {
let render_ctx = PietGpuRenderContext::new();
let scale_context = ScaleContext::new();
GlyphRenderer {
render_ctx,
scale_context,
}
}
pub unsafe fn add_glyph(
&mut self,
font_data: &[u8],
font_id: u64,
glyph_id: u16,
transform: [f32; 6],
) {
// This transmute is dodgy because the definition in swash isn't repr(transparent).
// I think the best solution is to have a from_u64 method, but we'll work that out
// later.
let font_id = FontId(std::mem::transmute(font_id));
let encoder = self.make_glyph(font_data, font_id, glyph_id);
const DEFAULT_UPEM: u16 = 2048;
let affine = Affine::new([
transform[0] as f64,
transform[1] as f64,
transform[2] as f64,
transform[3] as f64,
transform[4] as f64,
transform[5] as f64,
]) * Affine::scale(1.0 / DEFAULT_UPEM as f64);
self.render_ctx.transform(affine);
self.render_ctx.encode_glyph(&encoder);
// TODO: don't fill glyph if RGBA
self.render_ctx.fill_glyph(0xff_ff_ff_ff);
self.render_ctx.transform(affine.inverse());
}
pub fn reset(&mut self) {
self.render_ctx = PietGpuRenderContext::new();
}
fn make_glyph(&mut self, font_data: &[u8], font_id: FontId, glyph_id: u16) -> GlyphEncoder {
let mut encoder = GlyphEncoder::default();
let font_data = FontDataRef::new(font_data).expect("invalid font");
let mut font_ref = font_data.get(0).expect("invalid font index");
font_ref.key = font_id.0;
let mut scaler = self.scale_context.builder(font_ref).size(2048.).build();
if let Some(outline) = scaler.scale_outline(glyph_id) {
crate::text::append_outline(&mut encoder, outline.verbs(), outline.points());
} else {
println!("failed to scale");
}
encoder
}
}

View file

@ -1,249 +0,0 @@
// Copyright 2021 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.
//! Implementation of gradients.
use std::collections::hash_map::{Entry, HashMap};
use piet::kurbo::Point;
use piet::{Color, FixedLinearGradient, FixedRadialGradient, GradientStop};
/// Radial gradient compatible with COLRv1 spec
#[derive(Debug, Clone)]
pub struct Colrv1RadialGradient {
/// The center of the iner circle.
pub center0: Point,
/// The offset of the origin relative to the center.
pub center1: Point,
/// The radius of the inner circle.
pub radius0: f64,
/// The radius of the outer circle.
pub radius1: f64,
/// The stops.
pub stops: Vec<GradientStop>,
}
#[derive(Clone)]
pub struct BakedGradient {
ramp: Vec<u32>,
}
#[derive(Clone)]
pub struct LinearGradient {
pub(crate) start: [f32; 2],
pub(crate) end: [f32; 2],
pub(crate) ramp_id: u32,
}
#[derive(Clone)]
pub struct RadialGradient {
pub(crate) start: [f32; 2],
pub(crate) end: [f32; 2],
pub(crate) r0: f32,
pub(crate) r1: f32,
pub(crate) ramp_id: u32,
}
#[derive(Default)]
pub struct RampCache {
ramps: Vec<GradientRamp>,
map: HashMap<GradientRamp, usize>,
}
#[derive(Clone, Hash, PartialEq, Eq)]
struct GradientRamp(Vec<u32>);
pub const N_SAMPLES: usize = 512;
// TODO: make this dynamic
pub const N_GRADIENTS: usize = 256;
#[derive(Clone, Copy)]
struct PremulRgba([f64; 4]);
impl PremulRgba {
fn from_color(c: &Color) -> PremulRgba {
let rgba = c.as_rgba();
let a = rgba.3;
// TODO: sRGB nonlinearity? This is complicated.
PremulRgba([rgba.0 * a, rgba.1 * a, rgba.2 * a, a])
}
fn to_u32(&self) -> u32 {
let z = self.0;
let r = (z[0].max(0.0).min(1.0) * 255.0).round() as u32;
let g = (z[1].max(0.0).min(1.0) * 255.0).round() as u32;
let b = (z[2].max(0.0).min(1.0) * 255.0).round() as u32;
let a = (z[3].max(0.0).min(1.0) * 255.0).round() as u32;
r | (g << 8) | (b << 16) | (a << 24)
}
fn lerp(&self, other: PremulRgba, t: f64) -> PremulRgba {
fn l(a: f64, b: f64, t: f64) -> f64 {
a * (1.0 - t) + b * t
}
let a = self.0;
let b = other.0;
PremulRgba([
l(a[0], b[0], t),
l(a[1], b[1], t),
l(a[2], b[2], t),
l(a[3], b[3], t),
])
}
}
impl GradientRamp {
fn from_stops(stops: &[GradientStop]) -> GradientRamp {
let mut last_u = 0.0;
let mut last_c = PremulRgba::from_color(&stops[0].color);
let mut this_u = last_u;
let mut this_c = last_c;
let mut j = 0;
let v = (0..N_SAMPLES)
.map(|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.pos as f64;
this_c = PremulRgba::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_u32()
})
.collect();
GradientRamp(v)
}
/// For debugging/development.
pub(crate) fn dump(&self) {
for val in &self.0 {
println!("{:x}", val);
}
}
}
impl RampCache {
/// Add a gradient ramp to the cache.
///
/// Currently there is no eviction, so if the gradient is animating, there may
/// be resource leaks. In order to support lifetime management, the signature
/// should probably change so it returns a ref-counted handle, so that eviction
/// is deferred until the last handle is dropped.
///
/// This function is pretty expensive, but the result is lightweight.
fn add_ramp(&mut self, ramp: &[GradientStop]) -> usize {
let ramp = GradientRamp::from_stops(ramp);
match self.map.entry(ramp) {
Entry::Occupied(o) => *o.get(),
Entry::Vacant(v) => {
let idx = self.ramps.len();
self.ramps.push(v.key().clone());
v.insert(idx);
idx
}
}
}
pub fn add_linear_gradient(&mut self, lin: &FixedLinearGradient) -> LinearGradient {
let ramp_id = self.add_ramp(&lin.stops);
LinearGradient {
ramp_id: ramp_id as u32,
start: crate::render_ctx::to_f32_2(lin.start),
end: crate::render_ctx::to_f32_2(lin.end),
}
}
pub fn add_radial_gradient(&mut self, rad: &FixedRadialGradient) -> RadialGradient {
let ramp_id = self.add_ramp(&rad.stops);
RadialGradient {
ramp_id: ramp_id as u32,
start: crate::render_ctx::to_f32_2(rad.center + rad.origin_offset),
end: crate::render_ctx::to_f32_2(rad.center),
r0: 0.0,
r1: rad.radius as f32,
}
}
pub fn add_radial_gradient_colrv1(&mut self, rad: &Colrv1RadialGradient) -> RadialGradient {
let ramp_id = self.add_ramp(&rad.stops);
RadialGradient {
ramp_id: ramp_id as u32,
start: crate::render_ctx::to_f32_2(rad.center0),
end: crate::render_ctx::to_f32_2(rad.center1),
r0: rad.radius0 as f32,
r1: rad.radius1 as f32,
}
}
/// Dump the contents of a gradient. This is for debugging.
#[allow(unused)]
pub(crate) fn dump_gradient(&self, lin: &LinearGradient) {
println!("id = {}", lin.ramp_id);
self.ramps[lin.ramp_id as usize].dump();
}
/// Get the ramp data.
///
/// This concatenates all the ramps; we'll want a more sophisticated approach to
/// incremental update.
pub fn get_ramp_data(&self) -> Vec<u32> {
let mut result = Vec::with_capacity(N_SAMPLES * self.ramps.len());
for ramp in &self.ramps {
result.extend(&ramp.0);
}
result
}
}
#[cfg(test)]
mod test {
use super::RampCache;
use piet::kurbo::Point;
use piet::{Color, FixedLinearGradient, GradientStop};
#[test]
fn simple_ramp() {
let stops = vec![
GradientStop {
color: Color::WHITE,
pos: 0.0,
},
GradientStop {
color: Color::BLACK,
pos: 1.0,
},
];
let mut cache = RampCache::default();
let lin = FixedLinearGradient {
start: Point::new(0.0, 0.0),
end: Point::new(0.0, 1.0),
stops,
};
let our_lin = cache.add_linear_gradient(&lin);
cache.dump_gradient(&our_lin);
}
}

View file

@ -1,31 +1,26 @@
mod blend;
mod encoder;
pub mod glyph_render;
mod gradient;
mod pico_svg; mod pico_svg;
mod render_ctx; mod ramp;
mod render_driver; mod render_driver;
pub mod samples;
mod simple_text;
pub mod stages; pub mod stages;
pub mod test_scenes;
mod text; pub use piet_scene as scene;
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use scene::ResourcePatch;
use std::convert::TryInto; use std::convert::TryInto;
pub use blend::{Blend, BlendMode, CompositionMode};
pub use encoder::EncodedSceneRef;
pub use gradient::Colrv1RadialGradient;
pub use render_ctx::PietGpuRenderContext;
pub use render_driver::RenderDriver; pub use render_driver::RenderDriver;
pub use simple_text::SimpleText;
use piet::kurbo::Vec2;
use piet::{ImageFormat, RenderContext};
use piet_gpu_hal::{ use piet_gpu_hal::{
include_shader, BindType, Buffer, BufferUsage, CmdBuf, ComputePassDescriptor, DescriptorSet, include_shader, BindType, BufWrite, Buffer, BufferUsage, CmdBuf, ComputePassDescriptor,
Error, Image, ImageLayout, Pipeline, QueryPool, Session, DescriptorSet, Error, Image, ImageLayout, Pipeline, QueryPool, Session,
}; };
use piet_scene::Scene;
pub use pico_svg::PicoSvg; pub use pico_svg::PicoSvg;
use stages::{ClipBinding, ElementBinding, ElementCode, DRAW_PART_SIZE, PATHSEG_PART_SIZE}; use stages::{ClipBinding, ElementBinding, ElementCode, DRAW_PART_SIZE, PATHSEG_PART_SIZE};
@ -36,6 +31,10 @@ const TILE_H: usize = 16;
const PTCL_INITIAL_ALLOC: usize = 1024; const PTCL_INITIAL_ALLOC: usize = 1024;
const N_GRADIENT_SAMPLES: usize = 512;
// TODO: make this dynamic
const N_GRADIENTS: usize = 256;
#[allow(unused)] #[allow(unused)]
fn dump_scene(buf: &[u8]) { fn dump_scene(buf: &[u8]) {
for i in 0..(buf.len() / 4) { for i in 0..(buf.len() / 4) {
@ -157,6 +156,9 @@ pub struct Renderer {
gradient_bufs: Vec<Buffer>, gradient_bufs: Vec<Buffer>,
gradients: Image, gradients: Image,
ramps: ramp::RampCache,
drawdata_patches: Vec<(usize, u32)>,
} }
impl RenderConfig { impl RenderConfig {
@ -222,7 +224,7 @@ impl Renderer {
let image_format = match config.format { let image_format = match config.format {
PixelFormat::A8 => piet_gpu_hal::ImageFormat::A8, PixelFormat::A8 => piet_gpu_hal::ImageFormat::A8,
PixelFormat::Rgba8 => piet_gpu_hal::ImageFormat::Rgba8, PixelFormat::Rgba8 => piet_gpu_hal::ImageFormat::Surface,
}; };
let image_dev = session.create_image2d(width as u32, height as u32, image_format)?; let image_dev = session.create_image2d(width as u32, height as u32, image_format)?;
@ -333,8 +335,8 @@ impl Renderer {
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let bg_image = Self::make_test_bg_image(&session); let bg_image = Self::make_test_bg_image(&session);
const GRADIENT_BUF_SIZE: usize = const GRADIENT_BUF_SIZE: usize = N_GRADIENTS * N_GRADIENT_SAMPLES * 4;
crate::gradient::N_GRADIENTS * crate::gradient::N_SAMPLES * 4;
let gradient_bufs = (0..n_bufs) let gradient_bufs = (0..n_bufs)
.map(|_| { .map(|_| {
session session
@ -367,6 +369,9 @@ impl Renderer {
.build(&session, &k4_pipeline)?; .build(&session, &k4_pipeline)?;
let scene_stats = Default::default(); let scene_stats = Default::default();
let ramps = ramp::RampCache::default();
let drawdata_patches = vec![];
Ok(Renderer { Ok(Renderer {
width, width,
height, height,
@ -406,62 +411,40 @@ impl Renderer {
_bg_image: bg_image, _bg_image: bg_image,
gradient_bufs, gradient_bufs,
gradients, gradients,
ramps,
drawdata_patches,
}) })
} }
/// Convert the scene in the render context to GPU resources. pub fn upload_scene(&mut self, scene: &Scene, buf_ix: usize) -> Result<(), Error> {
/// self.drawdata_patches.clear();
/// At present, this requires that any command buffer submission has completed. self.scene_stats = SceneStats::from_scene(scene);
/// A future evolution will handle staging of the next frame's scene while the self.ramps.advance();
/// rendering of the current frame is in flight. let data = scene.data();
pub fn upload_render_ctx( let stop_data = &data.resources.stops;
&mut self, for patch in &data.resources.patches {
render_ctx: &mut PietGpuRenderContext, match patch {
buf_ix: usize, ResourcePatch::Ramp { offset, stops } => {
) -> Result<(), Error> { let ramp_id = self.ramps.add(&stop_data[stops.clone()]);
self.scene_stats = render_ctx.stats(); self.drawdata_patches.push((*offset, ramp_id));
}
}
}
unsafe { unsafe {
self.upload_config(buf_ix)?; self.upload_config(buf_ix)?;
{ {
let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?; let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?;
render_ctx.write_scene(&mut mapped_scene); write_scene(scene, &self.drawdata_patches, &mut mapped_scene);
} }
// Upload gradient data. // Upload gradient data.
let ramp_data = render_ctx.get_ramp_data(); let ramp_data = self.ramps.data();
if !ramp_data.is_empty() { if !ramp_data.is_empty() {
assert!( assert!(
self.gradient_bufs[buf_ix].size() as usize self.gradient_bufs[buf_ix].size() as usize
>= std::mem::size_of_val(&*ramp_data) >= std::mem::size_of_val(&*ramp_data)
); );
self.gradient_bufs[buf_ix].write(&ramp_data)?; self.gradient_bufs[buf_ix].write(ramp_data)?;
}
}
Ok(())
}
pub fn upload_scene<T: Copy + Pod>(
&mut self,
scene: &EncodedSceneRef<T>,
buf_ix: usize,
) -> Result<(), Error> {
self.scene_stats = scene.stats();
unsafe {
self.upload_config(buf_ix)?;
{
let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?;
scene.write_scene(&mut mapped_scene);
}
// 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(()) Ok(())
@ -643,12 +626,8 @@ impl Renderer {
width: usize, width: usize,
height: usize, height: usize,
buf: &[u8], buf: &[u8],
format: ImageFormat,
) -> Result<Image, Error> { ) -> Result<Image, Error> {
unsafe { unsafe {
if format != ImageFormat::RgbaPremul {
return Err("unsupported image format".into());
}
let buffer = session.create_buffer_init(&buf, BufferUsage::COPY_SRC)?; let buffer = session.create_buffer_init(&buf, BufferUsage::COPY_SRC)?;
const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8; const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8;
let image = session.create_image2d(width.try_into()?, height.try_into()?, RGBA)?; let image = session.create_image2d(width.try_into()?, height.try_into()?, RGBA)?;
@ -682,18 +661,14 @@ impl Renderer {
buf[(y * WIDTH + x) * 4 + 2] = b; buf[(y * WIDTH + x) * 4 + 2] = b;
} }
} }
Self::make_image(session, WIDTH, HEIGHT, &buf, ImageFormat::RgbaPremul).unwrap() Self::make_image(session, WIDTH, HEIGHT, &buf).unwrap()
} }
fn make_gradient_image(session: &Session) -> Image { fn make_gradient_image(session: &Session) -> Image {
unsafe { unsafe {
const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8; const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8;
session session
.create_image2d( .create_image2d(N_GRADIENT_SAMPLES as u32, N_GRADIENTS as u32, RGBA)
gradient::N_SAMPLES as u32,
gradient::N_GRADIENTS as u32,
RGBA,
)
.unwrap() .unwrap()
} }
} }
@ -792,6 +767,21 @@ const DRAWTAG_SIZE: usize = 4;
const ANNOTATED_SIZE: usize = 40; const ANNOTATED_SIZE: usize = 40;
impl SceneStats { impl SceneStats {
pub fn from_scene(scene: &piet_scene::Scene) -> Self {
let data = scene.data();
Self {
n_drawobj: data.drawtag_stream.len(),
drawdata_len: data.drawdata_stream.len(),
n_transform: data.transform_stream.len(),
linewidth_len: std::mem::size_of_val(&*data.linewidth_stream),
pathseg_len: data.pathseg_stream.len(),
n_pathtag: data.tag_stream.len(),
n_path: data.n_path,
n_pathseg: data.n_pathseg,
n_clip: data.n_clip,
}
}
pub(crate) fn scene_size(&self) -> usize { pub(crate) fn scene_size(&self) -> usize {
align_up(self.n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE align_up(self.n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE
+ self.drawdata_len + self.drawdata_len
@ -896,6 +886,40 @@ impl SceneStats {
} }
} }
fn write_scene(scene: &Scene, drawdata_patches: &[(usize, u32)], buf: &mut BufWrite) {
let data = scene.data();
buf.extend_slice(&data.drawtag_stream);
let n_drawobj = data.drawtag_stream.len();
buf.fill_zero(padding(n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE);
if !drawdata_patches.is_empty() {
let mut pos = 0;
for patch in drawdata_patches {
let offset = patch.0;
let value = patch.1;
if pos < offset {
buf.extend_slice(&data.drawdata_stream[pos..offset]);
}
buf.push(value);
pos = offset + 4;
}
if pos < data.drawdata_stream.len() {
buf.extend_slice(&data.drawdata_stream[pos..])
}
} else {
buf.extend_slice(&data.drawdata_stream);
}
buf.extend_slice(&data.transform_stream);
buf.extend_slice(&data.linewidth_stream);
buf.extend_slice(&data.tag_stream);
let n_pathtag = data.tag_stream.len();
buf.fill_zero(padding(n_pathtag, PATHSEG_PART_SIZE as usize));
buf.extend_slice(&data.pathseg_stream);
}
fn padding(x: usize, align: usize) -> usize {
x.wrapping_neg() & (align - 1)
}
fn align_up(x: usize, align: usize) -> usize { fn align_up(x: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two()); debug_assert!(align.is_power_of_two());
(x + align - 1) & !(align - 1) (x + align - 1) & !(align - 1)

View file

@ -4,12 +4,12 @@ use std::str::FromStr;
use roxmltree::{Document, Node}; use roxmltree::{Document, Node};
use piet::kurbo::{Affine, BezPath}; use kurbo::{Affine, BezPath};
use piet::{Color, RenderContext}; use piet_scene::Color;
pub struct PicoSvg { pub struct PicoSvg {
items: Vec<Item>, pub items: Vec<Item>,
} }
pub enum Item { pub enum Item {
@ -18,14 +18,14 @@ pub enum Item {
} }
pub struct StrokeItem { pub struct StrokeItem {
width: f64, pub width: f64,
color: Color, pub color: Color,
path: BezPath, pub path: BezPath,
} }
pub struct FillItem { pub struct FillItem {
color: Color, pub color: Color,
path: BezPath, pub path: BezPath,
} }
struct Parser<'a> { struct Parser<'a> {
@ -44,20 +44,6 @@ impl PicoSvg {
} }
Ok(PicoSvg { items }) Ok(PicoSvg { items })
} }
pub fn render(&self, rc: &mut impl RenderContext) {
for item in &self.items {
match item {
Item::Fill(fill_item) => {
rc.fill(&fill_item.path, &fill_item.color);
//rc.stroke(&fill_item.path, &fill_item.color, 1.0);
}
Item::Stroke(stroke_item) => {
rc.stroke(&stroke_item.path, &stroke_item.color, stroke_item.width);
}
}
}
}
} }
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
@ -119,7 +105,14 @@ fn parse_color(color: &str) -> Color {
if color.len() == 4 { if color.len() == 4 {
hex = (hex >> 8) * 0x110000 + ((hex >> 4) & 0xf) * 0x1100 + (hex & 0xf) * 0x11; hex = (hex >> 8) * 0x110000 + ((hex >> 4) & 0xf) * 0x1100 + (hex & 0xf) * 0x11;
} }
Color::from_rgba32_u32((hex << 8) + 0xff) let rgba = (hex << 8) + 0xff;
let (r, g, b, a) = (
(rgba >> 24 & 255) as u8,
((rgba >> 16) & 255) as u8,
((rgba >> 8) & 255) as u8,
(rgba & 255) as u8,
);
Color::rgba8(r, g, b, a)
} else if color.starts_with("rgb(") { } else if color.starts_with("rgb(") {
let mut iter = color[4..color.len() - 1].split(','); let mut iter = color[4..color.len() - 1].split(',');
let r = u8::from_str(iter.next().unwrap()).unwrap(); let r = u8::from_str(iter.next().unwrap()).unwrap();
@ -127,19 +120,20 @@ fn parse_color(color: &str) -> Color {
let b = u8::from_str(iter.next().unwrap()).unwrap(); let b = u8::from_str(iter.next().unwrap()).unwrap();
Color::rgb8(r, g, b) Color::rgb8(r, g, b)
} else { } else {
Color::from_rgba32_u32(0xff00ff80) Color::rgba8(255, 0, 255, 0x80)
} }
} }
fn modify_opacity(color: Color, attr_name: &str, node: Node) -> Color { fn modify_opacity(mut color: Color, attr_name: &str, node: Node) -> Color {
if let Some(opacity) = node.attribute(attr_name) { if let Some(opacity) = node.attribute(attr_name) {
let alpha = if opacity.ends_with("%") { let alpha = if opacity.ends_with("%") {
let pctg = opacity[..opacity.len() - 1].parse().unwrap_or(100.0); let pctg = opacity[..opacity.len() - 1].parse().unwrap_or(100.0);
pctg * 0.01 pctg * 0.01
} else { } else {
opacity.parse().unwrap_or(1.0) opacity.parse().unwrap_or(1.0)
}; } as f64;
color.with_alpha(alpha) color.a = (alpha.min(1.0).max(0.0) * 255.0).round() as u8;
color
} else { } else {
color color
} }

View file

@ -1,4 +1,5 @@
use crate::brush::{Color, Stop, StopVec}; use piet_scene::{Color, GradientStop, GradientStops};
use std::collections::HashMap; use std::collections::HashMap;
const N_SAMPLES: usize = 512; const N_SAMPLES: usize = 512;
@ -7,15 +8,11 @@ const RETAINED_COUNT: usize = 64;
#[derive(Default)] #[derive(Default)]
pub struct RampCache { pub struct RampCache {
epoch: u64, epoch: u64,
map: HashMap<StopVec, (u32, u64)>, map: HashMap<GradientStops, (u32, u64)>,
data: Vec<u32>, data: Vec<u32>,
} }
impl RampCache { impl RampCache {
pub fn new() -> Self {
Self::default()
}
pub fn advance(&mut self) { pub fn advance(&mut self) {
self.epoch += 1; self.epoch += 1;
if self.map.len() > RETAINED_COUNT { if self.map.len() > RETAINED_COUNT {
@ -25,13 +22,7 @@ impl RampCache {
} }
} }
pub fn clear(&mut self) { pub fn add(&mut self, stops: &[GradientStop]) -> u32 {
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) { if let Some(entry) = self.map.get_mut(stops) {
entry.1 = self.epoch; entry.1 = self.epoch;
entry.0 entry.0
@ -73,7 +64,7 @@ impl RampCache {
} }
} }
fn make_ramp<'a>(stops: &'a [Stop]) -> impl Iterator<Item = u32> + 'a { fn make_ramp<'a>(stops: &'a [GradientStop]) -> impl Iterator<Item = u32> + 'a {
let mut last_u = 0.0; let mut last_u = 0.0;
let mut last_c = ColorF64::from_color(stops[0].color); let mut last_c = ColorF64::from_color(stops[0].color);
let mut this_u = last_u; let mut this_u = last_u;
@ -133,6 +124,6 @@ impl ColorF64 {
let g = ((self.0[1] * 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 b = ((self.0[2] * a).min(1.0).max(0.0) * 255.0) as u32;
let a = (a * 255.0) as u32; let a = (a * 255.0) as u32;
b | (g << 8) | (r << 16) | (a << 24) r | (g << 8) | (b << 16) | (a << 24)
} }
} }

View file

@ -1,452 +0,0 @@
// This should match the value in kernel4.comp for correct rendering.
const DO_SRGB_CONVERSION: bool = false;
use std::borrow::Cow;
use crate::encoder::GlyphEncoder;
use crate::stages::Transform;
use piet::kurbo::{Affine, PathEl, Point, Rect, Shape};
use piet::{
Color, Error, FixedGradient, ImageFormat, InterpolationMode, IntoBrush, RenderContext,
StrokeStyle,
};
use piet_gpu_hal::BufWrite;
use piet_gpu_types::encoder::{Encode, Encoder};
use piet_gpu_types::scene::Element;
use crate::gradient::{Colrv1RadialGradient, LinearGradient, RadialGradient, RampCache};
use crate::text::Font;
pub use crate::text::{PietGpuText, PietGpuTextLayout, PietGpuTextLayoutBuilder};
use crate::{Blend, SceneStats};
pub struct PietGpuImage;
pub struct PietGpuRenderContext {
encoder: Encoder,
elements: Vec<Element>,
// Will probably need direct accesss to hal Device to create images etc.
inner_text: PietGpuText,
stroke_width: f32,
// We're tallying these cpu-side for expedience, but will probably
// move this to some kind of readback from element processing.
/// The count of elements that make it through to coarse rasterization.
path_count: usize,
/// The count of path segment elements.
pathseg_count: usize,
/// The count of transform elements.
trans_count: usize,
cur_transform: Affine,
state_stack: Vec<State>,
clip_stack: Vec<ClipElement>,
ramp_cache: RampCache,
// Fields for new element processing pipeline below
// TODO: delete old encoder, rename
new_encoder: crate::encoder::Encoder,
}
#[derive(Clone)]
pub enum PietGpuBrush {
Solid(u32),
LinGradient(LinearGradient),
RadGradient(RadialGradient),
}
#[derive(Default)]
struct State {
/// The transform at the parent state.
transform: Affine,
n_clip: usize,
}
struct ClipElement {
blend: Option<Blend>,
}
const TOLERANCE: f64 = 0.25;
impl PietGpuRenderContext {
pub fn new() -> PietGpuRenderContext {
let encoder = Encoder::new();
let elements = Vec::new();
let font = Font::new();
let inner_text = PietGpuText::new(font);
let stroke_width = -1.0;
PietGpuRenderContext {
encoder,
elements,
inner_text,
stroke_width,
path_count: 0,
pathseg_count: 0,
trans_count: 0,
cur_transform: Affine::default(),
state_stack: Vec::new(),
clip_stack: Vec::new(),
ramp_cache: RampCache::default(),
new_encoder: crate::encoder::Encoder::new(),
}
}
pub(crate) fn stats(&self) -> SceneStats {
self.new_encoder.stats()
}
pub fn write_scene(&self, buf: &mut BufWrite) {
self.new_encoder.write_scene(buf);
}
// TODO: delete
pub fn get_scene_buf(&mut self) -> &[u8] {
const ALIGN: usize = 128;
let padded_size = (self.elements.len() + (ALIGN - 1)) & ALIGN.wrapping_neg();
self.elements.resize(padded_size, Element::Nop());
self.elements.encode(&mut self.encoder);
self.encoder.buf()
}
pub fn path_count(&self) -> usize {
self.path_count
}
pub fn pathseg_count(&self) -> usize {
self.pathseg_count
}
pub fn trans_count(&self) -> usize {
self.trans_count
}
pub fn get_ramp_data(&self) -> Vec<u32> {
self.ramp_cache.get_ramp_data()
}
}
impl RenderContext for PietGpuRenderContext {
type Brush = PietGpuBrush;
type Image = PietGpuImage;
type Text = PietGpuText;
type TextLayout = PietGpuTextLayout;
fn status(&mut self) -> Result<(), Error> {
Ok(())
}
fn solid_brush(&mut self, color: Color) -> Self::Brush {
// kernel4 expects colors encoded in alpha-premultiplied sRGB:
//
// [α,sRGB(α⋅R),sRGB(α⋅G),sRGB(α⋅B)]
//
// See also http://ssp.impulsetrain.com/gamma-premult.html.
let (r, g, b, a) = color.as_rgba();
let premul = Color::rgba(
to_srgb(from_srgb(r) * a),
to_srgb(from_srgb(g) * a),
to_srgb(from_srgb(b) * a),
a,
);
PietGpuBrush::Solid(premul.as_rgba_u32())
}
fn gradient(&mut self, gradient: impl Into<FixedGradient>) -> Result<Self::Brush, Error> {
match gradient.into() {
FixedGradient::Linear(lin) => {
let lin = self.ramp_cache.add_linear_gradient(&lin);
Ok(PietGpuBrush::LinGradient(lin))
}
FixedGradient::Radial(rad) => {
let rad = self.ramp_cache.add_radial_gradient(&rad);
Ok(PietGpuBrush::RadGradient(rad))
}
}
}
fn clear(&mut self, _color: Color) {}
fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>, width: f64) {
self.encode_linewidth(width.abs() as f32);
let brush = brush.make_brush(self, || shape.bounding_box()).into_owned();
let path = shape.path_elements(TOLERANCE);
self.encode_path(path, false);
self.encode_brush(&brush);
}
fn stroke_styled(
&mut self,
_shape: impl Shape,
_brush: &impl IntoBrush<Self>,
_width: f64,
_style: &StrokeStyle,
) {
}
fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
let brush = brush.make_brush(self, || shape.bounding_box()).into_owned();
let path = shape.path_elements(TOLERANCE);
self.encode_linewidth(-1.0);
self.encode_path(path, true);
self.encode_brush(&brush);
}
fn fill_even_odd(&mut self, _shape: impl Shape, _brush: &impl IntoBrush<Self>) {}
fn clip(&mut self, shape: impl Shape) {
self.encode_linewidth(-1.0);
let path = shape.path_elements(TOLERANCE);
self.encode_path(path, true);
self.new_encoder.begin_clip(None);
self.clip_stack.push(ClipElement { blend: None });
if let Some(tos) = self.state_stack.last_mut() {
tos.n_clip += 1;
}
}
fn text(&mut self) -> &mut Self::Text {
&mut self.inner_text
}
fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into<Point>) {
self.encode_linewidth(-1.0);
layout.draw_text(self, pos.into());
}
fn save(&mut self) -> Result<(), Error> {
self.state_stack.push(State {
transform: self.cur_transform,
n_clip: 0,
});
Ok(())
}
fn restore(&mut self) -> Result<(), Error> {
if let Some(state) = self.state_stack.pop() {
self.encode_transform(Transform::from_kurbo(state.transform));
self.cur_transform = state.transform;
for _ in 0..state.n_clip {
self.pop_clip();
}
Ok(())
} else {
Err(Error::StackUnbalance)
}
}
fn finish(&mut self) -> Result<(), Error> {
for _ in 0..self.clip_stack.len() {
self.pop_clip();
}
Ok(())
}
fn transform(&mut self, transform: Affine) {
self.cur_transform *= transform;
self.encode_transform(Transform::from_kurbo(self.cur_transform));
}
fn make_image(
&mut self,
_width: usize,
_height: usize,
_buf: &[u8],
_format: ImageFormat,
) -> Result<Self::Image, Error> {
Ok(PietGpuImage)
}
fn draw_image(
&mut self,
_image: &Self::Image,
_rect: impl Into<Rect>,
_interp: InterpolationMode,
) {
}
fn draw_image_area(
&mut self,
_image: &Self::Image,
_src_rect: impl Into<Rect>,
_dst_rect: impl Into<Rect>,
_interp: InterpolationMode,
) {
}
fn blurred_rect(&mut self, _rect: Rect, _blur_radius: f64, _brush: &impl IntoBrush<Self>) {}
fn current_transform(&self) -> Affine {
self.cur_transform
}
fn with_save(&mut self, f: impl FnOnce(&mut Self) -> Result<(), Error>) -> Result<(), Error> {
self.save()?;
// Always try to restore the stack, even if `f` errored.
f(self).and(self.restore())
}
}
impl PietGpuRenderContext {
pub fn blend(&mut self, shape: impl Shape, blend: Blend) {
self.encode_linewidth(-1.0);
let path = shape.path_elements(TOLERANCE);
self.encode_path(path, true);
self.new_encoder.begin_clip(Some(blend));
self.clip_stack.push(ClipElement { blend: Some(blend) });
if let Some(tos) = self.state_stack.last_mut() {
tos.n_clip += 1;
}
}
pub fn radial_gradient_colrv1(&mut self, rad: &Colrv1RadialGradient) -> PietGpuBrush {
PietGpuBrush::RadGradient(self.ramp_cache.add_radial_gradient_colrv1(rad))
}
pub fn fill_transform(&mut self, shape: impl Shape, brush: &PietGpuBrush, transform: Affine) {
let path = shape.path_elements(TOLERANCE);
self.encode_linewidth(-1.0);
self.encode_path(path, true);
self.encode_transform(Transform::from_kurbo(transform));
self.new_encoder.swap_last_tags();
self.encode_brush(&brush);
self.encode_transform(Transform::from_kurbo(transform.inverse()));
}
fn encode_path(&mut self, path: impl Iterator<Item = PathEl>, is_fill: bool) {
if is_fill {
self.encode_path_inner(
path.flat_map(|el| {
match el {
PathEl::MoveTo(..) => Some(PathEl::ClosePath),
_ => None,
}
.into_iter()
.chain(Some(el))
})
.chain(Some(PathEl::ClosePath)),
)
} else {
self.encode_path_inner(path)
}
}
fn encode_path_inner(&mut self, path: impl Iterator<Item = PathEl>) {
let mut pe = self.new_encoder.path_encoder();
for el in path {
match el {
PathEl::MoveTo(p) => {
let p = to_f32_2(p);
pe.move_to(p[0], p[1]);
}
PathEl::LineTo(p) => {
let p = to_f32_2(p);
pe.line_to(p[0], p[1]);
}
PathEl::QuadTo(p1, p2) => {
let p1 = to_f32_2(p1);
let p2 = to_f32_2(p2);
pe.quad_to(p1[0], p1[1], p2[0], p2[1]);
}
PathEl::CurveTo(p1, p2, p3) => {
let p1 = to_f32_2(p1);
let p2 = to_f32_2(p2);
let p3 = to_f32_2(p3);
pe.cubic_to(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);
}
PathEl::ClosePath => pe.close_path(),
}
}
pe.path();
let n_pathseg = pe.n_pathseg();
self.new_encoder.finish_path(n_pathseg);
}
fn pop_clip(&mut self) {
let tos = self.clip_stack.pop().unwrap();
self.new_encoder.end_clip(tos.blend);
}
pub(crate) fn encode_glyph(&mut self, glyph: &GlyphEncoder) {
self.new_encoder.encode_glyph(glyph);
}
pub(crate) fn fill_glyph(&mut self, rgba_color: u32) {
self.new_encoder.fill_color(rgba_color);
}
pub(crate) fn encode_transform(&mut self, transform: Transform) {
self.new_encoder.transform(transform);
}
fn encode_linewidth(&mut self, linewidth: f32) {
if self.stroke_width != linewidth {
self.new_encoder.linewidth(linewidth);
self.stroke_width = linewidth;
}
}
fn encode_brush(&mut self, brush: &PietGpuBrush) {
match brush {
PietGpuBrush::Solid(rgba_color) => {
self.new_encoder.fill_color(*rgba_color);
}
PietGpuBrush::LinGradient(lin) => {
self.new_encoder
.fill_lin_gradient(lin.ramp_id, lin.start, lin.end);
}
PietGpuBrush::RadGradient(rad) => {
self.new_encoder
.fill_rad_gradient(rad.ramp_id, rad.start, rad.end, rad.r0, rad.r1);
}
}
}
}
impl IntoBrush<PietGpuRenderContext> for PietGpuBrush {
fn make_brush<'b>(
&'b self,
_piet: &mut PietGpuRenderContext,
_bbox: impl FnOnce() -> Rect,
) -> std::borrow::Cow<'b, PietGpuBrush> {
Cow::Borrowed(self)
}
}
pub(crate) fn to_f32_2(point: Point) -> [f32; 2] {
[point.x as f32, point.y as f32]
}
fn rect_to_f32_4(rect: Rect) -> [f32; 4] {
[
rect.x0 as f32,
rect.y0 as f32,
rect.x1 as f32,
rect.y1 as f32,
]
}
fn to_srgb(f: f64) -> f64 {
if DO_SRGB_CONVERSION {
if f <= 0.0031308 {
f * 12.92
} else {
let a = 0.055;
(1. + a) * f64::powf(f, f64::recip(2.4)) - a
}
} else {
f
}
}
fn from_srgb(f: f64) -> f64 {
if DO_SRGB_CONVERSION {
if f <= 0.04045 {
f / 12.92
} else {
let a = 0.055;
f64::powf((f + a) * f64::recip(1. + a), 2.4)
}
} else {
f
}
}

View file

@ -14,10 +14,10 @@
// //
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
use bytemuck::Pod;
use piet_gpu_hal::{CmdBuf, Error, Image, QueryPool, Semaphore, Session, SubmittedCmdBuf}; use piet_gpu_hal::{CmdBuf, Error, Image, QueryPool, Semaphore, Session, SubmittedCmdBuf};
use piet_scene::Scene;
use crate::{EncodedSceneRef, MemoryHeader, PietGpuRenderContext, Renderer, SceneStats}; use crate::{MemoryHeader, Renderer, SceneStats};
/// Additional logic for sequencing rendering operations, specifically /// Additional logic for sequencing rendering operations, specifically
/// for handling failure and reallocation. /// for handling failure and reallocation.
@ -86,22 +86,8 @@ impl RenderDriver {
} }
} }
pub fn upload_render_ctx( pub fn upload_scene(&mut self, session: &Session, scene: &Scene) -> Result<(), Error> {
&mut self, let stats = SceneStats::from_scene(scene);
session: &Session,
render_ctx: &mut PietGpuRenderContext,
) -> Result<(), Error> {
let stats = render_ctx.stats();
self.ensure_scene_buffers(session, &stats)?;
self.renderer.upload_render_ctx(render_ctx, self.buf_ix)
}
pub fn upload_scene<T: Copy + Pod>(
&mut self,
session: &Session,
scene: &EncodedSceneRef<T>,
) -> Result<(), Error> {
let stats = scene.stats();
self.ensure_scene_buffers(session, &stats)?; self.ensure_scene_buffers(session, &stats)?;
self.renderer.upload_scene(scene, self.buf_ix) self.renderer.upload_scene(scene, self.buf_ix)
} }

428
piet-gpu/src/samples.rs Normal file
View file

@ -0,0 +1,428 @@
use crate::PicoSvg;
use kurbo::BezPath;
use piet_scene::*;
use crate::SimpleText;
#[allow(unused)]
const N_CIRCLES: usize = 0;
#[allow(unused)]
pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) {
use crate::pico_svg::*;
let start = std::time::Instant::now();
for item in &svg.items {
match item {
Item::Fill(fill) => {
sb.fill(
Fill::NonZero,
Affine::IDENTITY,
&fill.color.into(),
None,
convert_bez_path(&fill.path),
);
}
Item::Stroke(stroke) => {
sb.stroke(
&simple_stroke(stroke.width as f32),
Affine::IDENTITY,
&stroke.color.into(),
None,
convert_bez_path(&stroke.path),
);
}
}
}
if print_stats {
println!("flattening and encoding time: {:?}", start.elapsed());
}
}
#[allow(unused)]
pub fn render_tiger(sb: &mut SceneBuilder, print_stats: bool) {
use super::pico_svg::*;
let xml_str = std::str::from_utf8(include_bytes!("../Ghostscript_Tiger.svg")).unwrap();
let start = std::time::Instant::now();
let svg = PicoSvg::load(xml_str, 8.0).unwrap();
if print_stats {
println!("parsing time: {:?}", start.elapsed());
}
render_svg(sb, &svg, print_stats);
}
pub fn render_scene(sb: &mut SceneBuilder) {
render_cardioid(sb);
render_clip_test(sb);
render_alpha_test(sb);
//render_tiger(sb, false);
}
#[allow(unused)]
fn render_cardioid(sb: &mut SceneBuilder) {
let n = 601;
let dth = std::f32::consts::PI * 2.0 / (n as f32);
let center = Point::new(1024.0, 768.0);
let r = 750.0;
let mut path = vec![];
for i in 1..n {
let mut p0 = center;
let a0 = i as f32 * dth;
p0.x += a0.cos() * r;
p0.y += a0.sin() * r;
let mut p1 = center;
let a1 = ((i * 2) % n) as f32 * dth;
p1.x += a1.cos() * r;
p1.y += a1.sin() * r;
path.push(PathElement::MoveTo(p0));
path.push(PathElement::LineTo(p1));
}
sb.stroke(
&simple_stroke(2.0),
Affine::IDENTITY,
&Brush::Solid(Color::rgb8(0, 0, 0)),
None,
&path,
);
}
#[allow(unused)]
fn render_clip_test(sb: &mut SceneBuilder) {
const N: usize = 16;
const X0: f32 = 50.0;
const Y0: f32 = 450.0;
// Note: if it gets much larger, it will exceed the 1MB scratch buffer.
// But this is a pretty demanding test.
const X1: f32 = 550.0;
const Y1: f32 = 950.0;
let step = 1.0 / ((N + 1) as f32);
for i in 0..N {
let t = ((i + 1) as f32) * step;
let path = &[
PathElement::MoveTo((X0, Y0).into()),
PathElement::LineTo((X1, Y0).into()),
PathElement::LineTo((X1, Y0 + t * (Y1 - Y0)).into()),
PathElement::LineTo((X1 + t * (X0 - X1), Y1).into()),
PathElement::LineTo((X0, Y1).into()),
PathElement::Close,
];
sb.push_layer(Mix::Clip.into(), Affine::IDENTITY, path);
}
let rect = Rect {
min: Point::new(X0, Y0),
max: Point::new(X1, Y1),
};
sb.fill(
Fill::NonZero,
Affine::IDENTITY,
&Brush::Solid(Color::rgb8(0, 0, 0)),
None,
rect.elements(),
);
for _ in 0..N {
sb.pop_layer();
}
}
#[allow(unused)]
fn render_alpha_test(sb: &mut SceneBuilder) {
// Alpha compositing tests.
sb.fill(
Fill::NonZero,
Affine::IDENTITY,
&Color::rgb8(255, 0, 0).into(),
None,
make_diamond(1024.0, 100.0),
);
sb.fill(
Fill::NonZero,
Affine::IDENTITY,
&Color::rgba8(0, 255, 0, 0x80).into(),
None,
make_diamond(1024.0, 125.0),
);
sb.push_layer(
Mix::Clip.into(),
Affine::IDENTITY,
make_diamond(1024.0, 150.0),
);
sb.fill(
Fill::NonZero,
Affine::IDENTITY,
&Color::rgba8(0, 0, 255, 0x80).into(),
None,
make_diamond(1024.0, 175.0),
);
sb.pop_layer();
}
#[allow(unused)]
pub fn render_blend_grid(sb: &mut SceneBuilder) {
const BLEND_MODES: &[Mix] = &[
Mix::Normal,
Mix::Multiply,
Mix::Darken,
Mix::Screen,
Mix::Lighten,
Mix::Overlay,
Mix::ColorDodge,
Mix::ColorBurn,
Mix::HardLight,
Mix::SoftLight,
Mix::Difference,
Mix::Exclusion,
Mix::Hue,
Mix::Saturation,
Mix::Color,
Mix::Luminosity,
];
for (ix, &blend) in BLEND_MODES.iter().enumerate() {
let i = ix % 4;
let j = ix / 4;
let transform = Affine::translate(i as f32 * 225., j as f32 * 225.);
let square = blend_square(blend.into());
sb.append(&square, Some(transform));
}
}
#[allow(unused)]
fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affine) {
// Inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
let rect = Rect::from_origin_size(Point::new(0., 0.), 200., 200.);
let stops = &[
GradientStop {
color: Color::rgb8(0, 0, 0),
offset: 0.0,
},
GradientStop {
color: Color::rgb8(255, 255, 255),
offset: 1.0,
},
][..];
let linear = Brush::LinearGradient(LinearGradient {
start: Point::new(0.0, 0.0),
end: Point::new(200.0, 0.0),
stops: stops.into(),
extend: ExtendMode::Pad,
});
sb.fill(Fill::NonZero, transform, &linear, None, rect.elements());
const GRADIENTS: &[(f32, f32, Color)] = &[
(150., 0., Color::rgb8(255, 240, 64)),
(175., 100., Color::rgb8(255, 96, 240)),
(125., 200., Color::rgb8(64, 192, 255)),
];
for (x, y, c) in GRADIENTS {
let mut color2 = c.clone();
color2.a = 0;
let stops = &[
GradientStop {
color: c.clone(),
offset: 0.0,
},
GradientStop {
color: color2,
offset: 1.0,
},
][..];
let rad = Brush::RadialGradient(RadialGradient {
center0: Point::new(*x, *y),
center1: Point::new(*x, *y),
radius0: 0.0,
radius1: 100.0,
stops: stops.into(),
extend: ExtendMode::Pad,
});
sb.fill(Fill::NonZero, transform, &rad, None, rect.elements());
}
const COLORS: &[Color] = &[
Color::rgb8(255, 0, 0),
Color::rgb8(0, 255, 0),
Color::rgb8(0, 0, 255),
];
sb.push_layer(Mix::Normal.into(), transform, rect.elements());
for (i, c) in COLORS.iter().enumerate() {
let stops = &[
GradientStop {
color: Color::rgb8(255, 255, 255),
offset: 0.0,
},
GradientStop {
color: c.clone(),
offset: 1.0,
},
][..];
let linear = Brush::LinearGradient(LinearGradient {
start: Point::new(0.0, 0.0),
end: Point::new(0.0, 200.0),
stops: stops.into(),
extend: ExtendMode::Pad,
});
sb.push_layer(blend, transform, rect.elements());
// squash the ellipse
let a = transform
* Affine::translate(100., 100.)
* Affine::rotate(std::f32::consts::FRAC_PI_3 * (i * 2 + 1) as f32)
* Affine::scale(1.0, 0.357)
* Affine::translate(-100., -100.);
sb.fill(
Fill::NonZero,
a,
&linear,
None,
make_ellipse(100., 100., 90., 90.),
);
sb.pop_layer();
}
sb.pop_layer();
}
#[allow(unused)]
fn blend_square(blend: BlendMode) -> SceneFragment {
let mut fragment = SceneFragment::default();
let mut sb = SceneBuilder::for_fragment(&mut fragment);
render_blend_square(&mut sb, blend, Affine::IDENTITY);
sb.finish();
fragment
}
#[allow(unused)]
pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) {
sb.fill(
Fill::NonZero,
Affine::IDENTITY,
&Brush::Solid(Color::rgb8(128, 128, 128)),
None,
Rect::from_origin_size(Point::new(0.0, 0.0), 1000.0, 1000.0).elements(),
);
let text_size = 60.0 + 40.0 * (0.01 * i as f32).sin();
let s = "\u{1f600}hello piet-gpu text!";
text.add(
sb,
None,
text_size,
None,
Affine::translate(110.0, 600.0),
s,
);
text.add(
sb,
None,
text_size,
None,
Affine::translate(110.0, 700.0),
s,
);
let th = (std::f32::consts::PI / 180.0) * (i as f32);
let center = Point::new(500.0, 500.0);
let mut p1 = center;
p1.x += 400.0 * th.cos();
p1.y += 400.0 * th.sin();
sb.stroke(
&simple_stroke(5.0),
Affine::IDENTITY,
&Brush::Solid(Color::rgb8(128, 0, 0)),
None,
&[PathElement::MoveTo(center), PathElement::LineTo(p1)],
);
}
#[allow(unused)]
pub fn render_brush_transform(sb: &mut SceneBuilder, i: usize) {
let th = (std::f32::consts::PI / 180.0) * (i as f32);
let stops = &[
GradientStop {
color: Color::rgb8(255, 0, 0),
offset: 0.0,
},
GradientStop {
color: Color::rgb8(0, 255, 0),
offset: 0.5,
},
GradientStop {
color: Color::rgb8(0, 0, 255),
offset: 1.0,
},
][..];
let linear = LinearGradient {
start: Point::new(0.0, 0.0),
end: Point::new(0.0, 200.0),
stops: stops.into(),
extend: ExtendMode::Pad,
}
.into();
sb.fill(
Fill::NonZero,
Affine::translate(200.0, 200.0),
&linear,
Some(Affine::rotate(th).around_center(200.0, 100.0)),
Rect::from_origin_size(Point::default(), 400.0, 200.0).elements(),
);
sb.stroke(
&simple_stroke(40.0),
Affine::translate(800.0, 200.0),
&linear,
Some(Affine::rotate(th).around_center(200.0, 100.0)),
Rect::from_origin_size(Point::default(), 400.0, 200.0).elements(),
);
}
fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator<Item = PathElement> + 'a + Clone {
path.elements()
.iter()
.map(|el| PathElement::from_kurbo(*el))
}
fn make_ellipse(cx: f32, cy: f32, rx: f32, ry: f32) -> impl Iterator<Item = PathElement> + Clone {
let a = 0.551915024494;
let arx = a * rx;
let ary = a * ry;
let elements = [
PathElement::MoveTo(Point::new(cx + rx, cy)),
PathElement::CurveTo(
Point::new(cx + rx, cy + ary),
Point::new(cx + arx, cy + ry),
Point::new(cx, cy + ry),
),
PathElement::CurveTo(
Point::new(cx - arx, cy + ry),
Point::new(cx - rx, cy + ary),
Point::new(cx - rx, cy),
),
PathElement::CurveTo(
Point::new(cx - rx, cy - ary),
Point::new(cx - arx, cy - ry),
Point::new(cx, cy - ry),
),
PathElement::CurveTo(
Point::new(cx + arx, cy - ry),
Point::new(cx + rx, cy - ary),
Point::new(cx + rx, cy),
),
PathElement::Close,
];
(0..elements.len()).map(move |i| elements[i])
}
fn make_diamond(cx: f32, cy: f32) -> impl Iterator<Item = PathElement> + Clone {
const SIZE: f32 = 50.0;
let elements = [
PathElement::MoveTo(Point::new(cx, cy - SIZE)),
PathElement::LineTo(Point::new(cx + SIZE, cy)),
PathElement::LineTo(Point::new(cx, cy + SIZE)),
PathElement::LineTo(Point::new(cx - SIZE, cy)),
PathElement::Close,
];
(0..elements.len()).map(move |i| elements[i])
}
fn simple_stroke(width: f32) -> Stroke<[f32; 0]> {
Stroke {
width,
join: Join::Round,
miter_limit: 1.4,
start_cap: Cap::Round,
end_cap: Cap::Round,
dash_pattern: [],
dash_offset: 0.0,
scale: true,
}
}

View file

@ -0,0 +1,82 @@
// 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_scene::glyph::{pinot, pinot::TableProvider, GlyphContext};
use piet_scene::{Affine, Brush, SceneBuilder};
pub use pinot::FontRef;
// This is very much a hack to get things working.
// On Windows, can set this to "c:\\Windows\\Fonts\\seguiemj.ttf" to get color emoji
const FONT_DATA: &[u8] = include_bytes!("../third-party/Roboto-Regular.ttf");
pub struct SimpleText {
gcx: GlyphContext,
}
impl SimpleText {
pub fn new() -> Self {
Self {
gcx: GlyphContext::new(),
}
}
pub fn add(
&mut self,
builder: &mut SceneBuilder,
font: Option<&FontRef>,
size: f32,
brush: Option<&Brush>,
transform: Affine,
text: &str,
) {
let font = font.unwrap_or(&FontRef {
data: FONT_DATA,
offset: 0,
});
if let Some(cmap) = font.cmap() {
if let Some(hmtx) = font.hmtx() {
let upem = font.head().map(|head| head.units_per_em()).unwrap_or(1000) as f32;
let scale = size / upem;
let vars: [(pinot::types::Tag, f32); 0] = [];
let mut provider = self.gcx.new_provider(font, None, size, false, vars);
let hmetrics = hmtx.hmetrics();
let default_advance = hmetrics
.get(hmetrics.len().saturating_sub(1))
.map(|h| h.advance_width)
.unwrap_or(0);
let mut pen_x = 0f32;
for ch in text.chars() {
let gid = cmap.map(ch as u32).unwrap_or(0);
let advance = hmetrics
.get(gid as usize)
.map(|h| h.advance_width)
.unwrap_or(default_advance) as f32
* scale;
if let Some(glyph) = provider.get(gid, brush) {
if !glyph.is_empty() {
let xform = transform
* Affine::translate(pen_x, 0.0)
* Affine::scale(1.0, -1.0);
builder.append(&glyph, Some(xform));
}
}
pen_x += advance;
}
}
}
}
}

View file

@ -18,8 +18,6 @@
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use piet::kurbo::Affine;
/// An affine transform. /// An affine transform.
// This is equivalent to the version in piet-gpu-types, but the bytemuck // This is equivalent to the version in piet-gpu-types, but the bytemuck
// representation will likely be faster. // representation will likely be faster.
@ -35,23 +33,4 @@ impl Transform {
mat: [1.0, 0.0, 0.0, 1.0], mat: [1.0, 0.0, 0.0, 1.0],
translate: [0.0, 0.0], translate: [0.0, 0.0],
}; };
pub fn from_kurbo(a: Affine) -> Transform {
let c = a.as_coeffs();
Transform {
mat: [c[0] as f32, c[1] as f32, c[2] as f32, c[3] as f32],
translate: [c[4] as f32, c[5] as f32],
}
}
pub fn to_kurbo(self) -> Affine {
Affine::new([
self.mat[0] as f64,
self.mat[1] as f64,
self.mat[2] as f64,
self.mat[3] as f64,
self.translate[0] as f64,
self.translate[1] as f64,
])
}
} }

View file

@ -1,332 +0,0 @@
//! Various synthetic scenes for exercising the renderer.
use rand::{Rng, RngCore};
use crate::{Blend, BlendMode, Colrv1RadialGradient, PietGpuRenderContext};
use piet::kurbo::{Affine, BezPath, Circle, Line, Point, Rect, Shape};
use piet::{
Color, GradientStop, LinearGradient, Text, TextAttribute, TextLayoutBuilder, UnitPoint,
};
use crate::{PicoSvg, RenderContext, Vec2};
const N_CIRCLES: usize = 0;
pub fn render_blend_test(rc: &mut PietGpuRenderContext, i: usize, blend: Blend) {
rc.fill(Rect::new(400., 400., 800., 800.), &Color::rgb8(0, 0, 200));
rc.save().unwrap();
rc.blend(Rect::new(0., 0., 1000., 1000.), blend);
rc.transform(Affine::translate(Vec2::new(600., 600.)) * Affine::rotate(0.01 * i as f64));
rc.fill(Rect::new(0., 0., 400., 400.), &Color::rgba8(255, 0, 0, 255));
rc.restore().unwrap();
}
pub fn render_svg(rc: &mut impl RenderContext, svg: &PicoSvg) {
let start = std::time::Instant::now();
svg.render(rc);
println!("flattening and encoding time: {:?}", start.elapsed());
}
pub fn render_scene(rc: &mut PietGpuRenderContext) {
const WIDTH: usize = 2048;
const HEIGHT: usize = 1536;
let mut rng = rand::thread_rng();
for _ in 0..N_CIRCLES {
let color = Color::from_rgba32_u32(rng.next_u32());
let center = Point::new(
rng.gen_range(0.0..WIDTH as f64),
rng.gen_range(0.0..HEIGHT as f64),
);
let radius = rng.gen_range(0.0..50.0);
let circle = Circle::new(center, radius);
rc.fill(circle, &color);
}
let _ = rc.save();
let mut path = BezPath::new();
path.move_to((200.0, 150.0));
path.line_to((100.0, 200.0));
path.line_to((150.0, 250.0));
path.close_path();
rc.clip(path);
let mut path = BezPath::new();
path.move_to((100.0, 150.0));
path.line_to((200.0, 200.0));
path.line_to((150.0, 250.0));
path.close_path();
rc.fill(path, &Color::rgb8(128, 0, 128));
let _ = rc.restore();
rc.stroke(
piet::kurbo::Line::new((100.0, 100.0), (200.0, 150.0)),
&Color::WHITE,
5.0,
);
//render_cardioid(rc);
render_clip_test(rc);
render_alpha_test(rc);
render_gradient_test(rc);
render_text_test(rc);
//render_tiger(rc);
}
#[allow(unused)]
fn render_cardioid(rc: &mut impl RenderContext) {
let n = 601;
let dth = std::f64::consts::PI * 2.0 / (n as f64);
let center = Point::new(1024.0, 768.0);
let r = 750.0;
let mut path = BezPath::new();
for i in 1..n {
let p0 = center + Vec2::from_angle(i as f64 * dth) * r;
let p1 = center + Vec2::from_angle(((i * 2) % n) as f64 * dth) * r;
//rc.fill(&Circle::new(p0, 8.0), &Color::WHITE);
path.move_to(p0);
path.line_to(p1);
//rc.stroke(Line::new(p0, p1), &Color::BLACK, 2.0);
}
rc.stroke(&path, &Color::BLACK, 2.0);
}
#[allow(unused)]
fn render_clip_test(rc: &mut impl RenderContext) {
const N: usize = 16;
const X0: f64 = 50.0;
const Y0: f64 = 450.0;
// Note: if it gets much larger, it will exceed the 1MB scratch buffer.
// But this is a pretty demanding test.
const X1: f64 = 550.0;
const Y1: f64 = 950.0;
let step = 1.0 / ((N + 1) as f64);
for i in 0..N {
let t = ((i + 1) as f64) * step;
rc.save();
let mut path = BezPath::new();
path.move_to((X0, Y0));
path.line_to((X1, Y0));
path.line_to((X1, Y0 + t * (Y1 - Y0)));
path.line_to((X1 + t * (X0 - X1), Y1));
path.line_to((X0, Y1));
path.close_path();
rc.clip(path);
}
let rect = piet::kurbo::Rect::new(X0, Y0, X1, Y1);
rc.fill(rect, &Color::BLACK);
for _ in 0..N {
rc.restore();
}
}
#[allow(unused)]
fn render_alpha_test(rc: &mut impl RenderContext) {
// Alpha compositing tests.
rc.fill(
diamond(Point::new(1024.0, 100.0)),
&Color::Rgba32(0xff0000ff),
);
rc.fill(
diamond(Point::new(1024.0, 125.0)),
&Color::Rgba32(0x00ff0080),
);
rc.save();
rc.clip(diamond(Point::new(1024.0, 150.0)));
rc.fill(
diamond(Point::new(1024.0, 175.0)),
&Color::Rgba32(0x0000ff80),
);
rc.restore();
}
#[allow(unused)]
fn render_gradient_test(rc: &mut PietGpuRenderContext) {
let stops = vec![
GradientStop {
color: Color::rgb8(0, 255, 0),
pos: 0.0,
},
GradientStop {
color: Color::BLACK,
pos: 1.0,
},
];
let rad = Colrv1RadialGradient {
center0: Point::new(200.0, 200.0),
center1: Point::new(250.0, 200.0),
radius0: 50.0,
radius1: 100.0,
stops,
};
let brush = rc.radial_gradient_colrv1(&rad);
//let brush = FixedGradient::Radial(rad);
//let brush = Color::rgb8(0, 128, 0);
let transform = Affine::new([1.0, 0.0, 0.0, 0.5, 0.0, 100.0]);
rc.fill_transform(Rect::new(100.0, 100.0, 300.0, 300.0), &brush, transform);
}
fn diamond(origin: Point) -> impl Shape {
let mut path = BezPath::new();
const SIZE: f64 = 50.0;
path.move_to((origin.x, origin.y - SIZE));
path.line_to((origin.x + SIZE, origin.y));
path.line_to((origin.x, origin.y + SIZE));
path.line_to((origin.x - SIZE, origin.y));
path.close_path();
return path;
}
#[allow(unused)]
fn render_text_test(rc: &mut impl RenderContext) {
rc.save();
//rc.transform(Affine::new([0.2, 0.0, 0.0, -0.2, 200.0, 800.0]));
let layout = rc
.text()
.new_text_layout("\u{1f600}hello piet-gpu text!")
.default_attribute(TextAttribute::FontSize(100.0))
.build()
.unwrap();
rc.draw_text(&layout, Point::new(110.0, 600.0));
rc.draw_text(&layout, Point::new(110.0, 700.0));
rc.restore();
}
#[allow(unused)]
fn render_tiger(rc: &mut impl RenderContext) {
let xml_str = std::str::from_utf8(include_bytes!("../Ghostscript_Tiger.svg")).unwrap();
let start = std::time::Instant::now();
let svg = PicoSvg::load(xml_str, 8.0).unwrap();
println!("parsing time: {:?}", start.elapsed());
let start = std::time::Instant::now();
svg.render(rc);
println!("flattening and encoding time: {:?}", start.elapsed());
}
pub fn render_blend_square(rc: &mut PietGpuRenderContext, blend: Blend) {
// Inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
let rect = Rect::new(0., 0., 200., 200.);
let stops = vec![
GradientStop {
color: Color::BLACK,
pos: 0.0,
},
GradientStop {
color: Color::WHITE,
pos: 1.0,
},
];
let linear = LinearGradient::new(UnitPoint::LEFT, UnitPoint::RIGHT, stops);
rc.fill(rect, &linear);
const GRADIENTS: &[(f64, f64, Color)] = &[
(150., 0., Color::rgb8(255, 240, 64)),
(175., 100., Color::rgb8(255, 96, 240)),
(125., 200., Color::rgb8(64, 192, 255)),
];
for (x, y, c) in GRADIENTS {
let stops = vec![
GradientStop {
color: c.clone(),
pos: 0.0,
},
GradientStop {
color: Color::rgba8(0, 0, 0, 0),
pos: 1.0,
},
];
let rad = Colrv1RadialGradient {
center0: Point::new(*x, *y),
center1: Point::new(*x, *y),
radius0: 0.0,
radius1: 100.0,
stops,
};
let brush = rc.radial_gradient_colrv1(&rad);
rc.fill(Rect::new(0., 0., 200., 200.), &brush);
}
const COLORS: &[Color] = &[
Color::rgb8(255, 0, 0),
Color::rgb8(0, 255, 0),
Color::rgb8(0, 0, 255),
];
let _ = rc.with_save(|rc| {
// Isolation (this can be removed for non-isolated version)
rc.blend(rect, BlendMode::Normal.into());
for (i, c) in COLORS.iter().enumerate() {
let stops = vec![
GradientStop {
color: Color::WHITE,
pos: 0.0,
},
GradientStop {
color: c.clone(),
pos: 1.0,
},
];
// squash the ellipse
let a = Affine::translate((100., 100.))
* Affine::rotate(std::f64::consts::FRAC_PI_3 * (i * 2 + 1) as f64)
* Affine::scale_non_uniform(1.0, 0.357)
* Affine::translate((-100., -100.));
let linear = LinearGradient::new(UnitPoint::TOP, UnitPoint::BOTTOM, stops);
let _ = rc.with_save(|rc| {
rc.blend(rect, blend);
rc.transform(a);
rc.fill(Circle::new((100., 100.), 90.), &linear);
Ok(())
});
}
Ok(())
});
}
pub fn render_blend_grid(rc: &mut PietGpuRenderContext) {
const BLEND_MODES: &[BlendMode] = &[
BlendMode::Normal,
BlendMode::Multiply,
BlendMode::Darken,
BlendMode::Screen,
BlendMode::Lighten,
BlendMode::Overlay,
BlendMode::ColorDodge,
BlendMode::ColorBurn,
BlendMode::HardLight,
BlendMode::SoftLight,
BlendMode::Difference,
BlendMode::Exclusion,
BlendMode::Hue,
BlendMode::Saturation,
BlendMode::Color,
BlendMode::Luminosity,
];
for (ix, &blend) in BLEND_MODES.iter().enumerate() {
let _ = rc.with_save(|rc| {
let i = ix % 4;
let j = ix / 4;
rc.transform(Affine::translate((i as f64 * 225., j as f64 * 225.)));
render_blend_square(rc, blend.into());
Ok(())
});
}
}
pub fn render_anim_frame(rc: &mut impl RenderContext, i: usize) {
rc.fill(
Rect::new(0.0, 0.0, 1000.0, 1000.0),
&Color::rgb8(128, 128, 128),
);
let text_size = 60.0 + 40.0 * (0.01 * i as f64).sin();
rc.save().unwrap();
//rc.transform(Affine::new([0.2, 0.0, 0.0, -0.2, 200.0, 800.0]));
let layout = rc
.text()
.new_text_layout("\u{1f600}hello piet-gpu text!")
.default_attribute(TextAttribute::FontSize(text_size))
.build()
.unwrap();
rc.draw_text(&layout, Point::new(110.0, 600.0));
rc.draw_text(&layout, Point::new(110.0, 700.0));
rc.restore().unwrap();
let th = (std::f64::consts::PI / 180.0) * (i as f64);
let center = Point::new(500.0, 500.0);
let p1 = center + 400.0 * Vec2::from_angle(th);
let line = Line::new(center, p1);
rc.stroke(line, &Color::rgb8(128, 0, 0), 5.0);
}

View file

@ -1,271 +0,0 @@
use std::ops::RangeBounds;
use swash::scale::{ScaleContext, Scaler};
use swash::zeno::{Vector, Verb};
use swash::{FontRef, GlyphId};
use piet::kurbo::{Point, Rect, Size};
use piet::{
Error, FontFamily, HitTestPoint, HitTestPosition, LineMetric, RenderContext, Text,
TextAttribute, TextLayout, TextLayoutBuilder, TextStorage,
};
use crate::encoder::GlyphEncoder;
use crate::render_ctx;
use crate::stages::Transform;
use crate::PietGpuRenderContext;
// This is very much a hack to get things working.
// On Windows, can set this to "c:\\Windows\\Fonts\\seguiemj.ttf" to get color emoji
const FONT_DATA: &[u8] = include_bytes!("../third-party/Roboto-Regular.ttf");
#[derive(Clone)]
pub struct Font {
// Storing the font_ref is ok for static font data, but the better way to do
// this is to store the CacheKey.
font_ref: FontRef<'static>,
}
#[derive(Clone)]
pub struct PietGpuText {
font: Font,
}
#[derive(Clone)]
pub struct PietGpuTextLayout {
font: Font,
size: f64,
glyphs: Vec<Glyph>,
}
pub struct PietGpuTextLayoutBuilder {
font: Font,
text: String,
size: f64,
}
#[derive(Clone, Debug)]
struct Glyph {
glyph_id: GlyphId,
x: f32,
y: f32,
}
struct TextRenderCtx<'a> {
scaler: Scaler<'a>,
}
impl PietGpuText {
pub(crate) fn new(font: Font) -> PietGpuText {
PietGpuText { font }
}
}
impl Text for PietGpuText {
type TextLayout = PietGpuTextLayout;
type TextLayoutBuilder = PietGpuTextLayoutBuilder;
fn load_font(&mut self, _data: &[u8]) -> Result<FontFamily, Error> {
Ok(FontFamily::default())
}
fn new_text_layout(&mut self, text: impl TextStorage) -> Self::TextLayoutBuilder {
PietGpuTextLayoutBuilder::new(&self.font, &text.as_str())
}
fn font_family(&mut self, _family_name: &str) -> Option<FontFamily> {
Some(FontFamily::default())
}
}
impl TextLayout for PietGpuTextLayout {
fn size(&self) -> Size {
Size::ZERO
}
fn image_bounds(&self) -> Rect {
Rect::ZERO
}
fn line_text(&self, _line_number: usize) -> Option<&str> {
None
}
fn line_metric(&self, _line_number: usize) -> Option<LineMetric> {
None
}
fn line_count(&self) -> usize {
0
}
fn hit_test_point(&self, _point: Point) -> HitTestPoint {
HitTestPoint::default()
}
fn hit_test_text_position(&self, _text_position: usize) -> HitTestPosition {
HitTestPosition::default()
}
fn text(&self) -> &str {
""
}
}
impl Font {
pub fn new() -> Font {
let font_ref = FontRef::from_index(FONT_DATA, 0).expect("error parsing font");
Font { font_ref }
}
fn make_path<'a>(&self, glyph_id: GlyphId, tc: &mut TextRenderCtx<'a>) -> GlyphEncoder {
let mut encoder = GlyphEncoder::default();
if tc.scaler.has_color_outlines() {
if let Some(outline) = tc.scaler.scale_color_outline(glyph_id) {
// TODO: be more sophisticated choosing a palette
let palette = self.font_ref.color_palettes().next().unwrap();
let mut i = 0;
while let Some(layer) = outline.get(i) {
if let Some(color_ix) = layer.color_index() {
let color = palette.get(color_ix);
append_outline(&mut encoder, layer.verbs(), layer.points());
encoder.fill_color(*bytemuck::from_bytes(&color));
}
i += 1;
}
return encoder;
}
}
if let Some(outline) = tc.scaler.scale_outline(glyph_id) {
append_outline(&mut encoder, outline.verbs(), outline.points());
}
encoder
}
}
impl PietGpuTextLayout {
pub(crate) fn make_layout(font: &Font, text: &str, size: f64) -> PietGpuTextLayout {
let mut glyphs = Vec::new();
let mut x = 0.0;
let y = 0.0;
for c in text.chars() {
let glyph_id = font.font_ref.charmap().map(c);
let glyph = Glyph { glyph_id, x, y };
glyphs.push(glyph);
let adv = font.font_ref.glyph_metrics(&[]).advance_width(glyph_id);
x += adv;
}
PietGpuTextLayout {
glyphs,
font: font.clone(),
size,
}
}
pub(crate) fn draw_text(&self, ctx: &mut PietGpuRenderContext, pos: Point) {
let mut scale_ctx = ScaleContext::new();
let scaler = scale_ctx.builder(self.font.font_ref).size(2048.).build();
let mut tc = TextRenderCtx { scaler };
// Should we use ppem from font, or let swash scale?
const DEFAULT_UPEM: u16 = 2048;
let scale = self.size as f32 / DEFAULT_UPEM as f32;
ctx.save().unwrap();
// TODO: handle y offsets also
for glyph in &self.glyphs {
let tpos = render_ctx::to_f32_2(pos);
let transform = Transform {
mat: [scale, 0.0, 0.0, -scale],
translate: [tpos[0] + scale * glyph.x, tpos[1]],
};
//println!("{:?}, {:?}", transform.mat, transform.translate);
ctx.encode_transform(transform);
let glyph = self.font.make_path(glyph.glyph_id, &mut tc);
ctx.encode_glyph(&glyph);
if !glyph.is_color() {
ctx.fill_glyph(0xff_ff_ff_ff);
}
}
ctx.restore().unwrap();
}
}
impl PietGpuTextLayoutBuilder {
pub(crate) fn new(font: &Font, text: &str) -> PietGpuTextLayoutBuilder {
PietGpuTextLayoutBuilder {
font: font.clone(),
text: text.to_owned(),
size: 12.0,
}
}
}
impl TextLayoutBuilder for PietGpuTextLayoutBuilder {
type Out = PietGpuTextLayout;
fn max_width(self, _width: f64) -> Self {
self
}
fn alignment(self, _alignment: piet::TextAlignment) -> Self {
self
}
fn default_attribute(mut self, attribute: impl Into<TextAttribute>) -> Self {
let attribute = attribute.into();
match attribute {
TextAttribute::FontSize(size) => self.size = size,
_ => (),
}
self
}
fn range_attribute(
self,
_range: impl RangeBounds<usize>,
_attribute: impl Into<TextAttribute>,
) -> Self {
self
}
fn build(self) -> Result<Self::Out, Error> {
Ok(PietGpuTextLayout::make_layout(
&self.font, &self.text, self.size,
))
}
}
pub(crate) fn append_outline(encoder: &mut GlyphEncoder, verbs: &[Verb], points: &[Vector]) {
let mut path_encoder = encoder.path_encoder();
let mut i = 0;
for verb in verbs {
match verb {
Verb::MoveTo => {
let p = points[i];
path_encoder.move_to(p.x, p.y);
i += 1;
}
Verb::LineTo => {
let p = points[i];
path_encoder.line_to(p.x, p.y);
i += 1;
}
Verb::QuadTo => {
let p1 = points[i];
let p2 = points[i + 1];
path_encoder.quad_to(p1.x, p1.y, p2.x, p2.y);
i += 2;
}
Verb::CurveTo => {
let p1 = points[i];
let p2 = points[i + 1];
let p3 = points[i + 2];
path_encoder.cubic_to(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
i += 3;
}
Verb::Close => path_encoder.close_path(),
}
}
path_encoder.path();
let n_pathseg = path_encoder.n_pathseg();
encoder.finish_path(n_pathseg);
}

View file

@ -14,6 +14,7 @@
// //
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
/// 32-bit RGBA color.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct Color { pub struct Color {
pub r: u8, pub r: u8,

View file

@ -15,17 +15,19 @@
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
use super::color::Color; use super::color::Color;
use super::ExtendMode;
use crate::geometry::Point; use crate::geometry::Point;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
/// Offset and color of a transition point in a gradient.
#[derive(Copy, Clone, PartialOrd, Default, Debug)] #[derive(Copy, Clone, PartialOrd, Default, Debug)]
pub struct Stop { pub struct GradientStop {
pub offset: f32, pub offset: f32,
pub color: Color, pub color: Color,
} }
impl Hash for Stop { impl Hash for GradientStop {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.offset.to_bits().hash(state); self.offset.to_bits().hash(state);
self.color.hash(state); self.color.hash(state);
@ -33,46 +35,46 @@ impl Hash for Stop {
} }
// Override PartialEq to use to_bits for the offset to match with the Hash impl // Override PartialEq to use to_bits for the offset to match with the Hash impl
impl std::cmp::PartialEq for Stop { impl std::cmp::PartialEq for GradientStop {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.offset.to_bits() == other.offset.to_bits() && self.color == other.color self.offset.to_bits() == other.offset.to_bits() && self.color == other.color
} }
} }
impl std::cmp::Eq for Stop {} impl std::cmp::Eq for GradientStop {}
pub type StopVec = SmallVec<[Stop; 4]>; /// Collection of gradient stops.
pub type GradientStops = SmallVec<[GradientStop; 4]>;
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Extend {
Pad,
Repeat,
Reflect,
}
/// Definition of a gradient that transitions between two or more colors along
/// a line.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct LinearGradient { pub struct LinearGradient {
pub start: Point, pub start: Point,
pub end: Point, pub end: Point,
pub stops: StopVec, pub stops: GradientStops,
pub extend: Extend, pub extend: ExtendMode,
} }
/// Definition of a gradient that transitions between two or more colors that
/// radiate from an origin.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RadialGradient { pub struct RadialGradient {
pub center0: Point, pub center0: Point,
pub radius0: f32, pub radius0: f32,
pub center1: Point, pub center1: Point,
pub radius1: f32, pub radius1: f32,
pub stops: StopVec, pub stops: GradientStops,
pub extend: Extend, pub extend: ExtendMode,
} }
/// Definition gradient that transitions between two or more colors that rotate
/// around a center point.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SweepGradient { pub struct SweepGradient {
pub center: Point, pub center: Point,
pub start_angle: f32, pub start_angle: f32,
pub end_angle: f32, pub end_angle: f32,
pub stops: StopVec, pub stops: GradientStops,
pub extend: Extend, pub extend: ExtendMode,
} }

View file

@ -18,53 +18,35 @@ use std::result::Result;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc; use std::sync::Arc;
#[derive(Copy, Clone, PartialEq, Eq, Debug)] /// Image data resource.
pub enum Format {
A8,
Rgba8,
}
impl Format {
pub fn data_size(self, width: u32, height: u32) -> Option<usize> {
(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)] #[derive(Clone, Debug)]
pub struct Image(Arc<Inner>); pub struct Image(Arc<Inner>);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct Inner { struct Inner {
id: u64, id: u64,
format: Format,
width: u32, width: u32,
height: u32, height: u32,
data: Vec<u8>, data: Arc<[u8]>,
} }
impl Image { impl Image {
pub fn new( pub fn new(
format: Format,
width: u32, width: u32,
height: u32, height: u32,
mut data: Vec<u8>, data: impl Into<Arc<[u8]>>,
) -> Result<Self, DataSizeError> { ) -> Result<Self, ImageDataSizeError> {
let data_size = format.data_size(width, height).ok_or(DataSizeError)?; let data_size = width
.checked_mul(height)
.and_then(|x| x.checked_mul(4))
.ok_or(ImageDataSizeError)? as usize;
let data = data.into();
if data.len() < data_size { if data.len() < data_size {
return Err(DataSizeError); return Err(ImageDataSizeError);
} }
data.truncate(data_size);
static ID: AtomicU64 = AtomicU64::new(1); static ID: AtomicU64 = AtomicU64::new(1);
Ok(Self(Arc::new(Inner { Ok(Self(Arc::new(Inner {
id: ID.fetch_add(1, Ordering::Relaxed), id: ID.fetch_add(1, Ordering::Relaxed),
format,
width, width,
height, height,
data, data,
@ -75,10 +57,6 @@ impl Image {
self.0.id self.0.id
} }
pub fn format(&self) -> Format {
self.0.format
}
pub fn width(&self) -> u32 { pub fn width(&self) -> u32 {
self.0.width self.0.width
} }
@ -92,5 +70,7 @@ impl Image {
} }
} }
/// Error returned when image data size is not sufficient for the specified
/// dimensions.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DataSizeError; pub struct ImageDataSizeError;

View file

@ -22,8 +22,7 @@ pub use color::Color;
pub use gradient::*; pub use gradient::*;
pub use image::*; pub use image::*;
use crate::resource::PersistentBrush; /// Describes the content of a filled or stroked shape.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Brush { pub enum Brush {
Solid(Color), Solid(Color),
@ -31,5 +30,31 @@ pub enum Brush {
RadialGradient(RadialGradient), RadialGradient(RadialGradient),
SweepGradient(SweepGradient), SweepGradient(SweepGradient),
Image(Image), Image(Image),
Persistent(PersistentBrush), }
/// Defines how a brush is extended when the content does not
/// completely fill a shape.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ExtendMode {
Pad,
Repeat,
Reflect,
}
impl From<Color> for Brush {
fn from(c: Color) -> Self {
Self::Solid(c)
}
}
impl From<LinearGradient> for Brush {
fn from(g: LinearGradient) -> Self {
Self::LinearGradient(g)
}
}
impl From<RadialGradient> for Brush {
fn from(g: RadialGradient) -> Self {
Self::RadialGradient(g)
}
} }

View file

@ -201,6 +201,14 @@ impl Rect {
} }
} }
/// Creates a new rectangle from an origin point and dimensions.
pub fn from_origin_size(origin: Point, width: f32, height: f32) -> Self {
Self {
min: origin,
max: Point::new(origin.x + width, origin.y + height),
}
}
/// Returns the width of the rectangle. /// Returns the width of the rectangle.
pub fn width(&self) -> f32 { pub fn width(&self) -> f32 {
self.max.x - self.min.x self.max.x - self.min.x

View file

@ -14,12 +14,14 @@
// //
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
//! Support for glyph rendering.
pub use moscato::pinot; pub use moscato::pinot;
use crate::brush::{Brush, Color}; use crate::brush::{Brush, Color};
use crate::geometry::Affine; use crate::geometry::Affine;
use crate::path::Element; use crate::path::PathElement;
use crate::scene::{build_fragment, Fill, Fragment}; use crate::scene::{Fill, SceneBuilder, SceneFragment};
use moscato::{Context, Scaler}; use moscato::{Context, Scaler};
use pinot::{types::Tag, FontRef}; use pinot::{types::Tag, FontRef};
@ -81,28 +83,30 @@ pub struct GlyphProvider<'a> {
impl<'a> GlyphProvider<'a> { impl<'a> GlyphProvider<'a> {
/// Returns a scene fragment containing the commands to render the /// Returns a scene fragment containing the commands to render the
/// specified glyph. /// specified glyph.
pub fn get(&mut self, gid: u16) -> Option<Fragment> { pub fn get(&mut self, gid: u16, brush: Option<&Brush>) -> Option<SceneFragment> {
let glyph = self.scaler.glyph(gid)?; let glyph = self.scaler.glyph(gid)?;
let path = glyph.path(0)?; let path = glyph.path(0)?;
let mut fragment = Fragment::default(); let mut fragment = SceneFragment::default();
let mut builder = build_fragment(&mut fragment); let mut builder = SceneBuilder::for_fragment(&mut fragment);
builder.fill( builder.fill(
Fill::NonZero, Fill::NonZero,
&Brush::Solid(Color::rgb8(255, 255, 255)), Affine::IDENTITY,
brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))),
None, None,
convert_path(path.elements()), convert_path(path.elements()),
); );
builder.finish();
Some(fragment) Some(fragment)
} }
/// Returns a scene fragment containing the commands and resources to /// Returns a scene fragment containing the commands and resources to
/// render the specified color glyph. /// render the specified color glyph.
pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option<Fragment> { pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option<SceneFragment> {
use crate::geometry::*; use crate::geometry::*;
use moscato::Command; use moscato::Command;
let glyph = self.scaler.color_glyph(palette_index, gid)?; let glyph = self.scaler.color_glyph(palette_index, gid)?;
let mut fragment = Fragment::default(); let mut fragment = SceneFragment::default();
let mut builder = build_fragment(&mut fragment); let mut builder = SceneBuilder::for_fragment(&mut fragment);
let mut xform_stack: SmallVec<[Affine; 8]> = SmallVec::new(); let mut xform_stack: SmallVec<[Affine; 8]> = SmallVec::new();
for command in glyph.commands() { for command in glyph.commands() {
match command { match command {
@ -122,10 +126,15 @@ impl<'a> GlyphProvider<'a> {
if let Some(xform) = xform_stack.last() { if let Some(xform) = xform_stack.last() {
builder.push_layer( builder.push_layer(
Default::default(), Default::default(),
Affine::IDENTITY,
convert_transformed_path(path.elements(), xform), convert_transformed_path(path.elements(), xform),
); );
} else { } else {
builder.push_layer(Default::default(), convert_path(path.elements())); builder.push_layer(
Default::default(),
Affine::IDENTITY,
convert_path(path.elements()),
);
} }
} }
Command::PopClip => builder.pop_layer(), Command::PopClip => builder.pop_layer(),
@ -137,7 +146,7 @@ impl<'a> GlyphProvider<'a> {
if let Some(xform) = xform_stack.last() { if let Some(xform) = xform_stack.last() {
rect = rect.transform(xform); rect = rect.transform(xform);
} }
builder.push_layer(Default::default(), rect.elements()); builder.push_layer(Default::default(), Affine::IDENTITY, rect.elements());
} }
Command::PopLayer => builder.pop_layer(), Command::PopLayer => builder.pop_layer(),
Command::BeginBlend(bounds, mode) => { Command::BeginBlend(bounds, mode) => {
@ -148,7 +157,7 @@ impl<'a> GlyphProvider<'a> {
if let Some(xform) = xform_stack.last() { if let Some(xform) = xform_stack.last() {
rect = rect.transform(xform); rect = rect.transform(xform);
} }
builder.push_layer(convert_blend(*mode), rect.elements()) builder.push_layer(convert_blend(*mode), Affine::IDENTITY, rect.elements())
} }
Command::EndBlend => builder.pop_layer(), Command::EndBlend => builder.pop_layer(),
Command::SimpleFill(path_index, brush, brush_xform) => { Command::SimpleFill(path_index, brush, brush_xform) => {
@ -158,6 +167,7 @@ impl<'a> GlyphProvider<'a> {
if let Some(xform) = xform_stack.last() { if let Some(xform) = xform_stack.last() {
builder.fill( builder.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
&brush, &brush,
brush_xform.map(|x| x * *xform), brush_xform.map(|x| x * *xform),
convert_transformed_path(path.elements(), xform), convert_transformed_path(path.elements(), xform),
@ -165,65 +175,71 @@ impl<'a> GlyphProvider<'a> {
} else { } else {
builder.fill( builder.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY,
&brush, &brush,
brush_xform, brush_xform,
convert_path(path.elements()), convert_path(path.elements()),
); );
} }
} }
Command::Fill(brush, brush_xform) => { Command::Fill(_brush, _brush_xform) => {
// TODO: this needs to compute a bounding box for // TODO: this needs to compute a bounding box for
// the parent clips // the parent clips
} }
} }
} }
builder.finish();
Some(fragment) Some(fragment)
} }
} }
fn convert_path( fn convert_path(
path: impl Iterator<Item = moscato::Element> + Clone, path: impl Iterator<Item = moscato::Element> + Clone,
) -> impl Iterator<Item = Element> + Clone { ) -> impl Iterator<Item = PathElement> + Clone {
use crate::geometry::Point; use crate::geometry::Point;
path.map(|el| match el { path.map(|el| match el {
moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y)), moscato::Element::MoveTo(p0) => PathElement::MoveTo(Point::new(p0.x, p0.y)),
moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y)), moscato::Element::LineTo(p0) => PathElement::LineTo(Point::new(p0.x, p0.y)),
moscato::Element::QuadTo(p0, p1) => { moscato::Element::QuadTo(p0, p1) => {
Element::QuadTo(Point::new(p0.x, p0.y), Point::new(p1.x, p1.y)) PathElement::QuadTo(Point::new(p0.x, p0.y), Point::new(p1.x, p1.y))
} }
moscato::Element::CurveTo(p0, p1, p2) => Element::CurveTo( moscato::Element::CurveTo(p0, p1, p2) => PathElement::CurveTo(
Point::new(p0.x, p0.y), Point::new(p0.x, p0.y),
Point::new(p1.x, p1.y), Point::new(p1.x, p1.y),
Point::new(p2.x, p2.y), Point::new(p2.x, p2.y),
), ),
moscato::Element::Close => Element::Close, moscato::Element::Close => PathElement::Close,
}) })
} }
fn convert_transformed_path( fn convert_transformed_path(
path: impl Iterator<Item = moscato::Element> + Clone, path: impl Iterator<Item = moscato::Element> + Clone,
xform: &Affine, xform: &Affine,
) -> impl Iterator<Item = Element> + Clone { ) -> impl Iterator<Item = PathElement> + Clone {
use crate::geometry::Point; use crate::geometry::Point;
let xform = *xform; let xform = *xform;
path.map(move |el| match el { path.map(move |el| match el {
moscato::Element::MoveTo(p0) => Element::MoveTo(Point::new(p0.x, p0.y).transform(&xform)), moscato::Element::MoveTo(p0) => {
moscato::Element::LineTo(p0) => Element::LineTo(Point::new(p0.x, p0.y).transform(&xform)), PathElement::MoveTo(Point::new(p0.x, p0.y).transform(&xform))
moscato::Element::QuadTo(p0, p1) => Element::QuadTo( }
moscato::Element::LineTo(p0) => {
PathElement::LineTo(Point::new(p0.x, p0.y).transform(&xform))
}
moscato::Element::QuadTo(p0, p1) => PathElement::QuadTo(
Point::new(p0.x, p0.y).transform(&xform), Point::new(p0.x, p0.y).transform(&xform),
Point::new(p1.x, p1.y).transform(&xform), Point::new(p1.x, p1.y).transform(&xform),
), ),
moscato::Element::CurveTo(p0, p1, p2) => Element::CurveTo( moscato::Element::CurveTo(p0, p1, p2) => PathElement::CurveTo(
Point::new(p0.x, p0.y).transform(&xform), Point::new(p0.x, p0.y).transform(&xform),
Point::new(p1.x, p1.y).transform(&xform), Point::new(p1.x, p1.y).transform(&xform),
Point::new(p2.x, p2.y).transform(&xform), Point::new(p2.x, p2.y).transform(&xform),
), ),
moscato::Element::Close => Element::Close, moscato::Element::Close => PathElement::Close,
}) })
} }
fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::Blend { fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::BlendMode {
use crate::scene::{Blend, Compose, Mix}; use crate::scene::{BlendMode, Compose, Mix};
use moscato::CompositeMode; use moscato::CompositeMode;
let mut mix = Mix::Normal; let mut mix = Mix::Normal;
let mut compose = Compose::SrcOver; let mut compose = Compose::SrcOver;
@ -257,7 +273,7 @@ fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::Blend {
CompositeMode::HslColor => mix = Mix::Color, CompositeMode::HslColor => mix = Mix::Color,
CompositeMode::HslLuminosity => mix = Mix::Luminosity, CompositeMode::HslLuminosity => mix = Mix::Luminosity,
} }
Blend { mix, compose } BlendMode { mix, compose }
} }
fn convert_transform(xform: &moscato::Transform) -> crate::geometry::Affine { fn convert_transform(xform: &moscato::Transform) -> crate::geometry::Affine {
@ -298,11 +314,11 @@ fn convert_brush(brush: &moscato::Brush) -> crate::brush::Brush {
} }
} }
fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::StopVec { fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::GradientStops {
use crate::brush::Stop; use crate::brush::GradientStop;
stops stops
.iter() .iter()
.map(|stop| Stop { .map(|stop| GradientStop {
offset: stop.offset, offset: stop.offset,
color: Color { color: Color {
r: stop.color.r, r: stop.color.r,
@ -314,8 +330,8 @@ fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::StopVec {
.collect() .collect()
} }
fn convert_extend(extend: moscato::ExtendMode) -> crate::brush::Extend { fn convert_extend(extend: moscato::ExtendMode) -> crate::brush::ExtendMode {
use crate::brush::Extend::*; use crate::brush::ExtendMode::*;
match extend { match extend {
moscato::ExtendMode::Pad => Pad, moscato::ExtendMode::Pad => Pad,
moscato::ExtendMode::Repeat => Repeat, moscato::ExtendMode::Repeat => Repeat,

View file

@ -14,19 +14,26 @@
// //
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
pub mod brush; mod brush;
pub mod geometry; mod geometry;
mod path;
mod resource;
mod scene;
pub mod glyph; pub mod glyph;
pub mod path;
pub mod resource; pub use brush::*;
pub mod scene; pub use geometry::*;
pub use path::*;
pub use resource::*;
pub use scene::*;
/// Implement conversions to and from Kurbo types when the `kurbo` feature is /// Implement conversions to and from Kurbo types when the `kurbo` feature is
/// enabled. /// enabled.
#[cfg(feature = "kurbo")] #[cfg(feature = "kurbo")]
mod kurbo_conv { mod kurbo_conv {
use super::geometry::{Affine, Point, Rect}; use super::geometry::{Affine, Point, Rect};
use super::path::Element; use super::path::PathElement;
impl Point { impl Point {
/// Creates a new point from the equivalent kurbo type. /// Creates a new point from the equivalent kurbo type.
@ -90,26 +97,27 @@ mod kurbo_conv {
} }
} }
impl Element { impl PathElement {
/// Creates a new path element from the equivalent kurbo type. /// Creates a new path element from the equivalent kurbo type.
pub fn from_kurbo(el: kurbo::PathEl) -> Self { pub fn from_kurbo(el: kurbo::PathEl) -> Self {
use kurbo::PathEl::*; use kurbo::PathEl::*;
use Point::from_kurbo; match el {
match e { MoveTo(p0) => Self::MoveTo(Point::from_kurbo(p0)),
MoveTo(p0) => Self::MoveTo(from_kurbo(p0)), LineTo(p0) => Self::LineTo(Point::from_kurbo(p0)),
LineTo(p0) => Self::LineTo(from_kurbo(p0)), QuadTo(p0, p1) => Self::QuadTo(Point::from_kurbo(p0), Point::from_kurbo(p1)),
QuadTo(p0, p1) => Self::QuadTo(from_kurbo(p0), from_kurbo(p1)), CurveTo(p0, p1, p2) => Self::CurveTo(
CurveTo(p0, p1, p2) => { Point::from_kurbo(p0),
Self::CurveTo(from_kurbo(p0), from_kurbo(p1), from_kurbo(p2)) Point::from_kurbo(p1),
} Point::from_kurbo(p2),
),
ClosePath => Self::Close, ClosePath => Self::Close,
} }
} }
} }
impl From<Element> for kurbo::PathEl { impl From<PathElement> for kurbo::PathEl {
fn from(e: Element) -> Self { fn from(e: PathElement) -> Self {
use Element::*; use PathElement::*;
match e { match e {
MoveTo(p0) => Self::MoveTo(p0.into()), MoveTo(p0) => Self::MoveTo(p0.into()),
LineTo(p0) => Self::LineTo(p0.into()), LineTo(p0) => Self::LineTo(p0.into()),

View file

@ -18,7 +18,7 @@ use super::geometry::{Point, Rect};
/// Action of a path element. /// Action of a path element.
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Verb { pub enum PathVerb {
MoveTo, MoveTo,
LineTo, LineTo,
QuadTo, QuadTo,
@ -28,7 +28,7 @@ pub enum Verb {
/// Element of a path represented by a verb and its associated points. /// Element of a path represented by a verb and its associated points.
#[derive(Copy, Clone, PartialEq, Debug)] #[derive(Copy, Clone, PartialEq, Debug)]
pub enum Element { pub enum PathElement {
MoveTo(Point), MoveTo(Point),
LineTo(Point), LineTo(Point),
QuadTo(Point, Point), QuadTo(Point, Point),
@ -36,27 +36,27 @@ pub enum Element {
Close, Close,
} }
impl Element { impl PathElement {
/// Returns the verb that describes the action of the path element. /// Returns the verb that describes the action of the path element.
pub fn verb(&self) -> Verb { pub fn verb(&self) -> PathVerb {
match self { match self {
Self::MoveTo(..) => Verb::MoveTo, Self::MoveTo(..) => PathVerb::MoveTo,
Self::LineTo(..) => Verb::LineTo, Self::LineTo(..) => PathVerb::LineTo,
Self::QuadTo(..) => Verb::QuadTo, Self::QuadTo(..) => PathVerb::QuadTo,
Self::CurveTo(..) => Verb::CurveTo, Self::CurveTo(..) => PathVerb::CurveTo,
Self::Close => Verb::Close, Self::Close => PathVerb::Close,
} }
} }
} }
impl Rect { impl Rect {
pub fn elements(&self) -> impl Iterator<Item = Element> + Clone { pub fn elements(&self) -> impl Iterator<Item = PathElement> + Clone {
let elements = [ let elements = [
Element::MoveTo((self.min.x, self.min.y).into()), PathElement::MoveTo((self.min.x, self.min.y).into()),
Element::LineTo((self.max.x, self.min.y).into()), PathElement::LineTo((self.max.x, self.min.y).into()),
Element::LineTo((self.max.x, self.max.y).into()), PathElement::LineTo((self.max.x, self.max.y).into()),
Element::LineTo((self.min.x, self.max.y).into()), PathElement::LineTo((self.min.x, self.max.y).into()),
Element::Close, PathElement::Close,
]; ];
(0..5).map(move |i| elements[i]) (0..5).map(move |i| elements[i])
} }

View file

@ -0,0 +1,31 @@
use crate::brush::GradientStop;
use core::ops::Range;
#[derive(Default)]
/// Collection of late bound resources for a scene or scene fragment.
pub struct ResourceBundle {
/// Sequence of resource patches.
pub patches: Vec<ResourcePatch>,
/// Cache of gradient stops, referenced by range from the patches.
pub stops: Vec<GradientStop>,
}
impl ResourceBundle {
/// Clears the resource set.
pub(crate) fn clear(&mut self) {
self.patches.clear();
self.stops.clear();
}
}
#[derive(Clone)]
/// Description of a late bound resource.
pub enum ResourcePatch {
/// Gradient ramp resource.
Ramp {
/// Byte offset to the ramp id in the draw data stream.
offset: usize,
/// Range of the gradient stops in the resource set.
stops: Range<usize>,
},
}

View file

@ -1,56 +0,0 @@
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<u64, PersistentBrushData>,
}
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,
}

View file

@ -60,12 +60,12 @@ pub enum Compose {
/// Blend mode consisting of mixing and composition functions. /// Blend mode consisting of mixing and composition functions.
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Blend { pub struct BlendMode {
pub mix: Mix, pub mix: Mix,
pub compose: Compose, pub compose: Compose,
} }
impl Blend { impl BlendMode {
pub fn new(mix: Mix, compose: Compose) -> Self { pub fn new(mix: Mix, compose: Compose) -> Self {
Self { mix, compose } Self { mix, compose }
} }
@ -75,7 +75,7 @@ impl Blend {
} }
} }
impl Default for Blend { impl Default for BlendMode {
fn default() -> Self { fn default() -> Self {
Self { Self {
mix: Mix::Clip, mix: Mix::Clip,
@ -84,7 +84,7 @@ impl Default for Blend {
} }
} }
impl From<Mix> for Blend { impl From<Mix> for BlendMode {
fn from(mix: Mix) -> Self { fn from(mix: Mix) -> Self {
Self { Self {
mix, mix,
@ -93,7 +93,7 @@ impl From<Mix> for Blend {
} }
} }
impl From<Compose> for Blend { impl From<Compose> for BlendMode {
fn from(compose: Compose) -> Self { fn from(compose: Compose) -> Self {
Self { Self {
mix: Mix::Normal, mix: Mix::Normal,

View file

@ -15,72 +15,53 @@
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
use super::style::{Fill, Stroke}; use super::style::{Fill, Stroke};
use super::{Affine, Blend, Element, Fragment, FragmentResources, ResourcePatch, Scene, SceneData}; use super::{Affine, BlendMode, PathElement, Scene, SceneData, SceneFragment};
use crate::brush::*; use crate::{brush::*, ResourcePatch};
use crate::resource::ResourceContext;
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use core::borrow::Borrow; use core::borrow::Borrow;
use smallvec::SmallVec;
const MAX_BLEND_STACK: usize = 256;
/// Creates a new builder for filling a scene. Any current content in the scene
/// will be cleared.
pub fn build_scene<'a>(scene: &'a mut Scene, rcx: &'a mut ResourceContext) -> Builder<'a> {
Builder::new(&mut scene.data, ResourceData::Scene(rcx))
}
/// Creates a new builder for filling a scene fragment. Any current content in
/// the fragment will be cleared.
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. /// Builder for constructing a scene or scene fragment.
pub struct Builder<'a> { pub struct SceneBuilder<'a> {
scene: &'a mut SceneData, scene: &'a mut SceneData,
resources: ResourceData<'a>, layers: SmallVec<[BlendMode; 8]>,
layers: Vec<Blend>,
} }
impl<'a> Builder<'a> { impl<'a> SceneBuilder<'a> {
/// Creates a new builder for constructing a scene. /// Creates a new builder for filling a scene. Any current content in the scene
fn new(scene: &'a mut SceneData, mut resources: ResourceData<'a>) -> Self { /// will be cleared.
let is_fragment = match resources { pub fn for_scene(scene: &'a mut Scene) -> Self {
ResourceData::Fragment(_) => true, Self::new(&mut scene.data, false)
_ => false,
};
scene.reset(is_fragment);
resources.clear();
Self {
scene,
resources,
layers: vec![],
}
} }
/// Sets the current transformation. /// Creates a new builder for filling a scene fragment. Any current content in
pub fn transform(&mut self, transform: Affine) { /// the fragment will be cleared.
self.encode_transform(transform); pub fn for_fragment(fragment: &'a mut SceneFragment) -> Self {
Self::new(&mut fragment.data, true)
}
/// Creates a new builder for constructing a scene.
fn new(scene: &'a mut SceneData, is_fragment: bool) -> Self {
scene.reset(is_fragment);
Self {
scene,
layers: Default::default(),
}
} }
/// Pushes a new layer bound by the specifed shape and composed with /// Pushes a new layer bound by the specifed shape and composed with
/// previous layers using the specified blend mode. /// previous layers using the specified blend mode.
pub fn push_layer<'s, E>(&mut self, blend: Blend, elements: E) pub fn push_layer<'s, E>(&mut self, blend: BlendMode, transform: Affine, elements: E)
where where
E: IntoIterator, E: IntoIterator,
E::IntoIter: Clone, E::IntoIter: Clone,
E::Item: Borrow<Element>, E::Item: Borrow<PathElement>,
{ {
self.maybe_encode_transform(transform);
self.linewidth(-1.0); self.linewidth(-1.0);
let elements = elements.into_iter(); let elements = elements.into_iter();
self.encode_path(elements, true); self.encode_path(elements, true);
self.begin_clip(Some(blend)); 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); self.layers.push(blend);
} }
@ -94,32 +75,27 @@ impl<'a> Builder<'a> {
/// Fills a shape using the specified style and brush. /// Fills a shape using the specified style and brush.
pub fn fill<'s, E>( pub fn fill<'s, E>(
&mut self, &mut self,
style: Fill, _style: Fill,
transform: Affine,
brush: &Brush, brush: &Brush,
brush_transform: Option<Affine>, brush_transform: Option<Affine>,
elements: E, elements: E,
) where ) where
E: IntoIterator, E: IntoIterator,
E::IntoIter: Clone, E::IntoIter: Clone,
E::Item: Borrow<Element>, E::Item: Borrow<PathElement>,
{ {
self.maybe_encode_transform(transform);
self.linewidth(-1.0); self.linewidth(-1.0);
let elements = elements.into_iter(); let elements = elements.into_iter();
self.encode_path(elements, true); if self.encode_path(elements, true) {
if let Some(brush_transform) = brush_transform { if let Some(brush_transform) = brush_transform {
if let Some(last_transform) = self.scene.transform_stream.last().copied() { self.encode_transform(transform * brush_transform);
self.encode_transform(brush_transform * last_transform);
self.swap_last_tags(); self.swap_last_tags();
self.encode_brush(brush); self.encode_brush(brush);
self.encode_transform(last_transform);
} else { } else {
self.encode_transform(brush_transform);
self.swap_last_tags();
self.encode_brush(brush); self.encode_brush(brush);
self.encode_transform(Affine::IDENTITY);
} }
} else {
self.encode_brush(brush);
} }
} }
@ -127,6 +103,7 @@ impl<'a> Builder<'a> {
pub fn stroke<'s, D, E>( pub fn stroke<'s, D, E>(
&mut self, &mut self,
style: &Stroke<D>, style: &Stroke<D>,
transform: Affine,
brush: &Brush, brush: &Brush,
brush_transform: Option<Affine>, brush_transform: Option<Affine>,
elements: E, elements: E,
@ -134,78 +111,25 @@ impl<'a> Builder<'a> {
D: Borrow<[f32]>, D: Borrow<[f32]>,
E: IntoIterator, E: IntoIterator,
E::IntoIter: Clone, E::IntoIter: Clone,
E::Item: Borrow<Element>, E::Item: Borrow<PathElement>,
{ {
self.maybe_encode_transform(transform);
self.linewidth(style.width); self.linewidth(style.width);
let elements = elements.into_iter(); let elements = elements.into_iter();
self.encode_path(elements, false); if self.encode_path(elements, false) {
if let Some(brush_transform) = brush_transform { if let Some(brush_transform) = brush_transform {
if let Some(last_transform) = self.scene.transform_stream.last().copied() { self.encode_transform(transform * brush_transform);
self.encode_transform(brush_transform * last_transform);
self.swap_last_tags(); self.swap_last_tags();
self.encode_brush(brush); self.encode_brush(brush);
self.encode_transform(last_transform);
} else { } else {
self.encode_transform(brush_transform);
self.swap_last_tags();
self.encode_brush(brush); self.encode_brush(brush);
self.encode_transform(Affine::IDENTITY);
} }
} else {
self.encode_brush(brush);
} }
} }
/// Appends a fragment to the scene. /// Appends a fragment to the scene.
pub fn append(&mut self, fragment: &Fragment, transform: Option<Affine>) { pub fn append(&mut self, fragment: &SceneFragment, transform: Option<Affine>) {
let drawdata_base = self.scene.drawdata_stream.len();
let mut cur_transform = self.scene.transform_stream.last().copied();
if let Some(transform) = transform {
if cur_transform.is_none() {
cur_transform = Some(Affine::IDENTITY);
}
self.encode_transform(transform);
} else if cur_transform != Some(Affine::IDENTITY) {
self.encode_transform(Affine::IDENTITY);
}
self.scene.append(&fragment.data, &transform); self.scene.append(&fragment.data, &transform);
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,
},
},
));
}
}
// Prevent fragments from affecting transform state. Should we allow this?
if let Some(transform) = cur_transform {
self.encode_transform(transform);
}
} }
/// Completes construction and finalizes the underlying scene. /// Completes construction and finalizes the underlying scene.
@ -216,11 +140,11 @@ impl<'a> Builder<'a> {
} }
} }
impl<'a> Builder<'a> { impl<'a> SceneBuilder<'a> {
fn encode_path<E>(&mut self, elements: E, is_fill: bool) fn encode_path<E>(&mut self, elements: E, is_fill: bool) -> bool
where where
E: Iterator, E: Iterator,
E::Item: Borrow<Element>, E::Item: Borrow<PathElement>,
{ {
if is_fill { if is_fill {
self.encode_path_inner( self.encode_path_inner(
@ -228,34 +152,45 @@ impl<'a> Builder<'a> {
.map(|el| *el.borrow()) .map(|el| *el.borrow())
.flat_map(|el| { .flat_map(|el| {
match el { match el {
Element::MoveTo(..) => Some(Element::Close), PathElement::MoveTo(..) => Some(PathElement::Close),
_ => None, _ => None,
} }
.into_iter() .into_iter()
.chain(Some(el)) .chain(Some(el))
}) })
.chain(Some(Element::Close)), .chain(Some(PathElement::Close)),
) )
} else { } else {
self.encode_path_inner(elements.map(|el| *el.borrow())) self.encode_path_inner(elements.map(|el| *el.borrow()))
} }
} }
fn encode_path_inner(&mut self, elements: impl Iterator<Item = Element>) { fn encode_path_inner(&mut self, elements: impl Iterator<Item = PathElement>) -> bool {
let mut b = PathBuilder::new(&mut self.scene.tag_stream, &mut self.scene.pathseg_stream); let mut b = PathBuilder::new(&mut self.scene.tag_stream, &mut self.scene.pathseg_stream);
let mut has_els = false;
for el in elements { for el in elements {
match el { match el {
Element::MoveTo(p0) => b.move_to(p0.x, p0.y), PathElement::MoveTo(p0) => b.move_to(p0.x, p0.y),
Element::LineTo(p0) => b.line_to(p0.x, p0.y), PathElement::LineTo(p0) => b.line_to(p0.x, p0.y),
Element::QuadTo(p0, p1) => b.quad_to(p0.x, p0.y, p1.x, p1.y), PathElement::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), PathElement::CurveTo(p0, p1, p2) => b.cubic_to(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y),
Element::Close => b.close_path(), PathElement::Close => b.close_path(),
} }
has_els = true;
}
if has_els {
b.path();
let n_pathseg = b.n_pathseg();
self.scene.n_path += 1;
self.scene.n_pathseg += n_pathseg;
}
has_els
}
fn maybe_encode_transform(&mut self, transform: Affine) {
if self.scene.transform_stream.last() != Some(&transform) {
self.encode_transform(transform);
} }
b.path();
let n_pathseg = b.n_pathseg();
self.scene.n_path += 1;
self.scene.n_pathseg += n_pathseg;
} }
fn encode_transform(&mut self, transform: Affine) { fn encode_transform(&mut self, transform: Affine) {
@ -272,8 +207,10 @@ impl<'a> Builder<'a> {
// -1.0 means "fill" // -1.0 means "fill"
fn linewidth(&mut self, linewidth: f32) { fn linewidth(&mut self, linewidth: f32) {
self.scene.tag_stream.push(0x40); if self.scene.linewidth_stream.last() != Some(&linewidth) {
self.scene.linewidth_stream.push(linewidth); self.scene.tag_stream.push(0x40);
self.scene.linewidth_stream.push(linewidth);
}
} }
fn encode_brush(&mut self, brush: &Brush) { fn encode_brush(&mut self, brush: &Brush) {
@ -311,31 +248,26 @@ impl<'a> Builder<'a> {
} }
Brush::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"), Brush::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"),
Brush::Image(_image) => todo!("images 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 { fn add_ramp(&mut self, stops: &[GradientStop]) -> u32 {
match &mut self.resources { let offset = self.scene.drawdata_stream.len();
ResourceData::Scene(res) => res.add_ramp(stops), let resources = &mut self.scene.resources;
ResourceData::Fragment(res) => { let stops_start = resources.stops.len();
let stops_start = res.stops.len(); resources.stops.extend_from_slice(stops);
res.stops.extend_from_slice(stops); resources.patches.push(ResourcePatch::Ramp {
let id = res.patches.len() as u32; offset,
res.patches.push(ResourcePatch::Ramp { stops: stops_start..stops_start + stops.len(),
drawdata_offset: self.scene.drawdata_stream.len(), });
stops: stops_start..stops_start + stops.len(), 0
});
id
}
}
} }
/// Start a clip. /// Start a clip.
fn begin_clip(&mut self, blend: Option<Blend>) { fn begin_clip(&mut self, blend: Option<BlendMode>) {
self.scene.drawtag_stream.push(DRAWTAG_BEGINCLIP); self.scene.drawtag_stream.push(DRAWTAG_BEGINCLIP);
let element = Clip { let element = Clip {
blend: blend.unwrap_or(Blend::default()).pack(), blend: blend.unwrap_or(BlendMode::default()).pack(),
}; };
self.scene self.scene
.drawdata_stream .drawdata_stream
@ -343,10 +275,10 @@ impl<'a> Builder<'a> {
self.scene.n_clip += 1; self.scene.n_clip += 1;
} }
fn end_clip(&mut self, blend: Option<Blend>) { fn end_clip(&mut self, blend: Option<BlendMode>) {
self.scene.drawtag_stream.push(DRAWTAG_ENDCLIP); self.scene.drawtag_stream.push(DRAWTAG_ENDCLIP);
let element = Clip { let element = Clip {
blend: blend.unwrap_or(Blend::default()).pack(), blend: blend.unwrap_or(BlendMode::default()).pack(),
}; };
self.scene self.scene
.drawdata_stream .drawdata_stream
@ -358,23 +290,6 @@ impl<'a> Builder<'a> {
} }
} }
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. // Tags for draw objects. See shader/drawtag.h for the authoritative source.
const DRAWTAG_FILLCOLOR: u32 = 0x44; const DRAWTAG_FILLCOLOR: u32 = 0x44;
const DRAWTAG_FILLLINGRADIENT: u32 = 0x114; const DRAWTAG_FILLLINGRADIENT: u32 = 0x114;

View file

@ -18,15 +18,13 @@ mod blend;
mod builder; mod builder;
mod style; mod style;
pub use blend::{Blend, Compose, Mix}; pub use blend::{BlendMode, Compose, Mix};
pub use builder::{build_fragment, build_scene, Builder}; pub use builder::SceneBuilder;
pub use style::*; pub use style::*;
use super::brush::*;
use super::geometry::{Affine, Point}; use super::geometry::{Affine, Point};
use super::path::Element; use super::path::PathElement;
use super::resource::{ResourceBundle, ResourcePatch};
use core::ops::Range;
/// Raw data streams describing an encoded scene. /// Raw data streams describing an encoded scene.
#[derive(Default)] #[derive(Default)]
@ -40,9 +38,14 @@ pub struct SceneData {
pub n_path: u32, pub n_path: u32,
pub n_pathseg: u32, pub n_pathseg: u32,
pub n_clip: u32, pub n_clip: u32,
pub resources: ResourceBundle,
} }
impl SceneData { impl SceneData {
fn is_empty(&self) -> bool {
self.pathseg_stream.is_empty()
}
fn reset(&mut self, is_fragment: bool) { fn reset(&mut self, is_fragment: bool) {
self.transform_stream.clear(); self.transform_stream.clear();
self.tag_stream.clear(); self.tag_stream.clear();
@ -53,6 +56,7 @@ impl SceneData {
self.n_path = 0; self.n_path = 0;
self.n_pathseg = 0; self.n_pathseg = 0;
self.n_clip = 0; self.n_clip = 0;
self.resources.clear();
if !is_fragment { if !is_fragment {
self.transform_stream self.transform_stream
.push(Affine::new(&[1.0, 0.0, 0.0, 1.0, 0.0, 0.0])); .push(Affine::new(&[1.0, 0.0, 0.0, 1.0, 0.0, 0.0]));
@ -61,9 +65,11 @@ impl SceneData {
} }
fn append(&mut self, other: &SceneData, transform: &Option<Affine>) { fn append(&mut self, other: &SceneData, transform: &Option<Affine>) {
let stops_base = self.resources.stops.len();
let drawdata_base = self.drawdata_stream.len();
if let Some(transform) = *transform { if let Some(transform) = *transform {
self.transform_stream self.transform_stream
.extend(other.transform_stream.iter().map(|x| *x * transform)); .extend(other.transform_stream.iter().map(|x| transform * *x));
} else { } else {
self.transform_stream self.transform_stream
.extend_from_slice(&other.transform_stream); .extend_from_slice(&other.transform_stream);
@ -78,6 +84,20 @@ impl SceneData {
self.n_path += other.n_path; self.n_path += other.n_path;
self.n_pathseg += other.n_pathseg; self.n_pathseg += other.n_pathseg;
self.n_clip += other.n_clip; self.n_clip += other.n_clip;
self.resources
.stops
.extend_from_slice(&other.resources.stops);
self.resources
.patches
.extend(other.resources.patches.iter().map(|patch| match patch {
ResourcePatch::Ramp { offset, stops } => {
let stops = stops.start + stops_base..stops.end + stops_base;
ResourcePatch::Ramp {
offset: drawdata_base + offset,
stops,
}
}
}));
} }
} }
@ -97,28 +117,23 @@ impl Scene {
/// Encoded definition of a scene fragment and associated resources. /// Encoded definition of a scene fragment and associated resources.
#[derive(Default)] #[derive(Default)]
pub struct Fragment { pub struct SceneFragment {
data: SceneData, data: SceneData,
resources: FragmentResources,
} }
impl Fragment { impl SceneFragment {
/// Returns true if the fragment does not contain any paths.
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
/// Returns the underlying stream of points that defined all encoded path /// Returns the underlying stream of points that defined all encoded path
/// segments. /// segments.
pub fn points(&self) -> &[Point] { pub fn points(&self) -> &[Point] {
bytemuck::cast_slice(&self.data.pathseg_stream) if self.is_empty() {
&[]
} else {
bytemuck::cast_slice(&self.data.pathseg_stream)
}
} }
} }
#[derive(Default)]
struct FragmentResources {
patches: Vec<ResourcePatch>,
stops: Vec<Stop>,
}
enum ResourcePatch {
Ramp {
drawdata_offset: usize,
stops: Range<usize>,
},
}