From ec75847123899365ebcedb9d3ed0765b171ea773 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sat, 23 May 2026 20:57:59 +0700 Subject: [PATCH] Update Vello renderer stack for wgpu 29 after new releases --- .github/workflows/ci.yml | 23 +- Cargo.lock | 513 ++++++++++++++++-- imaging_skia/src/lib.rs | 4 +- imaging_snapshot_tests/Cargo.toml | 2 +- imaging_snapshot_tests/src/cases/mod.rs | 6 +- imaging_snapshot_tests/src/cases/text.rs | 19 +- ...gm_glyph_runs_gradient_brush_transform.png | Bin 0 -> 5410 bytes .../vello/gm_glyph_runs_gradient_fill.png | Bin 3409 -> 3755 bytes .../vello/gm_glyph_runs_gradient_stroke.png | Bin 6286 -> 4859 bytes .../vello/gm_glyph_runs_image_fill.png | Bin 4181 -> 3981 bytes .../vello/gm_glyph_runs_image_stroke.png | Bin 7081 -> 6422 bytes .../snapshots/vello/gm_image_brushes.png | Bin 6609 -> 6265 bytes .../snapshots/vello_cpu/gm_blend_grid.png | Bin 17211 -> 17126 bytes .../vello_cpu/gm_clip_stroke_nested.png | Bin 4595 -> 4592 bytes .../snapshots/vello_cpu/gm_image_brushes.png | Bin 7047 -> 7088 bytes ...gm_glyph_runs_gradient_brush_transform.png | Bin 5110 -> 5335 bytes imaging_vello/CHANGELOG.md | 12 + imaging_vello/Cargo.toml | 6 +- imaging_vello/README.md | 7 +- imaging_vello/src/lib.rs | 42 +- imaging_vello/src/scene_sink.rs | 44 +- imaging_vello_cpu/CHANGELOG.md | 9 + imaging_vello_cpu/Cargo.toml | 4 +- imaging_vello_cpu/src/lib.rs | 63 +-- imaging_vello_hybrid/CHANGELOG.md | 9 + imaging_vello_hybrid/Cargo.toml | 9 +- imaging_vello_hybrid/src/image_registry.rs | 32 +- imaging_vello_hybrid/src/lib.rs | 44 +- imaging_vello_hybrid/src/scene_sink.rs | 68 ++- imaging_wgpu/CHANGELOG.md | 9 + imaging_wgpu/Cargo.toml | 10 +- imaging_wgpu/README.md | 12 +- imaging_wgpu/src/lib.rs | 172 +++--- 33 files changed, 860 insertions(+), 259 deletions(-) create mode 100644 imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_gradient_brush_transform.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04cd2f3..5fc5c60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -274,20 +274,26 @@ jobs: if: runner.os == 'macOS' run: cargo nextest run -p imaging_snapshot_tests --locked --all-features --no-fail-fast + - name: cargo nextest (imaging_vello, vello-0-7) + run: cargo nextest run -p imaging_vello --locked --no-default-features --features vello-0-7 --no-fail-fast + - name: cargo nextest (imaging_vello, vello-0-8) run: cargo nextest run -p imaging_vello --locked --no-default-features --features vello-0-8 --no-fail-fast - - name: cargo nextest (imaging_vello, vello-0-7) - run: cargo nextest run -p imaging_vello --locked --no-default-features --features vello-0-7 --no-fail-fast + - name: cargo nextest (imaging_vello, vello-0-9) + run: cargo nextest run -p imaging_vello --locked --no-default-features --features vello-0-9 --no-fail-fast - name: cargo test --doc run: cargo test --doc ${{ env.RUST_ALL_FEATURES_EXCLUDE_PKGS }} --workspace --locked --all-features --no-fail-fast + - name: cargo test --doc (imaging_vello, vello-0-7) + run: cargo test --doc -p imaging_vello --locked --no-default-features --features vello-0-7 --no-fail-fast + - name: cargo test --doc (imaging_vello, vello-0-8) run: cargo test --doc -p imaging_vello --locked --no-default-features --features vello-0-8 --no-fail-fast - - name: cargo test --doc (imaging_vello, vello-0-7) - run: cargo test --doc -p imaging_vello --locked --no-default-features --features vello-0-7 --no-fail-fast + - name: cargo test --doc (imaging_vello, vello-0-9) + run: cargo test --doc -p imaging_vello --locked --no-default-features --features vello-0-9 --no-fail-fast package: name: cargo package --list @@ -434,8 +440,8 @@ jobs: env: RUSTDOCFLAGS: '--cfg docsrs -D warnings' - - name: cargo doc (imaging_vello, vello-0-8) - run: cargo doc -p imaging_vello --locked --no-default-features --features vello-0-8 --no-deps --document-private-items + - name: cargo doc (imaging_vello, vello-0-9) + run: cargo doc -p imaging_vello --locked --no-default-features --features vello-0-9 --no-deps --document-private-items env: RUSTDOCFLAGS: '--cfg docsrs -D warnings' @@ -444,6 +450,11 @@ jobs: env: RUSTDOCFLAGS: '--cfg docsrs -D warnings' + - name: cargo doc (imaging_vello, vello-0-8) + run: cargo doc -p imaging_vello --locked --no-default-features --features vello-0-8 --no-deps --document-private-items + env: + RUSTDOCFLAGS: '--cfg docsrs -D warnings' + # If this fails, consider changing your text or adding something to .typos.toml. typos: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 6465b65..3a818e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,7 +199,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec 0.9.1", ] [[package]] @@ -208,6 +217,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + [[package]] name = "bitflags" version = "1.3.2" @@ -238,6 +253,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -382,6 +406,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "color" version = "0.3.3" @@ -495,6 +530,25 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.1", + "objc2", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + [[package]] name = "document-features" version = "0.2.12" @@ -731,6 +785,24 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glifo" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d99fc21d493812643aae86d53b7bbd02f376434a90317e8a790bc209fdd6605e" +dependencies = [ + "bytemuck", + "core_maths", + "foldhash 0.2.0", + "hashbrown 0.17.1", + "log", + "peniko", + "png 0.18.1", + "skrifa 0.42.1", + "smallvec", + "vello_common", +] + [[package]] name = "glob" version = "0.3.3" @@ -749,6 +821,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "glutin_wgl_sys" version = "0.6.1" @@ -1086,7 +1170,7 @@ dependencies = [ "kurbo", "peniko", "pollster", - "skrifa", + "skrifa 0.40.0", "svg_imaging", "vello_hybrid", ] @@ -1115,6 +1199,7 @@ dependencies = [ "pollster", "vello 0.7.0", "vello 0.8.0", + "vello 0.9.0", ] [[package]] @@ -1132,6 +1217,7 @@ dependencies = [ name = "imaging_vello_hybrid" version = "0.0.1" dependencies = [ + "glifo", "imaging", "imaging_wgpu", "kurbo", @@ -1139,7 +1225,7 @@ dependencies = [ "pollster", "vello_common", "vello_hybrid", - "wgpu 28.0.0", + "wgpu 29.0.3", ] [[package]] @@ -1149,6 +1235,7 @@ dependencies = [ "imaging", "wgpu 27.0.1", "wgpu 28.0.0", + "wgpu 29.0.3", ] [[package]] @@ -1485,11 +1572,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" dependencies = [ "arrayvec", - "bit-set", + "bit-set 0.8.0", "bitflags 2.11.1", "cfg-if", "cfg_aliases", - "codespan-reporting", + "codespan-reporting 0.12.0", "half", "hashbrown 0.16.1", "hexf-parse", @@ -1499,7 +1586,7 @@ dependencies = [ "num-traits", "once_cell", "rustc-hash 1.1.0", - "spirv", + "spirv 0.3.0+sdk-1.3.268.0", "thiserror 2.0.18", "unicode-ident", ] @@ -1511,11 +1598,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" dependencies = [ "arrayvec", - "bit-set", + "bit-set 0.8.0", + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "codespan-reporting 0.12.0", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv 0.3.0+sdk-1.3.268.0", + "thiserror 2.0.18", + "unicode-ident", +] + +[[package]] +name = "naga" +version = "29.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd91265cc2454558f659b3b4b9640f0ddb8cc6521277f166b8a8c181c898079" +dependencies = [ + "arrayvec", + "bit-set 0.9.1", "bitflags 2.11.1", "cfg-if", "cfg_aliases", - "codespan-reporting", + "codespan-reporting 0.13.1", "half", "hashbrown 0.16.1", "hexf-parse", @@ -1525,7 +1638,7 @@ dependencies = [ "num-traits", "once_cell", "rustc-hash 1.1.0", - "spirv", + "spirv 0.4.0+sdk-1.4.341.0", "thiserror 2.0.18", "unicode-ident", ] @@ -1577,6 +1690,68 @@ dependencies = [ "malloc_buf", ] +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.1", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.11.1", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-metal", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1821,6 +1996,18 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-quartz-core", +] + [[package]] name = "rayon" version = "1.12.0" @@ -1846,6 +2033,16 @@ name = "read-fonts" version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "font-types 0.11.3", +] + +[[package]] +name = "read-fonts" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81" dependencies = [ "bytemuck", "core_maths", @@ -2144,10 +2341,20 @@ name = "skrifa" version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts 0.37.0", +] + +[[package]] +name = "skrifa" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564" dependencies = [ "bytemuck", "core_maths", - "read-fonts", + "read-fonts 0.39.2", ] [[package]] @@ -2190,6 +2397,15 @@ dependencies = [ "bitflags 2.11.1", ] +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags 2.11.1", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -2245,7 +2461,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "842f3cd369c2ba38966204f983eaa5e54a8e84a7d7159ed36ade2b6c335aae64" dependencies = [ - "skrifa", + "skrifa 0.40.0", "yazi", "zeno", ] @@ -2611,7 +2827,7 @@ dependencies = [ "log", "peniko", "png 0.17.16", - "skrifa", + "skrifa 0.40.0", "static_assertions", "thiserror 2.0.18", "vello_encoding 0.7.0", @@ -2630,7 +2846,7 @@ dependencies = [ "log", "peniko", "png 0.18.1", - "skrifa", + "skrifa 0.40.0", "static_assertions", "thiserror 2.0.18", "vello_encoding 0.8.0", @@ -2639,43 +2855,52 @@ dependencies = [ ] [[package]] -name = "vello_api" -version = "0.0.7" +name = "vello" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5088cd0113bc5332c753f24503825e3bc93e26c7883c9dc3ad9637bb62c4634" +checksum = "261359dbef879f8110ef7e1c442246c838d33d3d91cb05e0ea9288d432760c9f" dependencies = [ "bytemuck", + "futures-intrusive", + "log", "peniko", + "png 0.18.1", + "skrifa 0.42.1", + "static_assertions", + "thiserror 2.0.18", + "vello_encoding 0.9.0", + "vello_shaders 0.9.0", + "wgpu 29.0.3", ] [[package]] name = "vello_common" -version = "0.0.7" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "986dc49a501a683477614bf07b8e7b6c79ae4828efce3bf22e51850f4a0a8a4c" +checksum = "19d672facaa2d697285a786cd9d44d614cd2ce54cdc022504bf339f8fff3b750" dependencies = [ "bytemuck", "fearless_simd", "guillotiere 0.7.0", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "libm", "log", "peniko", "png 0.18.1", - "skrifa", "smallvec", "thiserror 2.0.18", ] [[package]] name = "vello_cpu" -version = "0.0.7" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a678f91c7524a3a9ac9a19df9f83552866ec70b2ca26441b916a6b219b6aa2de" +checksum = "588691169aed86b5c8fb487266afee01323234e6fd0a3f2aaec0eaa8e4007f23" dependencies = [ "bytemuck", - "hashbrown 0.16.1", - "vello_api", + "glifo", + "hashbrown 0.17.1", + "png 0.18.1", "vello_common", ] @@ -2688,7 +2913,7 @@ dependencies = [ "bytemuck", "guillotiere 0.6.2", "peniko", - "skrifa", + "skrifa 0.40.0", "smallvec", ] @@ -2701,24 +2926,37 @@ dependencies = [ "bytemuck", "guillotiere 0.7.0", "peniko", - "skrifa", + "skrifa 0.40.0", + "smallvec", +] + +[[package]] +name = "vello_encoding" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2346f5f0d7dccb3582fcd397b4a57b43165209f1424e0d76e85dd814db164af7" +dependencies = [ + "bytemuck", + "guillotiere 0.7.0", + "peniko", + "skrifa 0.42.1", "smallvec", ] [[package]] name = "vello_hybrid" -version = "0.0.7" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a2b295a72c9fb73be9679c0d468c64d4f97ef09b91ce6d16d309747719c6ff2" +checksum = "86ca7acbf6e59ae8c354c524732c73da45239a3a8ee03d0c8a9ad66a6622e488" dependencies = [ "bytemuck", - "hashbrown 0.16.1", + "glifo", + "hashbrown 0.17.1", "log", "thiserror 2.0.18", - "vello_api", "vello_common", "vello_sparse_shaders", - "wgpu 28.0.0", + "wgpu 29.0.3", ] [[package]] @@ -2747,11 +2985,24 @@ dependencies = [ "vello_encoding 0.8.0", ] +[[package]] +name = "vello_shaders" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd38937516fa4b47423d9255bb5e4a65e839ec9d57c38c4af6189ce56bf46b7" +dependencies = [ + "bytemuck", + "log", + "naga 29.0.3", + "thiserror 2.0.18", + "vello_encoding 0.9.0", +] + [[package]] name = "vello_sparse_shaders" -version = "0.0.7" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afd2601f1c8cc1e16807aaf1a66dd182eb6f895df095a48fb353fe49377a9ce" +checksum = "e004cfe28eb71738643f60460b9cae2236eba57b351afebf244148256ff25d8b" [[package]] name = "version_check" @@ -2830,6 +3081,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.98" @@ -2905,6 +3168,36 @@ dependencies = [ "wgpu-types 28.0.0", ] +[[package]] +name = "wgpu" +version = "29.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb3feacc458f7bee8bc1737149b42b6c731aa461039a4264a67bb6681646b250" +dependencies = [ + "arrayvec", + "bitflags 2.11.1", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga 29.0.3", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 29.0.3", + "wgpu-hal 29.0.3", + "wgpu-types 29.0.3", +] + [[package]] name = "wgpu-core" version = "27.0.3" @@ -2912,8 +3205,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" dependencies = [ "arrayvec", - "bit-set", - "bit-vec", + "bit-set 0.8.0", + "bit-vec 0.8.0", "bitflags 2.11.1", "bytemuck", "cfg_aliases", @@ -2944,8 +3237,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d23f4642f53f666adcfd2d3218ab174d1e6681101aef18696b90cbe64d1c10f9" dependencies = [ "arrayvec", - "bit-set", - "bit-vec", + "bit-set 0.8.0", + "bit-vec 0.8.0", "bitflags 2.11.1", "bytemuck", "cfg_aliases", @@ -2969,6 +3262,39 @@ dependencies = [ "wgpu-types 28.0.0", ] +[[package]] +name = "wgpu-core" +version = "29.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02da3ad1b568337f25513b317870960ef87073ea0945502e44b864b67a8c77b7" +dependencies = [ + "arrayvec", + "bit-set 0.9.1", + "bit-vec 0.9.1", + "bitflags 2.11.1", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga 29.0.3", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.18", + "wgpu-core-deps-apple 29.0.3", + "wgpu-core-deps-emscripten 29.0.3", + "wgpu-core-deps-windows-linux-android 29.0.3", + "wgpu-hal 29.0.3", + "wgpu-naga-bridge", + "wgpu-types 29.0.3", +] + [[package]] name = "wgpu-core-deps-apple" version = "27.0.0" @@ -2987,6 +3313,15 @@ dependencies = [ "wgpu-hal 28.0.1", ] +[[package]] +name = "wgpu-core-deps-apple" +version = "29.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e51b5447e144b3dbba4feb01f80f4fa21696fa0cd99afb2c3df1affd6fdb28" +dependencies = [ + "wgpu-hal 29.0.3", +] + [[package]] name = "wgpu-core-deps-emscripten" version = "27.0.0" @@ -3005,6 +3340,15 @@ dependencies = [ "wgpu-hal 28.0.1", ] +[[package]] +name = "wgpu-core-deps-emscripten" +version = "29.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3487cd6293a963bc5c0c0396f6a2192043c50003c07f4efdccbad3d90ec9d819" +dependencies = [ + "wgpu-hal 29.0.3", +] + [[package]] name = "wgpu-core-deps-windows-linux-android" version = "27.0.0" @@ -3023,6 +3367,15 @@ dependencies = [ "wgpu-hal 28.0.1", ] +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb01076d0aa08b0ba9bd741e178b5cc440f5abe99d9581323a4c8b5d1a1916" +dependencies = [ + "wgpu-hal 29.0.3", +] + [[package]] name = "wgpu-hal" version = "27.0.4" @@ -3032,14 +3385,14 @@ dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set", + "bit-set 0.8.0", "bitflags 2.11.1", "block", "bytemuck", "cfg-if", "cfg_aliases", "core-graphics-types", - "glow", + "glow 0.16.0", "glutin_wgl_sys", "gpu-alloc", "gpu-allocator 0.27.0", @@ -3081,14 +3434,14 @@ dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set", + "bit-set 0.8.0", "bitflags 2.11.1", "block", "bytemuck", "cfg-if", "cfg_aliases", "core-graphics-types", - "glow", + "glow 0.16.0", "glutin_wgl_sys", "gpu-allocator 0.28.0", "gpu-descriptor", @@ -3120,6 +3473,70 @@ dependencies = [ "windows-core 0.62.2", ] +[[package]] +name = "wgpu-hal" +version = "29.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8e1a9e7a8512f276f7c62e018c7fa8d60954303fed2e5750114332049193f" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set 0.9.1", + "bitflags 2.11.1", + "block2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow 0.17.0", + "glutin_wgl_sys", + "gpu-allocator 0.28.0", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "naga 29.0.3", + "ndk-sys", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-metal", + "objc2-quartz-core", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.18", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types 29.0.3", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-result 0.4.1", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c654c483f058800972c3645e95388a7eca31bf9fe1933bc20e036588a0be02" +dependencies = [ + "naga 29.0.3", + "wgpu-types 29.0.3", +] + [[package]] name = "wgpu-types" version = "27.0.1" @@ -3147,6 +3564,20 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wgpu-types" +version = "29.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9bcc31518a0e9735aefebedb5f7a9ef3ed1c42549c9f4c882fa9060ceaac639" +dependencies = [ + "bitflags 2.11.1", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + [[package]] name = "winapi-util" version = "0.1.11" diff --git a/imaging_skia/src/lib.rs b/imaging_skia/src/lib.rs index 0c0c248..1c2f2a4 100644 --- a/imaging_skia/src/lib.rs +++ b/imaging_skia/src/lib.rs @@ -194,7 +194,9 @@ use crate::gpu_readback::{ ReadbackError, ScratchTexture, read_texture_into, read_texture_into_target, }; #[cfg(feature = "gpu")] -use imaging_wgpu::{TextureRenderer, TextureRendererError, TextureTargetError}; +use imaging_wgpu::v28::TextureRenderer; +#[cfg(feature = "gpu")] +use imaging_wgpu::{TextureRendererError, TextureTargetError}; use sinks::MaskCache; pub use sinks::{SkCanvasSink, SkPictureRecorderSink}; #[cfg(feature = "gpu")] diff --git a/imaging_snapshot_tests/Cargo.toml b/imaging_snapshot_tests/Cargo.toml index 4d8e8ab..0860989 100644 --- a/imaging_snapshot_tests/Cargo.toml +++ b/imaging_snapshot_tests/Cargo.toml @@ -20,7 +20,7 @@ kurbo = { default-features = true, workspace = true } peniko = { default-features = true, workspace = true } skrifa = "0.40.0" svg_imaging = { workspace = true } -vello_hybrid = { default-features = true, optional = true, version = "0.0.7" } +vello_hybrid = { default-features = true, optional = true, version = "0.0.9" } [lints] workspace = true diff --git a/imaging_snapshot_tests/src/cases/mod.rs b/imaging_snapshot_tests/src/cases/mod.rs index 378c72d..47680ed 100644 --- a/imaging_snapshot_tests/src/cases/mod.rs +++ b/imaging_snapshot_tests/src/cases/mod.rs @@ -101,6 +101,10 @@ pub const CASES: &[&dyn SnapshotCase] = &[ &gradients::GmGradientsSweep, &gradients::GmGradientsTwoPointRadial, &images::GmImageBrushes, + // Keep the Vello GPU glyph image-brush snapshots adjacent to the image-brush + // case until the Vello 0.9 atlas invalidation fix is released. + &text::GmGlyphRunsImageFill, + &text::GmGlyphRunsImageStroke, &masks::GmMaskAlpha, &masks::GmMaskLuminance, &clips::GmClipNonIsolated, @@ -116,8 +120,6 @@ pub const CASES: &[&dyn SnapshotCase] = &[ &text::GmGlyphRunsGradientFill, &text::GmGlyphRunsGradientBrushTransform, &text::GmGlyphRunsGradientStroke, - &text::GmGlyphRunsImageFill, - &text::GmGlyphRunsImageStroke, &text::GmTextEditorLorem, &svg::GmSvgLayeredCard, &svg::GmSvgLuminanceMask, diff --git a/imaging_snapshot_tests/src/cases/text.rs b/imaging_snapshot_tests/src/cases/text.rs index ea7e52e..3c60dea 100644 --- a/imaging_snapshot_tests/src/cases/text.rs +++ b/imaging_snapshot_tests/src/cases/text.rs @@ -89,7 +89,10 @@ impl SnapshotCase for GmGlyphRunsGradientBrushTransform { } fn supports_backend(&self, backend: &str) -> bool { - matches!(backend, "skia" | "tiny_skia" | "vello_cpu" | "vello_hybrid") + matches!( + backend, + "skia" | "tiny_skia" | "vello" | "vello_cpu" | "vello_hybrid" + ) } fn skia_max_diff_pixels(&self) -> u64 { @@ -205,6 +208,13 @@ impl SnapshotCase for GmGlyphRunsImageFill { "gm_glyph_runs_image_fill" } + fn supports_backend(&self, backend: &str) -> bool { + matches!( + backend, + "skia" | "tiny_skia" | "vello" | "vello_cpu" | "vello_hybrid" + ) + } + fn run(&self, sink: &mut dyn PaintSink, width: f64, height: f64) { background(sink, width, height, Color::from_rgba8(247, 243, 236, 255)); let mut painter = Painter::new(sink); @@ -232,6 +242,13 @@ impl SnapshotCase for GmGlyphRunsImageStroke { "gm_glyph_runs_image_stroke" } + fn supports_backend(&self, backend: &str) -> bool { + matches!( + backend, + "skia" | "tiny_skia" | "vello" | "vello_cpu" | "vello_hybrid" + ) + } + fn vello_max_diff_pixels(&self) -> u64 { 8 } diff --git a/imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_gradient_brush_transform.png b/imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_gradient_brush_transform.png new file mode 100644 index 0000000000000000000000000000000000000000..22a909203faf4ece4971183c40fbc9125a044d06 GIT binary patch literal 5410 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 PPoQ$|iE^=`*_;0YUxtU+ literal 0 HcmV?d00001 diff --git a/imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_gradient_fill.png b/imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_gradient_fill.png index 06306f0753d2d962f3791b4186d54557afd88174..3ab269fed56fc0e8ad6472cf6590830a1e7be75f 100644 GIT binary patch literal 3755 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 diff --git a/imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_gradient_stroke.png b/imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_gradient_stroke.png index c8f71dfafb0c31160ad7327d0b53bb1821f1ed52..406a5405da73accb5871560b9bb1616e32e45728 100644 GIT binary patch literal 4859 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 J>J_Xb{s%kNWa0n- diff --git a/imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_image_fill.png b/imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_image_fill.png index 4e98cbba81f43f62f7d90f17b22fec34f46eae75..7358f195873b1e1535b631e604f725d6311484d0 100644 GIT binary patch literal 3981 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 literal 4181 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 WMawK#DgS*J04&XJnPP5uCj1WuUdy)t diff --git a/imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_image_stroke.png b/imaging_snapshot_tests/tests/snapshots/vello/gm_glyph_runs_image_stroke.png index bdb32d9850af725ed48dafcda5c560f7310d3e78..a1d26fc389242d4be11f4e57fc588ce50ae312a6 100644 GIT binary patch literal 6422 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 literal 7081 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 Cw{h42 diff --git a/imaging_snapshot_tests/tests/snapshots/vello/gm_image_brushes.png b/imaging_snapshot_tests/tests/snapshots/vello/gm_image_brushes.png index 29dec68452b0e0fde409c4bfc00359a03c8938e6..1b1dae1c843d2ec5009b5941865d9234ce51138e 100644 GIT binary patch literal 6265 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 literal 6609 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 delta 14509 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 diff --git a/imaging_snapshot_tests/tests/snapshots/vello_cpu/gm_clip_stroke_nested.png b/imaging_snapshot_tests/tests/snapshots/vello_cpu/gm_clip_stroke_nested.png index 11be3de120d8d13a56d40dee6fb6dbb4be4acb0b..e333c6c722169c2cedaa0956d6ca09ace7bfbfaa 100644 GIT binary patch delta 3932 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 H9U}e*{lVVv diff --git a/imaging_snapshot_tests/tests/snapshots/vello_cpu/gm_image_brushes.png b/imaging_snapshot_tests/tests/snapshots/vello_cpu/gm_image_brushes.png index 2fd48a30d72de29fa5b0d29077eb0381f8e5f17e..d1eb750217d18ab79dd7ba224d55f5bbe3477121 100644 GIT binary patch delta 6575 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 delta 6550 zcmY*YWk8hS(tUTAW41c!n%1K!i;bUg>jxvt;x=3eeHrOAGcE;SR| z_B*Y^kbG$jbfiV0G{65zDnR?VqNQZ;ZtwP|GwqO~`gxoZpv60XlqiYu5Agq0@ur9rsnymlx|8nkX?}5BZSfvb zm*9QrQS-la<@$giV!6e8XwTo#L-n^GlG%bCiShyb0~U3ObfGq5xi!JtM_hrW<0M^6FVk+h0a%- z_q#s)F^L%^3=MFTVVXVXyMY)G(dj^Mv-EHaXRHKqKka#KWf(xGK2Mi_>gLccCfP!QCODDFA$jEzrr4!;>aR z{|tQCod8>>BmNHZ18@LFVz0#mE6dGpcc%!z^->wK0oaP9*KTq7Dn{Uy0*lmq$@c{b zD_;@K(6UhjGPnz8w>3IyORHlUEN#ZSf1K>C-sr-CHt0T=TqgX`)U)~P*N9WLX5x8g zzW=i0xK|_R>|VMDKK~gRLLH9!-<+T#sv|t)$`=4}CM;$ICdmflO?w`4P-jTqDY~;; z_biC8W0!j+E8bY6b9eQwF|ZPjR;)330N?yam{2AEPbdlfg<H>=Rq%U!{hg`r9l@ zQniLo7>7ap_y&wn-ZvSG4|gtp(q0G;Ev1A~#O<5p!2?dnVQ9&_D)5)Wmpbg8Uy;sd z16LAe&FyzC!_nO0;ZTXmzG3M=ol(UTZX8Z*7&;mmgeOKQ;HlFR^VEqs9YQ~)*r2Y8 zgY><#G&3^5>tX)Coy@hXCqMFHZ-QF#yV2bLotsay#hp_0G6d^}p8ThwDxS6Af85|B z%6BMwdHq-b1n;%37n{x8?Hs_d(pBR$TT8=>2=lFV5ZKIoAoPv%u1tg?nDG6ZPJ!l+)tx>M;lRk`r4M9sfc0%&I&WPmbqV<$^aQ%W zY?$OT;#^H$9)+jG?qE%=>3`GJOMlNR`<5vqZqsP_ubh>Sq3)G3kgBu;+CumnOR(fC z`kfyimP_8e064NwUACzAdp(#Pb~J@P(Zb|LbT{o%M0L%&BFM?-GQefe$nLu`xHOER z0-x7L&2=6f?HmRv+}BfMHjnKjm(`uYtBav7xo`FZ{yLZW)b#F1J1`1vbnpMJFt08&wZvw> zu?0A230mO1e&CAeWO2JFMog*025_w99zsjfUkuC+i>7?M`{gFIrQ{ZdOP>BKUr4Y8 z!(DxoA%)#y!(-(Ky&msIPuymj?mS+tu$!#ux4huVeIk8z)L)JB0LWfe?6JIvRQmg2 zS;6Jbs0%;Sze>q%hn?V`=C%xuA`ay8y!Tqqjq@bvbzNKDZ2QhWojZ#39dr4LUaUMx z>u>d^HwDtCPZ0N{#}&>COB*Cb+l|efV$gYcI)k{xoo1_pXyenG?Wq>gXEMRJy_3sN zOaT4MA8~2f5xEBm&&D5)Knq8|NZlk%ZHBQiD~mW-kdSZwCj}9)_9WR1EQ^RpoCu6_*Gx+-`fBwi^7301UddtE zHg@HgIVTssN4{716WJcGb+q4DewwzWcXaVw!0J>whs<-jK(sF!1%l-YKedNCi~2a} z?{2jMoAwPjlb?mf_{cE+(}~iv4^(Dpi%V1N+=z%vFkdLYT-7aXs(b#6q2BtIpHc^w zZkT_>H0S1Cqk)%?c&U>-nZqAW9WfHO*S2gC9Pnph5(pJhCqKaMNz0g}^+d(=8Z!YT z5TysV67QCmCnsfzIcP_EacAEAqd&>d5R+v_mK>am2?{7{yP7*xi2U8SqT~VM=tM7ZbM8`!;+p_N?ia9I!YD5B zGc=?MZv8^O5H=Fx#X$JUn%r^Mq!6whR|I`$;dQd|Ei+3JMJlq8a*%|{Ac=bO{g0_3 z76SA3KNTL%sbVAnD*`rmH#hnAM<^e>^Q?s3$;YQp+v2ofy37;Irv15h4Edq zDRvIo>j-x$33H~n8|l5v^V#yAsr|o~d&SAXrdSN}31x%&~$q1>&#Q|jUg6xLr zppKC8tM_0=tD9OiqS9OFHeb2nDPMQ zGMZ$)`A=#uiY*ePKLqs+T!sC5ZvuRj4y#t7q-v5Fkvz>`?mpVOK>6R4U>6csH}dy) zV>9VXJeB3{PzdN3x=D<=Q`*UKsEo?sLbT!fk;K33JI7GYWAOkdwRNwQ@@F?@&(VEm z?c+yS{{{KMkB{GT94i#MSt|^%wkjhgA?4EEyzHvg?J9(G#r4Gp$+zdfF&cvBUYB~e zkn(H1_S%GMQqmS`9wDxR=UGmW@>w#{w|&BiV!@~GUhk)PKB#{Ne!F*1VNt!zd%^R} zHX`Rm0?}tmqc=?GNY^uCh{*dN%^OmBRca->^!Nh#;XUXhQN z*y#zphvh*K6<5}4?t#-$@A%;JJxO_DMA~x_g>OU}uWxVblXHZq6Wp+$XTMjK`XEm> zrbcemY0_u-sswT2EM1#+zMknXY-f=~3YSk4=l*4+RD(C5F!ki8?C4>I<9AEj@|Lm= zJr@0Z@OJ6w7-ilZse*@G`1&yD(3`{}Arnrq$DFE=zRI+-=zbVl$9?Nedb;-73$#Fw z{wQ9(nE^CgaIQz^g0zKBnX$sD_S^22BRl$wq@J);6SfZifTKe0*f!jcJbtsryt<+@ zW!4WuHo_35^ej;0%eOh*myI@hc!i+|sao*&ZxcPg-iqB@GO>QyItGto#Yg~ex?)p2 zZAsER9>wxX0D*8+;8e&a&eD*njZLpRuJ`2_(O4tn-5uoELRFE640dmarLBm4$AV8c z_YLj)k$mGUt(^*F7L`Z%5x6wQ?9k_Ms4H`f0Agdd4X@(_Iq~wwFSZHo%%koafgd(e zs$PnQ=~^-MdJP*#W`lI^Uj1TIMJW_+1K!3)2(fWO95~?5X&D4|LOC$}aGtX%8KvlxsF0^$=_tr>nngo(+1h+6P5{ zXR9SdA|_(3l_ur=l&Ot`jdlmGf0`!DW^&j~ymr*>+&QhsMoxLOv^e~&>{b1Y{+caA z4*Q6+tcN*!&g@Cy-n-KVo_zXG%B+;n{stI{*Fr)Xclitplm6SF3*Lqijh)2Hel1O( ztxO2iTc%BHmq@X2Flnu)c|WQR82S*?RQ5?$_E)U!l7lhKYPOOG__Z~E%buEeW{XH~ zy%6iUz%L`IxK|#(a;V_1yc(i8y zJJrNHIaC0bioZ*Z9w`y|iRH0k1LFikACDtegxP)1w%{6o?;jMcK+}>bIqmRU;^R-w zuYBD-;=0!@~U||e$$cV@}b8+e9K~+39 za$_y(A?*lqNQcNdv|2XbOUx9=e4De4a>^_^Fbn}BZoeJYvmn2c`^u>&2fer_tzTur zShc`o<{y5^-=bDQH7VS4X+-2`;sa!8Av`_Kql=avH=HN3H^TUQPCc@ok{K3_=Z44b z$}2?NJPO3M((s{ih+*)?`xK&V)(~$Gv64i>9L}*CZeU#_N*!2h9nK+T7}5wkkNQG~zHscIP*%VC4lcpXq`&`sV!khl934-Ws4Ek$FL~57Y5}EJ z04&Y-I}1Kw*O;S?gD(G`0hQU($uyoiAvKDFm7G*Qq#8bIxHnl>{~}wOH{6QxU}84~ zQyG4fE8w>BBJ>@@$q6Vps95zeqK-xgd zXpWyuC5f>+UNt!vs>N?G<_xP-!KV-Qb}KiG2T)VbG_kD)Z#+cU8lXnMc(SZqP^)iy zUlOir$)#bvU(-VWD;?4(6jqH|S8pdRe_D0qdnrN!(3JfowSU+#(E{10sCy9 z+uLxVJROsUbw-{3Tk1CGZ5JCg4cO&XBFvkgm-7qDN81;R)U6E;#A# zG3pQ)VMw`8xwvFo6BK2wiaVSMXkhcziH!vA6QaEvWC#oA<=tbStW9VfgSg}apHeYz z0i_Q>Lm#sqB;j#fz_g_xTSV^F1@K($UXz}YNFe&rqewf{C``$Es%nCOQCxymU5_l6 zw1N1v;W?0m(HKgQvz#?6TY5fVAqQreKKlN3kduf4`QyN)*$FCC!Wt_sHlxh$n|&Y2 zWlleyU$ZbiFy|TiyJD;Yl!j~wJpOyo@EyF_KK0sqwCaOg6#C1PemHcx(R*-^JGGHb z#^*uhryuxJ&7XqAzFMr51c5OK_*}-(j3iEy4Bu%AsK!E&I+fW$MHov9r<}v&UAQpe z8#(0q1btNA_l!-iAd?ylQ7G|_ZjabC`mmJs6%64zrEsGr1z#cfJTOBnX`~NDsW?=wa6~MM0M2k4-UrH=3Yl; z9y+zh?@Dc*6c|-p_kLP{+>iIolSg6sZcOBlR)Enj`xD}~E>xV}nSTGG`O+~8di$tf zpt12r%y{$pAnv*q_^bmfY45`K*|bjG(9uM-YeNW=nqS!qpU@YlH(vj+?5L_MJ-jcF z?#+?zi>HDsJ9Z_ub2Z+nlhqF!7a9WyF&zeq_ zf;*a2QS^{F)e;h`ix*n>nDl2Ej|9r(S9wBfnw%|2hTT37gPM;x7gzUI+-INuX%wFx za@uzH9(Lv`%!N}?QF(mKt>%o5gFkOOPv1f3;Z=^3WXz1|eARA6w>Ubm~ zX{^2i&&dmidAI0B0&gA)e!iEIpM{GWs{x2dDN)_2 z%=6lW%3mzkNMGaZF52zA(Yf0kRv+yI!@&U^9s`I56y(E6tJYuN7Vce&sMU?DoRXva z!o8Nq3SGps4={0yp9~$b>%5u?pcOe2bKXnijD(`CXPl4JRhTrOd^qf-g?ysQE-pD0 zMFHk@pJYjqjLCJ}5nGJ3RNS#*XKSA}n-jDFs;cGDTTy1DOxt4XYtvH3SX)%N>V4(g zo1}_mFi-|#^8n2X*ouXGML75-3niRevU0K~(nIG|fMaR2WO;qTkZ^Bt;gAF!KyFrJ zaZ_-*`@PJKnt1tqGHHxfb2i=W-SB7CdseQ?*KPSbdvrAw+8kMp{7yB2T&0m~EZ6C3 zsVw8Df+n9gLG!z6$eEIx)zY(v&ZOEm`TPrDbEJgLK!fQ6nIj}r?nZChRA_HLoMxOx zpIn4mW@a@k1loJ+@|tk9NBP$r?e&h#u>qm$P*`=_%}zL4;KSowLO=+X(#?l69Zvd` zyh8A~(KthD7yca=Wj_w&!gFm*M{kFsa>qUSdmfAfSP5w2=*X~@&U5O1bx0%<&jSM3 zNVDp4OkY6o>}$i4(S|-NZEH9IV4QUPG_*RvsuE|B=sEkmmB3V|2_XmYBDN@reVzxO z=AFbc&Z{qh@Q)*t?bSdE#@k6Fu)0H7Z#FeUf5POWPQk$LMD_dYO9mu&G!b2h13G$b zD6=J~wkz0kcSZ&*Ce_hm>r(9y1@F}7*j2tWHekIDoxra8VA49^2i|Ti7y`YUvGGvQ zQ|`DBZ6)MMrR|liySuBqyS|N_#A+L9rv}J@y8JMEjXF_Ae^7sEcP&$UtJm;> ztx9lO(YAn8W(t+~51No?%|IO+v>>GFTh!%s`o)%EZktTRMtXDaH)Kg(s*k{^kJ19S z;hm+Y{5TqKt?KEI1Aq6h=Q7aiv;`BEmGMblzs#M_-*f5QR~MG=5M$d|>H0+ZqsHe~ zvu||3X?RI zR>~uRNyVn4AyS>$jMgT>&LFp7C~Iga0V;jkr3psJ{asFJq|`#(9NU9$iH diff --git a/imaging_snapshot_tests/tests/snapshots/vello_hybrid/gm_glyph_runs_gradient_brush_transform.png b/imaging_snapshot_tests/tests/snapshots/vello_hybrid/gm_glyph_runs_gradient_brush_transform.png index c6ae18d7cef3c779cf9c867bd94e48fc89c51819..8f87c5f462c9feae3a6d15a9c877bc936e6bf3c3 100644 GIT binary patch literal 5335 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 literal 5110 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 diff --git a/imaging_vello/CHANGELOG.md b/imaging_vello/CHANGELOG.md index 0d813e9..6b74a41 100644 --- a/imaging_vello/CHANGELOG.md +++ b/imaging_vello/CHANGELOG.md @@ -13,12 +13,24 @@ The latest published `imaging_vello` release is [0.0.1](#001-2026-05-21), which ## [Unreleased] +### Changed + +- Updated the backend to Vello 0.9 and `wgpu` 29. ([#64][] by [@waywardmonkeys][]) +- Kept the `vello-0-7` and `vello-0-8` compatibility features for integrations that still use + `wgpu` 27 or 28. ([#64][] by [@waywardmonkeys][]) +- Enabled glyph brush transforms through the Vello 0.9 glyph-run API. + ([#64][] by [@waywardmonkeys][]) + ## [0.0.1][] (2026-05-21) This release has an [MSRV][] of 1.92. This is the initial release. +[@waywardmonkeys]: https://github.com/waywardmonkeys + +[#64]: https://github.com/forest-rs/imaging/pull/64 + [Unreleased]: https://github.com/forest-rs/imaging/compare/imaging_vello-v0.0.1...HEAD [0.0.1]: https://github.com/forest-rs/imaging/releases/tag/imaging_vello-v0.0.1 diff --git a/imaging_vello/Cargo.toml b/imaging_vello/Cargo.toml index bd34e97..5560140 100644 --- a/imaging_vello/Cargo.toml +++ b/imaging_vello/Cargo.toml @@ -17,11 +17,13 @@ kurbo = { default-features = true, workspace = true } peniko = { default-features = true, workspace = true } vello_07 = { default-features = true, optional = true, package = "vello", version = "0.7.0" } vello_08 = { default-features = true, optional = true, package = "vello", version = "0.8.0" } +vello_09 = { default-features = true, optional = true, package = "vello", version = "0.9.0" } [features] -default = ["vello-0-8"] +default = ["vello-0-9"] vello-0-7 = ["dep:vello_07", "imaging_wgpu/wgpu-27"] vello-0-8 = ["dep:vello_08", "imaging_wgpu/wgpu-28"] +vello-0-9 = ["dep:vello_09", "imaging_wgpu/wgpu-29"] [dev-dependencies] pollster = "0.4.0" @@ -30,6 +32,6 @@ pollster = "0.4.0" workspace = true [package.metadata.docs.rs] -features = ["vello-0-8"] +features = ["vello-0-9"] default-target = "x86_64-unknown-linux-gnu" targets = [] diff --git a/imaging_vello/README.md b/imaging_vello/README.md index a4a770a..0df3a11 100644 --- a/imaging_vello/README.md +++ b/imaging_vello/README.md @@ -32,10 +32,11 @@ Semantic [`imaging::record::Scene`] values can be lowered to native Vello scenes In UI integrations, the host application should usually own the `wgpu` device, queue, and presentation targets, then pass those handles into [`VelloRenderer`]. -Enable exactly one backend compatibility feature: +Enable a Vello compatibility feature: -- `vello-0-8` (default) -- `vello-0-7` +- `vello-0-9` (default) +- `vello-0-8` for integrations that still use `wgpu` 28 +- `vello-0-7` for integrations that still use `wgpu` 27 # Render A Recorded Scene diff --git a/imaging_vello/src/lib.rs b/imaging_vello/src/lib.rs index 0202587..2ce163e 100644 --- a/imaging_vello/src/lib.rs +++ b/imaging_vello/src/lib.rs @@ -12,10 +12,11 @@ //! In UI integrations, the host application should usually own the `wgpu` device, queue, and //! presentation targets, then pass those handles into [`VelloRenderer`]. //! -//! Enable exactly one backend compatibility feature: +//! Enable a Vello compatibility feature: //! -//! - `vello-0-8` (default) -//! - `vello-0-7` +//! - `vello-0-9` (default) +//! - `vello-0-8` for integrations that still use `wgpu` 28 +//! - `vello-0-7` for integrations that still use `wgpu` 27 //! //! # Render A Recorded Scene //! @@ -113,11 +114,8 @@ mod scene_sink; mod wgpu_support; -#[cfg(all(feature = "vello-0-7", feature = "vello-0-8"))] -compile_error!("Enable exactly one of `vello-0-7` or `vello-0-8`."); - -#[cfg(not(any(feature = "vello-0-7", feature = "vello-0-8")))] -compile_error!("Enable one of `vello-0-7` or `vello-0-8`."); +#[cfg(not(any(feature = "vello-0-7", feature = "vello-0-8", feature = "vello-0-9")))] +compile_error!("Enable one of `vello-0-7`, `vello-0-8`, or `vello-0-9`."); use imaging::RgbaImage; use imaging::record::{Scene, ValidateError, replay}; @@ -125,13 +123,31 @@ use imaging::render::{ GpuReadbackError, ImageBufferFormat, ImageBufferTarget, ImageRenderer, ImageRendererError, ImageTargetError, RenderContentError, RenderSource, RenderUnsupportedError, }; -use imaging_wgpu::{TextureRenderer, TextureRendererError, TextureTargetError, TextureViewTarget}; +#[cfg(all( + not(any(feature = "vello-0-8", feature = "vello-0-9")), + feature = "vello-0-7" +))] +use imaging_wgpu::v27::{TextureRenderer, TextureViewTarget}; +#[cfg(all(not(feature = "vello-0-9"), feature = "vello-0-8"))] +use imaging_wgpu::v28::{TextureRenderer, TextureViewTarget}; +#[cfg(feature = "vello-0-9")] +use imaging_wgpu::v29::{TextureRenderer, TextureViewTarget}; +use imaging_wgpu::{TextureRendererError, TextureTargetError}; use kurbo::Rect; -#[cfg(feature = "vello-0-7")] +#[cfg(all(feature = "vello-0-7", feature = "vello-0-9"))] +use vello_07 as _; +#[cfg(all( + not(any(feature = "vello-0-8", feature = "vello-0-9")), + feature = "vello-0-7" +))] pub use vello_07 as vello; -#[cfg(all(not(feature = "vello-0-7"), feature = "vello-0-8"))] +#[cfg(all(feature = "vello-0-8", feature = "vello-0-9"))] +use vello_08 as _; +#[cfg(all(not(feature = "vello-0-9"), feature = "vello-0-8"))] pub use vello_08 as vello; +#[cfg(feature = "vello-0-9")] +pub use vello_09 as vello; pub use crate::vello::wgpu; use crate::vello::{AaConfig, RenderParams}; @@ -146,7 +162,7 @@ pub use scene_sink::VelloSceneSink; pub enum Error { /// The scene is invalid (unbalanced stacks). InvalidScene(ValidateError), - /// An image brush was encountered; this backend does not support it. + /// An unsupported image-brush use was encountered. UnsupportedImageBrush, /// A filter configuration could not be translated. UnsupportedFilter, @@ -154,7 +170,7 @@ pub enum Error { UnsupportedMask, /// Glyph draws with non-default blend modes are not supported by this backend yet. UnsupportedGlyphBlend, - /// Glyph brush transforms are not supported until Vello exposes them on glyph runs. + /// Glyph brush transforms are not supported by older Vello compatibility lanes. UnsupportedGlyphBrushTransform, /// Blurred rounded rect draws with non-default blend modes are not supported by this backend yet. UnsupportedBlurredRoundedRectBlend, diff --git a/imaging_vello/src/scene_sink.rs b/imaging_vello/src/scene_sink.rs index 0060d3b..03d4342 100644 --- a/imaging_vello/src/scene_sink.rs +++ b/imaging_vello/src/scene_sink.rs @@ -124,11 +124,14 @@ impl<'a> VelloSceneSink<'a> { self.set_error_once(Error::UnsupportedGlyphBlend); return; } + #[cfg(all( + not(feature = "vello-0-9"), + any(feature = "vello-0-7", feature = "vello-0-8") + ))] if glyph_run.brush_transform.is_some() { self.set_error_once(Error::UnsupportedGlyphBrushTransform); return; } - let Some(paint) = self.brush_to_brush(glyph_run.brush, glyph_run.composite) else { return; }; @@ -140,8 +143,10 @@ impl<'a> VelloSceneSink<'a> { .font_size(glyph_run.font_size) .hint(glyph_run.hint) .normalized_coords(glyph_run.normalized_coords) - .brush(&paint) - .brush_alpha(glyph_run.composite.alpha); + .brush(&paint); + #[cfg(feature = "vello-0-9")] + let builder = builder.brush_transform(glyph_run.brush_transform); + let builder = builder.brush_alpha(glyph_run.composite.alpha); let builder = builder.glyph_transform(glyph_run.glyph_transform); let glyphs = glyphs.map(|glyph| VelloGlyph { id: glyph.id, @@ -552,7 +557,38 @@ mod tests { } #[test] - fn vello_scene_sink_rejects_glyph_brush_transforms_until_vello_supports_them() { + #[cfg(feature = "vello-0-9")] + fn vello_scene_sink_accepts_glyph_brush_transforms() { + const TEST_FONT_BYTES: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../test_assets/fonts/NotoSans-Regular.ttf" + )); + + let font = FontData::new(Blob::new(Arc::new(TEST_FONT_BYTES)), 0); + let style = Style::Fill(Fill::NonZero); + let mut glyphs = [imaging::record::Glyph { + id: 1, + x: 0.0, + y: 0.0, + }] + .into_iter(); + + let mut scene = vello::Scene::new(); + let mut sink = VelloSceneSink::new(&mut scene, Rect::new(0.0, 0.0, 32.0, 32.0)); + sink.glyph_run( + GlyphRunRef::new(&font, &style, Color::BLACK) + .brush_transform(Some(Affine::translate((-200.0, 0.0)))), + &mut glyphs, + ); + sink.finish().expect("glyph brush transform should encode"); + } + + #[test] + #[cfg(all( + not(feature = "vello-0-9"), + any(feature = "vello-0-7", feature = "vello-0-8") + ))] + fn vello_scene_sink_rejects_glyph_brush_transforms_before_vello_0_9() { let font = FontData::new(Blob::new(Arc::new([0_u8, 1_u8, 2_u8, 3_u8])), 0); let style = Style::Fill(Fill::NonZero); let mut glyphs = [imaging::record::Glyph { diff --git a/imaging_vello_cpu/CHANGELOG.md b/imaging_vello_cpu/CHANGELOG.md index 13358aa..7d82ea2 100644 --- a/imaging_vello_cpu/CHANGELOG.md +++ b/imaging_vello_cpu/CHANGELOG.md @@ -13,12 +13,21 @@ on 2026-05-21. You can find its changes [documented below](#001-2026-05-21). ## [Unreleased] +### Changed + +- Updated the backend to `vello_cpu` 0.0.9 and `vello_common` 0.0.9. + ([#64][] by [@waywardmonkeys][]) + ## [0.0.1][] (2026-05-21) This release has an [MSRV][] of 1.92. This is the initial release. +[@waywardmonkeys]: https://github.com/waywardmonkeys + +[#64]: https://github.com/forest-rs/imaging/pull/64 + [Unreleased]: https://github.com/forest-rs/imaging/compare/imaging_vello_cpu-v0.0.1...HEAD [0.0.1]: https://github.com/forest-rs/imaging/releases/tag/imaging_vello_cpu-v0.0.1 diff --git a/imaging_vello_cpu/Cargo.toml b/imaging_vello_cpu/Cargo.toml index 42373ba..f00e417 100644 --- a/imaging_vello_cpu/Cargo.toml +++ b/imaging_vello_cpu/Cargo.toml @@ -14,8 +14,8 @@ categories = ["graphics", "rendering"] imaging.workspace = true kurbo.workspace = true peniko.workspace = true -vello_common = { default-features = false, version = "0.0.7" } -vello_cpu = { default-features = false, features = ["text", "u8_pipeline"], version = "0.0.7" } +vello_common = { default-features = false, version = "0.0.9" } +vello_cpu = { default-features = false, features = ["text", "u8_pipeline"], version = "0.0.9" } [lints] workspace = true diff --git a/imaging_vello_cpu/src/lib.rs b/imaging_vello_cpu/src/lib.rs index 2c0a1eb..c9e0acd 100644 --- a/imaging_vello_cpu/src/lib.rs +++ b/imaging_vello_cpu/src/lib.rs @@ -76,13 +76,14 @@ use imaging::{ RenderContentError, RenderSource, RenderUnsupportedError, }, }; -use kurbo::{Affine, Rect, Shape as _}; +use kurbo::{Affine, Shape as _}; use peniko::{BlendMode, Brush, BrushRef, Fill, Style}; use vello_common::filter_effects::{EdgeMode, Filter as VelloFilter, FilterGraph, FilterPrimitive}; -use vello_common::glyph::Glyph as VelloGlyph; use vello_common::paint::{Image as VelloImage, ImageSource}; use vello_cpu::kurbo::{BezPath, StrokeOpts, stroke}; -use vello_cpu::{Pixmap, RenderContext, RenderMode, RenderSettings}; +use vello_cpu::{ + Glyph as VelloGlyph, Pixmap, RenderContext, RenderMode, RenderSettings, Resources, +}; /// Errors that can occur when rendering via Vello CPU. #[derive(Debug)] @@ -109,6 +110,7 @@ impl core::error::Error for Error {} #[derive(Debug)] pub struct VelloCpuRenderer { ctx: RenderContext, + resources: Resources, width: u16, height: u16, tolerance: f64, @@ -149,6 +151,7 @@ impl VelloCpuRenderer { let ctx = RenderContext::new_with(width, height, Self::render_settings()); Self { ctx, + resources: Resources::new(), width, height, tolerance: 0.1, @@ -190,6 +193,7 @@ impl VelloCpuRenderer { } self.ctx = RenderContext::new_with(width, height, Self::render_settings()); + self.resources = Resources::new(); self.width = width; self.height = height; self.clear_cached_masks(); @@ -240,6 +244,7 @@ impl VelloCpuRenderer { image.resize(u32::from(self.width), u32::from(self.height)); self.ctx.flush(); self.ctx.render_to_buffer( + &mut self.resources, image.data.as_mut_slice(), self.width, self.height, @@ -280,6 +285,7 @@ impl VelloCpuRenderer { self.ctx.flush(); self.ctx.render_to_buffer( + &mut self.resources, &mut target.data[..required_len], self.width, self.height, @@ -302,14 +308,6 @@ impl VelloCpuRenderer { } } - fn rect_fits_viewport_fast_path(&self, rect: &Rect, transform: Affine) -> bool { - let transformed = transform.transform_rect_bbox(*rect); - transformed.x0 >= 0.0 - && transformed.y0 >= 0.0 - && transformed.x1 <= f64::from(self.width) - && transformed.y1 <= f64::from(self.height) - } - fn brush_to_paint( &mut self, brush: BrushRef<'_>, @@ -413,13 +411,23 @@ impl VelloCpuRenderer { .set_paint_transform(glyph_run.brush_transform.unwrap_or(Affine::IDENTITY)); self.ctx.set_paint(paint); self.ctx.set_blend_mode(glyph_run.composite.blend); + // TODO: Revisit this allocation. `PaintSink` currently gives us a + // one-shot iterator, while glifo's glyph builders need a cloneable + // glyph source. + let glyphs = glyphs + .map(|glyph| VelloGlyph { + id: glyph.id, + x: glyph.x, + y: glyph.y, + }) + .collect::>(); match glyph_run.style { Style::Fill(fill_rule) => { self.ctx.set_fill_rule(*fill_rule); let builder = self .ctx - .glyph_run(glyph_run.font) + .glyph_run(&mut self.resources, glyph_run.font) .font_size(glyph_run.font_size) .hint(glyph_run.hint) .normalized_coords(glyph_run.normalized_coords); @@ -428,18 +436,13 @@ impl VelloCpuRenderer { } else { builder }; - let glyphs = glyphs.map(|glyph| VelloGlyph { - id: glyph.id, - x: glyph.x, - y: glyph.y, - }); - builder.fill_glyphs(glyphs); + builder.fill_glyphs(glyphs.into_iter()); } Style::Stroke(stroke) => { self.ctx.set_stroke(stroke.clone()); let builder = self .ctx - .glyph_run(glyph_run.font) + .glyph_run(&mut self.resources, glyph_run.font) .font_size(glyph_run.font_size) .hint(glyph_run.hint) .normalized_coords(glyph_run.normalized_coords); @@ -448,12 +451,7 @@ impl VelloCpuRenderer { } else { builder }; - let glyphs = glyphs.map(|glyph| VelloGlyph { - id: glyph.id, - x: glyph.x, - y: glyph.y, - }); - builder.stroke_glyphs(glyphs); + builder.stroke_glyphs(glyphs.into_iter()); } } } @@ -498,7 +496,9 @@ impl VelloCpuRenderer { let mut pixmap = Pixmap::new(self.width, self.height); renderer.ctx.flush(); - renderer.ctx.render_to_pixmap(&mut pixmap); + renderer + .ctx + .render_to_pixmap(&mut renderer.resources, &mut pixmap); let mask = match mode { MaskMode::Alpha => vello_cpu::Mask::new_alpha(&pixmap), MaskMode::Luminance => vello_cpu::Mask::new_luminance(&pixmap), @@ -708,16 +708,7 @@ impl PaintSink for VelloCpuRenderer { self.ctx.set_paint(paint); match draw.shape { - GeometryRef::Rect(r) => { - if self.rect_fits_viewport_fast_path(&r, draw.transform) { - self.ctx.fill_rect(&r); - } else { - // Work around a vello_cpu 0.0.7 rect fast-path panic for strips that land - // outside the viewport. Fall back to path filling until the upstream fix lands. - let path = r.to_path(self.tolerance); - self.ctx.fill_path(&path); - } - } + GeometryRef::Rect(r) => self.ctx.fill_rect(&r), GeometryRef::RoundedRect(rr) => { let path = rr.to_path(self.tolerance); self.ctx.fill_path(&path); diff --git a/imaging_vello_hybrid/CHANGELOG.md b/imaging_vello_hybrid/CHANGELOG.md index 608e700..915fd8e 100644 --- a/imaging_vello_hybrid/CHANGELOG.md +++ b/imaging_vello_hybrid/CHANGELOG.md @@ -13,12 +13,21 @@ released on 2026-05-21. You can find its changes [documented below](#001-2026-05 ## [Unreleased] +### Changed + +- Updated the backend to `vello_hybrid` 0.0.9 and `wgpu` 29. + ([#64][] by [@waywardmonkeys][]) + ## [0.0.1][] (2026-05-21) This release has an [MSRV][] of 1.92. This is the initial release. +[@waywardmonkeys]: https://github.com/waywardmonkeys + +[#64]: https://github.com/forest-rs/imaging/pull/64 + [Unreleased]: https://github.com/forest-rs/imaging/compare/imaging_vello_hybrid-v0.0.1...HEAD [0.0.1]: https://github.com/forest-rs/imaging/releases/tag/imaging_vello_hybrid-v0.0.1 diff --git a/imaging_vello_hybrid/Cargo.toml b/imaging_vello_hybrid/Cargo.toml index 19865ef..78227d8 100644 --- a/imaging_vello_hybrid/Cargo.toml +++ b/imaging_vello_hybrid/Cargo.toml @@ -11,16 +11,17 @@ keywords = ["gpu", "graphics", "imaging", "rendering", "vello"] categories = ["graphics", "rendering"] [dependencies] +glifo = { default-features = false, version = "0.1" } imaging = { features = ["std"], workspace = true } -imaging_wgpu = { default-features = false, features = ["wgpu-28"], workspace = true } +imaging_wgpu = { default-features = false, features = ["wgpu-29"], workspace = true } kurbo = { default-features = true, workspace = true } peniko = { default-features = true, workspace = true } -vello_common = { default-features = true, version = "0.0.7" } -vello_hybrid = { default-features = true, version = "0.0.7" } +vello_common = { default-features = true, version = "0.0.9" } +vello_hybrid = { default-features = true, version = "0.0.9" } wgpu = { default-features = false, features = [ "std", "wgsl", -], version = "28.0.0" } +], version = "29.0.3" } [lints] workspace = true diff --git a/imaging_vello_hybrid/src/image_registry.rs b/imaging_vello_hybrid/src/image_registry.rs index e29681e..a261570 100644 --- a/imaging_vello_hybrid/src/image_registry.rs +++ b/imaging_vello_hybrid/src/image_registry.rs @@ -31,6 +31,7 @@ impl HybridImageRegistry { pub(crate) fn begin_upload_session<'a>( &'a mut self, + resources: &mut vello_hybrid::Resources, renderer: &'a mut vello_hybrid::Renderer, device: &'a wgpu::Device, queue: &'a wgpu::Queue, @@ -42,7 +43,7 @@ impl HybridImageRegistry { // and the exact memory usage isn't as important. While doing it at resolve time // can lead to situations where we evict something from the same session, // which would mean dangling image references in draw calls. - self.evict_to_budget(renderer, device, queue, &mut encoder); + self.evict_to_budget(resources, renderer, device, queue, &mut encoder); HybridImageUploadSession { registry: self, @@ -68,6 +69,7 @@ impl HybridImageRegistry { fn evict_to_budget( &mut self, + resources: &mut vello_hybrid::Resources, renderer: &mut vello_hybrid::Renderer, device: &wgpu::Device, queue: &wgpu::Queue, @@ -78,19 +80,20 @@ impl HybridImageRegistry { break; }; self.bytes_used = self.bytes_used.saturating_sub(oldest.bytes); - renderer.destroy_image(device, queue, encoder, oldest.id); + renderer.destroy_image(resources, device, queue, encoder, oldest.id); } } pub(crate) fn clear( &mut self, + resources: &mut vello_hybrid::Resources, renderer: &mut vello_hybrid::Renderer, device: &wgpu::Device, queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, ) { for image in self.live.drain(..) { - renderer.destroy_image(device, queue, encoder, image.id); + renderer.destroy_image(resources, device, queue, encoder, image.id); } self.bytes_used = 0; } @@ -106,7 +109,11 @@ pub(crate) struct HybridImageUploadSession<'a> { } impl HybridImageUploadSession<'_> { - pub(crate) fn resolve_image_brush(&mut self, brush: &ImageBrush) -> Result { + pub(crate) fn resolve_image_brush( + &mut self, + resources: &mut vello_hybrid::Resources, + brush: &ImageBrush, + ) -> Result { let key = ImageKey::derive(&brush.image); let image = if let Some(image) = self.pending.iter().find(|ri| ri.key == key).copied() { image @@ -121,6 +128,7 @@ impl HybridImageUploadSession<'_> { )); }; let id = self.renderer.upload_image( + resources, self.device, self.queue, self.encoder @@ -131,7 +139,7 @@ impl HybridImageUploadSession<'_> { let image = RegisteredImage { key, id, - may_have_opacities: pixmap.may_have_opacities(), + may_have_transparency: pixmap.may_have_transparency(), bytes: brush .image .format @@ -143,12 +151,15 @@ impl HybridImageUploadSession<'_> { }; Ok(VelloImage { - image: ImageSource::opaque_id_with_opacity_hint(image.id, image.may_have_opacities), + image: ImageSource::opaque_id_with_transparency_hint( + image.id, + image.may_have_transparency, + ), sampler: brush.sampler, }) } - pub(crate) fn finish(&mut self, success: bool) { + pub(crate) fn finish(&mut self, resources: &mut vello_hybrid::Resources, success: bool) { if success { for image in self.pending.drain(..) { self.registry.live.push_back(image); @@ -157,6 +168,7 @@ impl HybridImageUploadSession<'_> { } else { for image in self.pending.drain(..) { self.renderer.destroy_image( + resources, self.device, self.queue, self.encoder @@ -177,7 +189,7 @@ impl HybridImageUploadSession<'_> { struct RegisteredImage { key: ImageKey, id: ImageId, - may_have_opacities: bool, + may_have_transparency: bool, bytes: usize, } @@ -255,13 +267,13 @@ mod tests { let a_ri = RegisteredImage { key: a_key, id: ImageId::new(0), - may_have_opacities: true, + may_have_transparency: true, bytes: a.data.len(), }; let b_ri = RegisteredImage { key: b_key, id: ImageId::new(1), - may_have_opacities: true, + may_have_transparency: true, bytes: b.data.len(), }; diff --git a/imaging_vello_hybrid/src/lib.rs b/imaging_vello_hybrid/src/lib.rs index 96976eb..2272235 100644 --- a/imaging_vello_hybrid/src/lib.rs +++ b/imaging_vello_hybrid/src/lib.rs @@ -161,16 +161,17 @@ mod image_registry; mod scene_sink; mod wgpu_support; -use image_registry::{HybridImageRegistry, HybridImageUploadSession}; +use image_registry::HybridImageRegistry; use imaging::RgbaImage; use imaging::record::{Scene, ValidateError, replay}; use imaging::render::{ GpuReadbackError, ImageBufferFormat, ImageBufferTarget, ImageRenderer, ImageRendererError, ImageTargetError, RenderContentError, RenderSource, RenderUnsupportedError, }; -pub use imaging_wgpu::wgpu; -use imaging_wgpu::{TextureRenderer, TextureRendererError, TextureTargetError, TextureViewTarget}; -use vello_hybrid::{RenderError, RenderSize, RenderTargetConfig}; +pub use imaging_wgpu::v29::wgpu; +use imaging_wgpu::v29::{TextureRenderer, TextureViewTarget}; +use imaging_wgpu::{TextureRendererError, TextureTargetError}; +use vello_hybrid::{RenderError, RenderSize, RenderTargetConfig, TextureBindings}; use wgpu::{CommandEncoderDescriptor, TextureFormat}; use crate::wgpu_support::{ @@ -210,6 +211,7 @@ impl core::error::Error for Error {} #[derive(Debug)] pub(crate) struct VelloHybridRendererState { renderer: vello_hybrid::Renderer, + resources: vello_hybrid::Resources, device: wgpu::Device, queue: wgpu::Queue, tolerance: f64, @@ -246,6 +248,7 @@ impl VelloHybridRendererState { Self { renderer, + resources: vello_hybrid::Resources::new(), device, queue, tolerance: 0.1, @@ -253,29 +256,19 @@ impl VelloHybridRendererState { } } - pub(crate) fn begin_image_upload_session( - &mut self, - label: &'static str, - ) -> HybridImageUploadSession<'_> { - let encoder = self - .device - .create_command_encoder(&CommandEncoderDescriptor { label: Some(label) }); - self.image_registry.begin_upload_session( - &mut self.renderer, - &self.device, - &self.queue, - encoder, - ) - } - fn clear_cached_images(&mut self) { let mut encoder = self .device .create_command_encoder(&CommandEncoderDescriptor { label: Some("imaging_vello_hybrid clear cached images"), }); - self.image_registry - .clear(&mut self.renderer, &self.device, &self.queue, &mut encoder); + self.image_registry.clear( + &mut self.resources, + &mut self.renderer, + &self.device, + &self.queue, + &mut encoder, + ); self.queue.submit([encoder.finish()]); } @@ -297,11 +290,13 @@ impl VelloHybridRendererState { self.renderer .render( scene, + &mut self.resources, &self.device, &self.queue, &mut encoder, &render_size, texture_view, + &TextureBindings::new(), ) .map_err(Error::Render)?; @@ -330,13 +325,6 @@ impl VelloHybridRenderer { self.state.clear_cached_images(); } - pub(crate) fn begin_image_upload_session( - &mut self, - label: &'static str, - ) -> HybridImageUploadSession<'_> { - self.state.begin_image_upload_session(label) - } - /// Lower a semantic [`imaging::record::Scene`] into a native [`vello_hybrid::Scene`]. pub fn encode_scene( &mut self, diff --git a/imaging_vello_hybrid/src/scene_sink.rs b/imaging_vello_hybrid/src/scene_sink.rs index 2808c81..a32cdf8 100644 --- a/imaging_vello_hybrid/src/scene_sink.rs +++ b/imaging_vello_hybrid/src/scene_sink.rs @@ -3,17 +3,18 @@ use super::Error; use crate::{VelloHybridRenderer, image_registry::HybridImageUploadSession}; +use glifo::Glyph as VelloGlyph; use imaging::{ BlurredRoundedRect, ClipRef, Composite, FillRef, GeometryRef, GlyphRunRef, GroupRef, PaintSink, StrokeRef, }; use kurbo::{Affine, Shape as _}; use peniko::{Brush, BrushRef, ImageBrush, Style}; -use vello_common::glyph::Glyph as VelloGlyph; /// Borrowed adapter that streams `imaging` commands into an existing [`vello_hybrid::Scene`]. pub struct VelloHybridSceneSink<'a> { scene: &'a mut vello_hybrid::Scene, + resources: SceneSinkResources<'a>, image_upload: Option>, tolerance: f64, error: Option, @@ -21,6 +22,20 @@ pub struct VelloHybridSceneSink<'a> { group_depth: u32, } +enum SceneSinkResources<'a> { + Owned(Box), + Borrowed(&'a mut vello_hybrid::Resources), +} + +impl SceneSinkResources<'_> { + fn as_mut(&mut self) -> &mut vello_hybrid::Resources { + match self { + Self::Owned(resources) => resources.as_mut(), + Self::Borrowed(resources) => resources, + } + } +} + impl core::fmt::Debug for VelloHybridSceneSink<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("VelloHybridSceneSink") @@ -37,6 +52,7 @@ impl<'a> VelloHybridSceneSink<'a> { pub fn new(scene: &'a mut vello_hybrid::Scene) -> Self { Self { scene, + resources: SceneSinkResources::Owned(Box::new(vello_hybrid::Resources::new())), image_upload: None, tolerance: 0.1, error: None, @@ -54,11 +70,23 @@ impl<'a> VelloHybridSceneSink<'a> { scene: &'a mut vello_hybrid::Scene, renderer: &'a mut VelloHybridRenderer, ) -> Self { + let state = &mut renderer.state; + let encoder = state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("imaging_vello_hybrid scene upload images"), + }); + let image_upload = state.image_registry.begin_upload_session( + &mut state.resources, + &mut state.renderer, + &state.device, + &state.queue, + encoder, + ); Self { scene, - image_upload: Some( - renderer.begin_image_upload_session("imaging_vello_hybrid scene upload images"), - ), + resources: SceneSinkResources::Borrowed(&mut state.resources), + image_upload: Some(image_upload), tolerance: 0.1, error: None, clip_depth: 0, @@ -84,7 +112,7 @@ impl<'a> VelloHybridSceneSink<'a> { }; if let Some(mut image_upload) = self.image_upload.take() { - image_upload.finish(result.is_ok()); + image_upload.finish(self.resources.as_mut(), result.is_ok()); } result @@ -114,7 +142,7 @@ impl<'a> VelloHybridSceneSink<'a> { self.set_error_once(Error::UnsupportedImageBrush); return None; }; - match image_upload.resolve_image_brush(image) { + match image_upload.resolve_image_brush(self.resources.as_mut(), image) { Ok(image) => Some(image), Err(err) => { self.set_error_once(err); @@ -169,13 +197,23 @@ impl<'a> VelloHybridSceneSink<'a> { .set_paint_transform(glyph_run.brush_transform.unwrap_or(Affine::IDENTITY)); self.scene.set_blend_mode(glyph_run.composite.blend); self.scene.set_paint(paint); + // TODO: Revisit this allocation. `PaintSink` currently gives us a + // one-shot iterator, while glifo's glyph builders need a cloneable + // glyph source. + let glyphs = glyphs + .map(|glyph| VelloGlyph { + id: glyph.id, + x: glyph.x, + y: glyph.y, + }) + .collect::>(); match glyph_run.style { Style::Fill(fill_rule) => { self.scene.set_fill_rule(*fill_rule); let builder = self .scene - .glyph_run(glyph_run.font) + .glyph_run(self.resources.as_mut(), glyph_run.font) .font_size(glyph_run.font_size) .hint(glyph_run.hint) .normalized_coords(glyph_run.normalized_coords); @@ -184,18 +222,13 @@ impl<'a> VelloHybridSceneSink<'a> { } else { builder }; - let glyphs = glyphs.map(|glyph| VelloGlyph { - id: glyph.id, - x: glyph.x, - y: glyph.y, - }); - builder.fill_glyphs(glyphs); + builder.fill_glyphs(glyphs.into_iter()); } Style::Stroke(stroke) => { self.scene.set_stroke(stroke.clone()); let builder = self .scene - .glyph_run(glyph_run.font) + .glyph_run(self.resources.as_mut(), glyph_run.font) .font_size(glyph_run.font_size) .hint(glyph_run.hint) .normalized_coords(glyph_run.normalized_coords); @@ -204,12 +237,7 @@ impl<'a> VelloHybridSceneSink<'a> { } else { builder }; - let glyphs = glyphs.map(|glyph| VelloGlyph { - id: glyph.id, - x: glyph.x, - y: glyph.y, - }); - builder.stroke_glyphs(glyphs); + builder.stroke_glyphs(glyphs.into_iter()); } } } diff --git a/imaging_wgpu/CHANGELOG.md b/imaging_wgpu/CHANGELOG.md index facf0d9..d5f08bc 100644 --- a/imaging_wgpu/CHANGELOG.md +++ b/imaging_wgpu/CHANGELOG.md @@ -13,12 +13,21 @@ The latest published `imaging_wgpu` release is [0.0.1](#001-2026-05-21), which w ## [Unreleased] +### Changed + +- Bumped the default renderer lane to `wgpu` 29 while keeping `wgpu` 27 and 28 available through + explicit version features and modules. ([#64][] by [@waywardmonkeys][]) + ## [0.0.1][] (2026-05-21) This release has an [MSRV][] of 1.92. This is the initial release. +[@waywardmonkeys]: https://github.com/waywardmonkeys + +[#64]: https://github.com/forest-rs/imaging/pull/64 + [Unreleased]: https://github.com/forest-rs/imaging/compare/imaging_wgpu-v0.0.1...HEAD [0.0.1]: https://github.com/forest-rs/imaging/releases/tag/imaging_wgpu-v0.0.1 diff --git a/imaging_wgpu/Cargo.toml b/imaging_wgpu/Cargo.toml index 063c1d5..e6a1d64 100644 --- a/imaging_wgpu/Cargo.toml +++ b/imaging_wgpu/Cargo.toml @@ -11,21 +11,23 @@ keywords = ["graphics", "imaging", "rendering", "wgpu"] categories = ["graphics", "rendering"] [features] -default = ["wgpu-28"] -# Normal consumers should enable exactly one version feature. If both are enabled by a -# workspace-wide `--all-features` build, the crate exports `wgpu-28` deterministically. +default = ["wgpu-29"] +# Normal consumers should enable exactly one version feature. If multiple are enabled by a +# workspace-wide `--all-features` build, the crate exports `wgpu-29` deterministically. wgpu-27 = ["dep:wgpu_27"] wgpu-28 = ["dep:wgpu_28"] +wgpu-29 = ["dep:wgpu_29"] [dependencies] imaging = { features = ["std"], workspace = true } wgpu_27 = { default-features = false, features = ["std"], optional = true, package = "wgpu", version = "27.0.1" } wgpu_28 = { default-features = false, features = ["std"], optional = true, package = "wgpu", version = "28.0.0" } +wgpu_29 = { default-features = false, features = ["std"], optional = true, package = "wgpu", version = "29.0.3" } [lints] workspace = true [package.metadata.docs.rs] -features = ["wgpu-28"] +features = ["wgpu-29"] default-target = "x86_64-unknown-linux-gnu" targets = [] diff --git a/imaging_wgpu/README.md b/imaging_wgpu/README.md index 15f8e95..05fa353 100644 --- a/imaging_wgpu/README.md +++ b/imaging_wgpu/README.md @@ -23,18 +23,20 @@ Full documentation at https://github.com/orium/cargo-rdme --> `wgpu` texture rendering traits for `imaging` backends. -The default feature set enables `wgpu-28`. Consumers that need `wgpu-27` should disable -default features and enable `wgpu-27`. +The default feature set enables `wgpu-29`. Consumers that need `wgpu-27` or `wgpu-28` +should disable default features and enable the matching version feature. -Workspace-wide `--all-features` builds enable both, so this crate resolves that case -deterministically by exporting `wgpu-28` as [`wgpu`] and keeping `wgpu-27` linked only to -satisfy older dependents that may still request it transitively. +Workspace-wide `--all-features` builds enable multiple lanes, so this crate resolves that case +deterministically by exporting `wgpu-29` as [`wgpu`]. Backends that must stay on an older +`wgpu` version should use the explicit [`v27`] or [`v28`] module. This crate is `std`-only because it depends on `wgpu`. [`wgpu`]: https://docs.rs/imaging_wgpu/latest/imaging_wgpu/wgpu/ +[`v27`]: https://docs.rs/imaging_wgpu/latest/imaging_wgpu/v27/ +[`v28`]: https://docs.rs/imaging_wgpu/latest/imaging_wgpu/v28/ ## Minimum supported Rust Version (MSRV) diff --git a/imaging_wgpu/src/lib.rs b/imaging_wgpu/src/lib.rs index 9bae24e..3233912 100644 --- a/imaging_wgpu/src/lib.rs +++ b/imaging_wgpu/src/lib.rs @@ -3,57 +3,22 @@ //! `wgpu` texture rendering traits for `imaging` backends. //! -//! The default feature set enables `wgpu-28`. Consumers that need `wgpu-27` should disable -//! default features and enable `wgpu-27`. +//! The default feature set enables `wgpu-29`. Consumers that need `wgpu-27` or `wgpu-28` +//! should disable default features and enable the matching version feature. //! -//! Workspace-wide `--all-features` builds enable both, so this crate resolves that case -//! deterministically by exporting `wgpu-28` as [`wgpu`] and keeping `wgpu-27` linked only to -//! satisfy older dependents that may still request it transitively. +//! Workspace-wide `--all-features` builds enable multiple lanes, so this crate resolves that case +//! deterministically by exporting `wgpu-29` as [`wgpu`]. Backends that must stay on an older +//! `wgpu` version should use the explicit [`v27`] or [`v28`] module. //! //! This crate is `std`-only because it depends on `wgpu`. #![deny(unsafe_code)] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use imaging::render::{ImageRenderer, RenderSource}; use std::boxed::Box; -use std::vec::Vec; -#[cfg(not(any(feature = "wgpu-27", feature = "wgpu-28")))] -compile_error!("Enable one of `wgpu-27` or `wgpu-28`."); - -// `cargo --all-features` enables both version flags across the workspace. Prefer the newer API -// surface in that case so the crate still compiles deterministically. -#[cfg(all(feature = "wgpu-27", feature = "wgpu-28"))] -use wgpu_27 as _; - -#[cfg(all(not(feature = "wgpu-28"), feature = "wgpu-27"))] -pub use wgpu_27 as wgpu; -#[cfg(feature = "wgpu-28")] -pub use wgpu_28 as wgpu; - -/// Shared `wgpu` texture-view render target used by view-based [`TextureRenderer`] backends. -#[derive(Clone, Debug)] -pub struct TextureViewTarget { - /// Render-target texture view. - pub view: wgpu::TextureView, - /// Effective render width in pixels. - pub width: u32, - /// Effective render height in pixels. - pub height: u32, -} - -impl TextureViewTarget { - /// Create a shared texture-view render target wrapper. - #[must_use] - pub fn new(view: &wgpu::TextureView, width: u32, height: u32) -> Self { - Self { - view: view.clone(), - width, - height, - } - } -} +#[cfg(not(any(feature = "wgpu-27", feature = "wgpu-28", feature = "wgpu-29")))] +compile_error!("Enable one of `wgpu-27`, `wgpu-28`, or `wgpu-29`."); /// Shared texture-render-target failures surfaced by texture renderers. #[derive(Debug)] @@ -136,38 +101,93 @@ impl core::error::Error for TextureRendererError { } } -/// Renderer capability for drawing a [`RenderSource`] into a backend-selected `wgpu` texture target. -/// -/// These methods intentionally use names distinct from [`imaging::render::ImageRenderer`] so -/// renderers that implement both traits can be called without fully qualified syntax. -pub trait TextureRenderer: ImageRenderer { - /// Concrete caller-owned target type consumed by this renderer. - type TextureTarget; - - /// Owned texture type returned by [`Self::render_source_texture`]. - type Texture; - - /// Return the `wgpu` texture formats this renderer can draw into directly. - /// - /// Callers can use this to preflight swapchain or offscreen target formats before calling - /// [`Self::render_source_into_texture`]. - fn supported_texture_formats(&self) -> Vec; - - /// Render a source into a caller-provided texture target. - /// - /// Renderers should treat the target as a fresh output and may clear or overwrite any - /// existing contents before drawing. - fn render_source_into_texture( - &mut self, - source: &mut dyn RenderSource, - target: Self::TextureTarget, - ) -> Result<(), TextureRendererError>; - - /// Render a source and return a backend-owned texture. - fn render_source_texture( - &mut self, - source: &mut dyn RenderSource, - width: u32, - height: u32, - ) -> Result; +macro_rules! define_wgpu_lane { + ($module:ident, $wgpu:ident) => { + /// Version-specific `wgpu` texture rendering traits and target wrappers. + pub mod $module { + use imaging::render::{ImageRenderer, RenderSource}; + use std::vec::Vec; + + pub use $wgpu as wgpu; + + /// Shared `wgpu` texture-view render target used by view-based [`TextureRenderer`] + /// backends. + #[derive(Clone, Debug)] + pub struct TextureViewTarget { + /// Render-target texture view. + pub view: wgpu::TextureView, + /// Effective render width in pixels. + pub width: u32, + /// Effective render height in pixels. + pub height: u32, + } + + impl TextureViewTarget { + /// Create a shared texture-view render target wrapper. + #[must_use] + pub fn new(view: &wgpu::TextureView, width: u32, height: u32) -> Self { + Self { + view: view.clone(), + width, + height, + } + } + } + + /// Renderer capability for drawing a [`RenderSource`] into a backend-selected `wgpu` + /// texture target. + /// + /// These methods intentionally use names distinct from + /// [`imaging::render::ImageRenderer`] so renderers that implement both traits can be + /// called without fully qualified syntax. + pub trait TextureRenderer: ImageRenderer { + /// Concrete caller-owned target type consumed by this renderer. + type TextureTarget; + + /// Owned texture type returned by [`Self::render_source_texture`]. + type Texture; + + /// Return the `wgpu` texture formats this renderer can draw into directly. + /// + /// Callers can use this to preflight swapchain or offscreen target formats before + /// calling [`Self::render_source_into_texture`]. + fn supported_texture_formats(&self) -> Vec; + + /// Render a source into a caller-provided texture target. + /// + /// Renderers should treat the target as a fresh output and may clear or overwrite + /// any existing contents before drawing. + fn render_source_into_texture( + &mut self, + source: &mut dyn RenderSource, + target: Self::TextureTarget, + ) -> Result<(), crate::TextureRendererError>; + + /// Render a source and return a backend-owned texture. + fn render_source_texture( + &mut self, + source: &mut dyn RenderSource, + width: u32, + height: u32, + ) -> Result; + } + } + }; } + +#[cfg(feature = "wgpu-27")] +define_wgpu_lane!(v27, wgpu_27); +#[cfg(feature = "wgpu-28")] +define_wgpu_lane!(v28, wgpu_28); +#[cfg(feature = "wgpu-29")] +define_wgpu_lane!(v29, wgpu_29); + +#[cfg(all( + not(any(feature = "wgpu-28", feature = "wgpu-29")), + feature = "wgpu-27" +))] +pub use v27::{TextureRenderer, TextureViewTarget, wgpu}; +#[cfg(all(not(feature = "wgpu-29"), feature = "wgpu-28"))] +pub use v28::{TextureRenderer, TextureViewTarget, wgpu}; +#[cfg(feature = "wgpu-29")] +pub use v29::{TextureRenderer, TextureViewTarget, wgpu};