commit 93090c66ae0be7aeb42653ab809bdb5291eb7fa4
parent a768b8ec470f48159b3b84d287fc82bbb6ae5c03
Author: Glenn Watson <git@chillybin.org>
Date: Mon, 6 Oct 2025 19:31:55 +0000
Bug 1992049 - Add initial wrshell command to convert a WR capture to a wrench scene r=gfx-reviewers,lsalzman
Differential Revision: https://phabricator.services.mozilla.com/D267153
Diffstat:
6 files changed, 1266 insertions(+), 1 deletion(-)
diff --git a/gfx/wr/webrender/src/lib.rs b/gfx/wr/webrender/src/lib.rs
@@ -121,7 +121,7 @@ mod render_task_cache;
mod render_task;
mod renderer;
mod resource_cache;
-mod scene;
+pub mod scene;
mod scene_builder_thread;
mod scene_building;
mod screen_capture;
diff --git a/gfx/wr/wrshell/Cargo.lock b/gfx/wr/wrshell/Cargo.lock
@@ -27,6 +27,15 @@ dependencies = [
]
[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "anstream"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -119,6 +128,12 @@ dependencies = [
]
[[package]]
+name = "ascii"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
+
+[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -152,6 +167,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -176,6 +200,17 @@ dependencies = [
]
[[package]]
+name = "build-parallel"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e3ff9db740167616e528c509b3618046fc05d337f8f3182d300f4aa977d2bb"
+dependencies = [
+ "crossbeam-utils",
+ "jobserver",
+ "num_cpus",
+]
+
+[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -209,6 +244,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
+name = "chunked_transfer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
+
+[[package]]
name = "clap"
version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -262,6 +303,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "core-graphics"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
+dependencies = [
+ "bitflags 1.2.1",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.2.1",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "core-text"
+version = "20.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5"
+dependencies = [
+ "core-foundation",
+ "core-graphics",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -280,6 +373,25 @@ dependencies = [
]
[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -302,6 +414,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
+name = "derive_more"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -344,6 +476,26 @@ dependencies = [
]
[[package]]
+name = "dwrote"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20c93d234bac0cdd0e2ac08bc8a5133f8df2169e95b262dfcea5e5cb7855672f"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "serde",
+ "serde_derive",
+ "winapi",
+ "wio",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -360,6 +512,17 @@ dependencies = [
]
[[package]]
+name = "etagere"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342"
+dependencies = [
+ "euclid",
+ "serde",
+ "svg_fmt",
+]
+
+[[package]]
name = "euclid"
version = "0.22.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -386,6 +549,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[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",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -395,6 +585,27 @@ dependencies = [
]
[[package]]
+name = "freetype"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a440748e063798e4893ceb877151e84acef9bea9a8c6800645cf3f1b3a7806e"
+dependencies = [
+ "freetype-sys",
+ "libc",
+]
+
+[[package]]
+name = "freetype-sys"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -484,6 +695,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
+name = "gl_generator"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
+dependencies = [
+ "khronos_api",
+ "log",
+ "xml-rs",
+]
+
+[[package]]
+name = "gleam"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8647cc2e2ffde598ce5ca2809452e722dd8dc127885ab8aba2fa8b469cd3ed94"
+dependencies = [
+ "gl_generator",
+]
+
+[[package]]
+name = "glslopt"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "913662ae8335df058d56e00f11340b20fa82e03e0276587797ef325ab01e50d4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
name = "http"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -524,6 +770,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
name = "hyper"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -748,6 +1000,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom 0.3.3",
+ "libc",
+]
+
+[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -758,6 +1020,12 @@ dependencies = [
]
[[package]]
+name = "khronos_api"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
+
+[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -811,6 +1079,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "malloc_size_of_derive"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -894,6 +1171,25 @@ dependencies = [
]
[[package]]
+name = "num_cpus"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -980,6 +1276,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
+name = "plane-split"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c1f7d82649829ecdef8e258790b0587acf0a8403f0ce963473d8e918acc1643"
+dependencies = [
+ "euclid",
+ "log",
+ "smallvec",
+]
+
+[[package]]
name = "potential_utf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1061,6 +1368,26 @@ dependencies = [
]
[[package]]
+name = "rayon"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
name = "redox_syscall"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1157,6 +1484,32 @@ dependencies = [
]
[[package]]
+name = "ron"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f"
+dependencies = [
+ "base64",
+ "bitflags 2.9.2",
+ "serde",
+ "serde_derive",
+ "unicode-ident",
+]
+
+[[package]]
+name = "ron"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468"
+dependencies = [
+ "base64",
+ "bitflags 2.9.2",
+ "serde",
+ "serde_derive",
+ "unicode-ident",
+]
+
+[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1175,6 +1528,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1348,6 +1707,9 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+dependencies = [
+ "serde",
+]
[[package]]
name = "socket2"
@@ -1384,6 +1746,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
+name = "svg_fmt"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
+
+[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1466,6 +1834,18 @@ dependencies = [
]
[[package]]
+name = "tiny_http"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
+dependencies = [
+ "ascii",
+ "chunked_transfer",
+ "httpdate",
+ "log",
+]
+
+[[package]]
name = "tinystr"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1492,6 +1872,12 @@ dependencies = [
]
[[package]]
+name = "topological-sort"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa7c7f42dea4b1b99439786f5633aeb9c14c1b53f75e282803c2ec2ad545873c"
+
+[[package]]
name = "tower"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1556,6 +1942,12 @@ dependencies = [
]
[[package]]
+name = "tracy-rs"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce607aae8ab0ab3abf3a2723a9ab6f09bb8639ed83fdd888d857b8e556c868d8"
+
+[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1743,6 +2135,46 @@ dependencies = [
]
[[package]]
+name = "webrender"
+version = "0.62.0"
+dependencies = [
+ "allocator-api2",
+ "base64",
+ "bincode",
+ "bitflags 2.9.2",
+ "build-parallel",
+ "byteorder",
+ "derive_more",
+ "etagere",
+ "euclid",
+ "gleam",
+ "glslopt",
+ "lazy_static",
+ "log",
+ "malloc_size_of_derive",
+ "num-traits",
+ "peek-poke",
+ "plane-split",
+ "rayon",
+ "ron 0.10.1",
+ "rustc-hash",
+ "serde",
+ "serde_json",
+ "sha1",
+ "smallvec",
+ "svg_fmt",
+ "tiny_http",
+ "topological-sort",
+ "tracy-rs",
+ "url",
+ "webrender_api",
+ "webrender_build",
+ "wr_glyph_rasterizer",
+ "wr_malloc_size_of",
+ "zeitstempel",
+]
+
+[[package]]
name = "webrender_api"
version = "0.62.0"
dependencies = [
@@ -1761,6 +2193,14 @@ dependencies = [
]
[[package]]
+name = "webrender_build"
+version = "0.0.2"
+dependencies = [
+ "bitflags 2.9.2",
+ "lazy_static",
+]
+
+[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1936,6 +2376,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
+name = "wio"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1945,6 +2394,30 @@ dependencies = [
]
[[package]]
+name = "wr_glyph_rasterizer"
+version = "0.1.0"
+dependencies = [
+ "core-foundation",
+ "core-graphics",
+ "core-text",
+ "dwrote",
+ "euclid",
+ "freetype",
+ "lazy_static",
+ "libc",
+ "log",
+ "malloc_size_of_derive",
+ "objc",
+ "rayon",
+ "rustc-hash",
+ "serde",
+ "smallvec",
+ "tracy-rs",
+ "webrender_api",
+ "wr_malloc_size_of",
+]
+
+[[package]]
name = "wr_malloc_size_of"
version = "0.2.2"
dependencies = [
@@ -1967,15 +2440,23 @@ dependencies = [
"imgui-sdl3",
"repl-ng",
"reqwest",
+ "ron 0.11.0",
"sdl3",
"serde",
"serde_json",
"strprox",
"tungstenite",
+ "webrender",
"webrender_api",
]
[[package]]
+name = "xml-rs"
+version = "0.8.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7"
+
+[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/gfx/wr/wrshell/Cargo.toml b/gfx/wr/wrshell/Cargo.toml
@@ -8,6 +8,7 @@ license = "MPL-2.0"
[dependencies]
reqwest = { version = "0.12", default-features = false, features = [ "blocking" ] }
+webrender = { path = "../webrender", features = [ "debugger", "replay" ] }
webrender_api = { path = "../webrender_api", features = [ "debugger" ] }
tungstenite = "0.27.0"
argh = "0.1"
@@ -15,6 +16,7 @@ serde = { version="1", features = ["derive"] }
serde_json = "1"
strprox = "0.3.3"
repl-ng = "0.3"
+ron = "0.11.0"
imgui-sdl3 = { git = "https://github.com/florianvazelle/imgui-sdl3" }
imgui = { version="0.12", features = [ "docking" ] }
diff --git a/gfx/wr/wrshell/src/main.rs b/gfx/wr/wrshell/src/main.rs
@@ -7,6 +7,8 @@ mod command;
mod debug_commands;
mod gui;
mod net;
+mod script_commands;
+mod wrench;
use argh::FromArgs;
use std::str;
@@ -50,6 +52,7 @@ fn main() {
let mut cmd_list = command::CommandList::new();
debug_commands::register(&mut cmd_list);
+ script_commands::register(&mut cmd_list);
match args.mode {
Mode::Repl => {
diff --git a/gfx/wr/wrshell/src/script_commands.rs b/gfx/wr/wrshell/src/script_commands.rs
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::command::{Command, CommandDescriptor, CommandList, ParamDescriptor};
+use crate::command::{CommandContext, CommandOutput};
+use crate::wrench;
+
+use webrender::scene::Scene;
+
+// Implementation of a basic set of script commands
+
+// Register the script commands in this source file
+pub fn register(cmd_list: &mut CommandList) {
+ cmd_list.register_command(Box::new(ProcessCaptureCommand));
+}
+
+pub struct ProcessCaptureCommand;
+
+impl Command for ProcessCaptureCommand {
+ fn descriptor(&self) -> CommandDescriptor {
+ CommandDescriptor {
+ name: "process-capture",
+ help: r#"
+Process a WR capture directory.
+ USAGE: process-capture [scene file] [output file]
+"#,
+ params: &[
+ ParamDescriptor {
+ name: "scene",
+ is_required: true,
+ },
+ ParamDescriptor {
+ name: "out",
+ is_required: true,
+ },
+ ],
+ ..Default::default()
+ }
+ }
+
+ fn run(
+ &mut self,
+ ctx: &mut CommandContext,
+ ) -> CommandOutput {
+ use std::io::Write;
+
+ let source = ctx.arg_string("scene");
+ let target = ctx.arg_string("out");
+
+ println!("Loading scene file '{}'", source);
+ let scene_file = match std::fs::read_to_string(source) {
+ Ok(f) => f,
+ Err(..) => return CommandOutput::Err("\tUnable to read scene".into()),
+ };
+
+ println!("Deserialize scene file '{}'", source);
+ let scene: Scene = match ron::de::from_str(&scene_file) {
+ Ok(out) => out,
+ Err(..) => return CommandOutput::Err("\tDeserialization failed".into()),
+ };
+
+ let yaml = match wrench::scene_to_yaml(&scene) {
+ Ok(yaml) => yaml,
+ Err(err) => return CommandOutput::Err(
+ format!("\tFailed to convert - {}", err)
+ ),
+ };
+
+ let mut output = match std::fs::File::create(target) {
+ Ok(f) => f,
+ Err(..) => return CommandOutput::Err("\tUnable to open output file".into()),
+ };
+ write!(output, "{}", yaml).expect("failed to write yaml");
+
+ CommandOutput::Log(yaml)
+ }
+}
diff --git a/gfx/wr/wrshell/src/wrench.rs b/gfx/wr/wrshell/src/wrench.rs
@@ -0,0 +1,701 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::HashMap;
+
+use webrender::scene::Scene;
+use webrender_api::{units::LayoutRect, BorderDetails, BorderStyle, BuiltDisplayList};
+use webrender_api::{ColorF, DisplayItem, PipelineId, PropertyBinding, SpatialId};
+use webrender_api::{ClipId, SpatialTreeItem, ClipChainId};
+
+fn color_to_string(
+ color: ColorF,
+) -> String {
+ match (color.r, color.g, color.b, color.a) {
+ (1.0, 0.0, 0.0, 1.0) => "red".into(),
+ _ => {
+ format!("{} {} {} {}",
+ color.r * 255.0,
+ color.g * 255.0,
+ color.b * 255.0,
+ color.a,
+ )
+ }
+ }
+}
+
+fn color_to_string_array(
+ color: ColorF,
+) -> String {
+ match (color.r, color.g, color.b, color.a) {
+ (1.0, 0.0, 0.0, 1.0) => "red".into(),
+ _ => {
+ format!("{}, {}, {}, {}",
+ color.r * 255.0,
+ color.g * 255.0,
+ color.b * 255.0,
+ color.a,
+ )
+ }
+ }
+}
+
+fn style_to_string(
+ style: BorderStyle,
+) -> String {
+ match style {
+ BorderStyle::None => "none",
+ BorderStyle::Solid => "solid",
+ BorderStyle::Double => "double",
+ BorderStyle::Dotted => "dotted",
+ BorderStyle::Dashed => "dashed",
+ BorderStyle::Hidden => "hidden",
+ BorderStyle::Ridge => "ridge",
+ BorderStyle::Inset => "inset",
+ BorderStyle::Outset => "outset",
+ BorderStyle::Groove => "groove",
+ }.into()
+}
+
+#[derive(Debug)]
+enum SpatialNodeKind {
+ Reference {
+ },
+ Scroll {
+ },
+ Sticky {
+ },
+}
+
+#[derive(Debug)]
+struct SpatialNode {
+ wrench_id: u64,
+}
+
+struct YamlWriter {
+ out: String,
+ indent: String,
+ spatial_nodes: HashMap<SpatialId, SpatialNode>,
+ clip_id_map: HashMap<ClipId, u64>,
+ clipchain_id_map: HashMap<ClipChainId, u64>,
+ next_wrench_id: u64,
+}
+
+impl YamlWriter {
+ fn new() -> Self {
+ YamlWriter {
+ out: String::new(),
+ indent: String::new(),
+ spatial_nodes: HashMap::new(),
+ next_wrench_id: 2,
+ clip_id_map: HashMap::new(),
+ clipchain_id_map: HashMap::new(),
+ }
+ }
+
+ fn push_level(&mut self) {
+ self.indent.push_str(" ");
+ }
+
+ fn pop_level(&mut self) {
+ self.indent.truncate(self.indent.len() - 2);
+ }
+
+ fn add_clip_id(
+ &mut self,
+ clip_id: ClipId
+ ) -> u64 {
+ let id = self.next_wrench_id;
+ self.next_wrench_id += 1;
+
+ let _prev = self.clip_id_map.insert(clip_id, id);
+ assert!(_prev.is_none());
+
+ id
+ }
+
+ fn add_clipchain_id(
+ &mut self,
+ clipchain_id: ClipChainId
+ ) -> u64 {
+ let id = self.next_wrench_id;
+ self.next_wrench_id += 1;
+
+ let _prev = self.clipchain_id_map.insert(clipchain_id, id);
+ assert!(_prev.is_none());
+
+ id
+ }
+
+ fn add_and_write_spatial_node(
+ &mut self,
+ spatial_id: SpatialId,
+ parent: SpatialId,
+ kind: SpatialNodeKind,
+ ) {
+ match kind {
+ SpatialNodeKind::Reference {} => {
+ self.write_line("- type: reference-frame");
+ self.push_level();
+ self.write_line(&format!("id: {}", self.next_wrench_id));
+ if let Some(parent) = self.spatial_nodes.get(&parent) {
+ self.write_line(&format!("spatial-id: {}", parent.wrench_id));
+ }
+ self.pop_level();
+ }
+ SpatialNodeKind::Scroll {} => {
+ let parent_id = self.spatial_nodes[&parent].wrench_id;
+
+ self.write_line("- type: scroll-frame");
+ self.push_level();
+ self.write_line(&format!("id: {}", self.next_wrench_id));
+ self.write_bounds(LayoutRect::zero());
+ self.write_line(&format!("spatial-id: {}", parent_id));
+ self.pop_level();
+ }
+ SpatialNodeKind::Sticky {} => {
+ let parent_id = self.spatial_nodes[&parent].wrench_id;
+
+ self.write_line("- type: sticky-frame");
+ self.push_level();
+ self.write_line(&format!("id: {}", self.next_wrench_id));
+ self.write_line(&format!("spatial-id: {}", parent_id));
+ self.write_bounds(LayoutRect::zero());
+ self.pop_level();
+ }
+ }
+
+ let _prev = self.spatial_nodes.insert(
+ spatial_id,
+ SpatialNode {
+ wrench_id: self.next_wrench_id,
+ },
+ );
+ assert!(_prev.is_none());
+ self.next_wrench_id += 1;
+ }
+
+ fn write_color(
+ &mut self,
+ color: ColorF,
+ ) {
+ self.write_line(
+ &format!("color: {}", color_to_string(color))
+ );
+ }
+
+ fn write_rect(
+ &mut self,
+ tag: &str,
+ rect: LayoutRect,
+ ) {
+ self.write_line(
+ &format!("{}: {} {} {} {}",
+ tag,
+ rect.min.x,
+ rect.min.y,
+ rect.width(),
+ rect.height(),
+ )
+ );
+ }
+
+ fn write_bounds(
+ &mut self,
+ bounds: LayoutRect,
+ ) {
+ self.write_rect("bounds", bounds);
+ }
+
+ fn maybe_write_clip_rect(
+ &mut self,
+ bounds: LayoutRect,
+ clip_rect: LayoutRect,
+ ) {
+ if bounds != clip_rect {
+ self.write_rect("clip-rect", clip_rect);
+ }
+ }
+
+ fn create_savepoint(
+ &mut self,
+ ) -> (usize, usize) {
+ (self.out.len(), self.indent.len())
+ }
+
+ fn restore_savepoint(
+ &mut self,
+ p: (usize, usize),
+ ) {
+ self.out.truncate(p.0);
+ self.indent.truncate(p.1);
+ }
+
+ fn write_line(
+ &mut self,
+ s: &str,
+ ) {
+ self.out.push_str(&self.indent);
+ self.out.push_str(s);
+ self.out.push_str("\n");
+ }
+
+ fn write_spatial_id(
+ &mut self,
+ id: SpatialId,
+ ) {
+ let spatial_node = self.spatial_nodes
+ .get(&id)
+ .expect(&format!("unknown spatial node {:?}", id));
+
+ self.write_line(&format!("spatial-id: {}", spatial_node.wrench_id));
+ }
+
+ fn write_clip_chain_id(
+ &mut self,
+ id: ClipChainId,
+ ) {
+ if id != ClipChainId::INVALID {
+ let clip_chain_id = self.clipchain_id_map[&id];
+ self.write_line(&format!("clip-chain: {}", clip_chain_id));
+ }
+ }
+
+ fn build_spatial_tree(
+ &mut self,
+ dl: &BuiltDisplayList,
+ pipeline_id: PipelineId,
+ ) {
+ // Insert root ref + scroll frames
+ self.add_and_write_spatial_node(
+ SpatialId::root_reference_frame(pipeline_id),
+ SpatialId::root_reference_frame(pipeline_id),
+ SpatialNodeKind::Reference { },
+ );
+ self.add_and_write_spatial_node(
+ SpatialId::root_scroll_node(pipeline_id),
+ SpatialId::root_reference_frame(pipeline_id),
+ SpatialNodeKind::Scroll { },
+ );
+
+ dl.iter_spatial_tree(|item| {
+ match item {
+ SpatialTreeItem::ScrollFrame(descriptor) => {
+ self.add_and_write_spatial_node(
+ descriptor.scroll_frame_id,
+ descriptor.parent_space,
+ SpatialNodeKind::Scroll {
+ },
+ );
+ }
+ SpatialTreeItem::ReferenceFrame(descriptor) => {
+ self.add_and_write_spatial_node(
+ descriptor.reference_frame.id,
+ descriptor.parent_spatial_id,
+ SpatialNodeKind::Reference {
+ },
+ );
+ }
+ SpatialTreeItem::StickyFrame(descriptor) => {
+ self.add_and_write_spatial_node(
+ descriptor.id,
+ descriptor.parent_spatial_id,
+ SpatialNodeKind::Sticky {
+ },
+ );
+ }
+ SpatialTreeItem::Invalid => {
+ unreachable!();
+ }
+ }
+ });
+ }
+
+ fn write_pipeline(
+ &mut self,
+ scene: &Scene,
+ pipeline_id: PipelineId,
+ ) -> Result<(), String> {
+ enum ContextKind {
+ Root,
+ StackingContext {
+ // sc_info: StackingContextInfo,
+ },
+ }
+ struct BuildContext {
+ kind: ContextKind,
+ }
+
+ let pipeline = &scene.pipelines[&pipeline_id];
+
+ self.build_spatial_tree(
+ &pipeline.display_list.display_list,
+ pipeline_id,
+ );
+
+ let mut stack = vec![BuildContext {
+ kind: ContextKind::Root,
+ }];
+ let mut traversal = pipeline.display_list.iter();
+
+ 'outer: while let Some(bc) = stack.pop() {
+ loop {
+ let item = match traversal.next() {
+ Some(item) => item,
+ None => break,
+ };
+
+ match item.item() {
+ DisplayItem::PushStackingContext(info) => {
+ self.write_line("- type: stacking-context");
+ self.push_level();
+ self.write_spatial_id(info.spatial_id);
+ if let Some(clip_chain_id) = info.stacking_context.clip_chain_id {
+ self.write_clip_chain_id(clip_chain_id);
+ }
+ self.write_line(
+ &format!("bounds: {} {} 0 0",
+ 0.0, //info.origin.x + info.ref_frame_offset.x,
+ 0.0, //info.origin.y + info.ref_frame_offset.y,
+ )
+ );
+ self.write_line("items:");
+ self.push_level();
+
+ let new_context = BuildContext {
+ kind: ContextKind::StackingContext {
+ // sc_info,
+ },
+ };
+ stack.push(bc);
+ stack.push(new_context);
+
+ traversal = item.sub_iter();
+ continue 'outer;
+ }
+ DisplayItem::PopStackingContext => {
+ self.pop_level();
+ self.pop_level();
+ }
+ DisplayItem::Iframe(info) => {
+ self.write_line("- type: iframe");
+ self.push_level();
+ self.write_spatial_id(info.space_and_clip.spatial_id);
+ self.write_clip_chain_id(info.space_and_clip.clip_chain_id);
+ self.write_bounds(info.bounds);
+ self.maybe_write_clip_rect(info.bounds, info.clip_rect);
+ self.write_line(&format!("id: [{}, {}]",
+ info.pipeline_id.0,
+ info.pipeline_id.1,
+ ));
+ self.pop_level();
+ }
+ DisplayItem::Rectangle(info) => {
+ self.write_line("- type: rect");
+ self.push_level();
+ self.write_spatial_id(info.common.spatial_id);
+ self.write_clip_chain_id(info.common.clip_chain_id);
+ self.write_bounds(info.bounds);
+ self.maybe_write_clip_rect(info.bounds, info.common.clip_rect);
+ let color = match info.color {
+ PropertyBinding::Binding(..) => {
+ println!("WARN: Property color bindings are unsupported");
+ ColorF::new(1.0, 0.0, 1.0, 0.5)
+ }
+ PropertyBinding::Value(color) => {
+ color
+ }
+ };
+ if color.a > 0.0 {
+ self.write_color(color);
+ }
+ self.pop_level();
+ }
+ DisplayItem::Text(info) => {
+ self.write_line("- type: rect");
+ self.push_level();
+ self.write_spatial_id(info.common.spatial_id);
+ self.write_clip_chain_id(info.common.clip_chain_id);
+ self.write_bounds(info.bounds);
+ self.maybe_write_clip_rect(info.bounds, info.common.clip_rect);
+ self.write_color(ColorF::new(1.0, 0.0, 0.0, 0.5));
+ self.pop_level();
+ }
+ DisplayItem::Border(info) => {
+ let sp = self.create_savepoint();
+
+ self.write_line("- type: border");
+ self.push_level();
+ self.write_spatial_id(info.common.spatial_id);
+ self.write_clip_chain_id(info.common.clip_chain_id);
+ self.maybe_write_clip_rect(info.bounds, info.common.clip_rect);
+ self.write_bounds(info.bounds);
+
+ match info.details {
+ BorderDetails::Normal(border) => {
+ self.write_line("border-type: normal");
+
+ let colors = [
+ border.top.color,
+ border.right.color,
+ border.bottom.color,
+ border.left.color,
+ ];
+
+ if colors.iter().all(|c| c.a == 0.0) {
+ self.restore_savepoint(sp);
+ continue;
+ }
+
+ if colors.iter().all(|c| *c == border.top.color) {
+ self.write_color(border.top.color);
+ } else {
+ self.write_line(&format!(
+ "color: [ [{}], [{}], [{}], [{}] ]",
+ color_to_string_array(colors[0]),
+ color_to_string_array(colors[1]),
+ color_to_string_array(colors[2]),
+ color_to_string_array(colors[3]),
+ )
+ );
+ }
+
+ let styles = [
+ border.top.style,
+ border.right.style,
+ border.bottom.style,
+ border.left.style,
+ ];
+
+ if styles.iter().all(|s| *s == border.top.style) {
+ self.write_line(&format!(
+ "style: {}", style_to_string(styles[0]),
+ )
+ );
+ } else {
+ self.write_line(&format!(
+ "style: [ {}, {}, {}, {} ]",
+ style_to_string(styles[0]),
+ style_to_string(styles[1]),
+ style_to_string(styles[2]),
+ style_to_string(styles[3]),
+ )
+ );
+ }
+
+ self.write_line("width: [1, 1, 1, 1]");
+
+ if !border.radius.is_zero() {
+ self.write_line("radius: {");
+ self.push_level();
+ self.write_line(
+ &format!("top-left: [{}, {}],",
+ border.radius.top_left.width,
+ border.radius.top_left.height,
+ )
+ );
+ self.write_line(
+ &format!("top-right: [{}, {}],",
+ border.radius.top_right.width,
+ border.radius.top_right.height,
+ )
+ );
+ self.write_line(
+ &format!("bottom-left: [{}, {}],",
+ border.radius.bottom_left.width,
+ border.radius.bottom_left.height,
+ )
+ );
+ self.write_line(
+ &format!("bottom-right: [{}, {}],",
+ border.radius.bottom_right.width,
+ border.radius.bottom_right.height,
+ )
+ );
+ self.pop_level();
+ self.write_line("}");
+ }
+ }
+ BorderDetails::NinePatch(..) => {
+ todo!();
+ }
+ }
+
+ self.pop_level();
+ }
+ DisplayItem::Image(info) => {
+ self.write_line("- type: image");
+ self.push_level();
+ self.write_spatial_id(info.common.spatial_id);
+ self.write_clip_chain_id(info.common.clip_chain_id);
+ self.write_bounds(info.bounds);
+ self.maybe_write_clip_rect(info.bounds, info.common.clip_rect);
+ self.write_line(
+ &format!("src: checkerboard(2,8,8,{},{})",
+ ((info.bounds.width() - 2.0) / 8.0).ceil() as i32,
+ ((info.bounds.height() - 2.0) / 8.0).ceil() as i32,
+ ),
+ );
+ self.pop_level();
+ }
+ DisplayItem::RectClip(info) => {
+ let clip_id = self.add_clip_id(info.id);
+ self.write_line("- type: clip");
+ self.push_level();
+ self.write_line(&format!("id: {}", clip_id));
+ self.write_spatial_id(info.spatial_id);
+ self.write_rect("bounds", info.clip_rect);
+ self.pop_level();
+ }
+ DisplayItem::ImageMaskClip(info) => {
+ let clip_id = self.add_clip_id(info.id);
+ self.write_line("- type: clip");
+ self.push_level();
+ self.write_line(&format!("id: {}", clip_id));
+ self.write_spatial_id(info.spatial_id);
+ self.write_rect("bounds", info.image_mask.rect);
+ self.pop_level();
+ }
+ DisplayItem::RoundedRectClip(info) => {
+ let clip_id = self.add_clip_id(info.id);
+ self.write_line("- type: clip");
+ self.push_level();
+ self.write_line(&format!("id: {}", clip_id));
+ self.write_spatial_id(info.spatial_id);
+ self.write_line("complex:");
+ self.push_level();
+ self.write_rect("- rect", info.clip.rect);
+ self.push_level();
+ self.write_line("radius: {");
+ self.push_level();
+ self.write_line(
+ &format!("top-left: [{}, {}],",
+ info.clip.radii.top_left.width,
+ info.clip.radii.top_left.height,
+ ));
+ self.write_line(
+ &format!("top-right: [{}, {}],",
+ info.clip.radii.top_right.width,
+ info.clip.radii.top_right.height,
+ ));
+ self.write_line(
+ &format!("bottom-right: [{}, {}],",
+ info.clip.radii.bottom_right.width,
+ info.clip.radii.bottom_right.height,
+ ));
+ self.write_line(
+ &format!("bottom-left: [{}, {}],",
+ info.clip.radii.bottom_left.width,
+ info.clip.radii.bottom_left.height,
+ ));
+ self.pop_level();
+ self.write_line("}");
+ self.pop_level();
+ self.pop_level();
+ self.pop_level();
+ }
+ DisplayItem::ClipChain(info) => {
+ let clipchain_id = self.add_clipchain_id(info.id);
+ self.write_line("- type: clip-chain");
+ self.push_level();
+ self.write_line(&format!("id: {}", clipchain_id));
+ let mut clips = String::new();
+ clips.push_str("clips: [");
+ for id in item.clip_chain_items().iter() {
+ clips.push_str(&format!("{}, ", self.clip_id_map[&id]))
+ }
+ clips.push_str("]");
+ self.write_line(&clips);
+ self.pop_level();
+ }
+
+ // TODO(gw): Ignored elements - we should as support for
+ // these as needed.
+ DisplayItem::SetGradientStops => {}
+ DisplayItem::SetFilterOps => {}
+ DisplayItem::SetFilterData => {}
+ DisplayItem::SetFilterPrimitives => {}
+ DisplayItem::SetPoints => {}
+ DisplayItem::PopAllShadows => {}
+ DisplayItem::ReuseItems(..) => {}
+ DisplayItem::RetainedItems(..) => {}
+ DisplayItem::RepeatingImage(..) => {}
+ DisplayItem::YuvImage(..) => {}
+ DisplayItem::BackdropFilter(..) => {}
+ DisplayItem::PushShadow(..) => {}
+ DisplayItem::Gradient(..) => {}
+ DisplayItem::RadialGradient(..) => {}
+ DisplayItem::ConicGradient(..) => {}
+ DisplayItem::ClearRectangle(..) => {}
+ DisplayItem::Line(..) => {}
+ DisplayItem::HitTest(..) => {}
+ DisplayItem::PushReferenceFrame(..) => {}
+ DisplayItem::PopReferenceFrame => {}
+ DisplayItem::DebugMarker(..) => {}
+ DisplayItem::BoxShadow(..) => {}
+ };
+ }
+
+ match bc.kind {
+ ContextKind::Root => {}
+ ContextKind::StackingContext { } => {
+ // self.pop_stacking_context(sc_info);
+ }
+ }
+ }
+
+ assert!(stack.is_empty());
+
+ Ok(())
+ }
+
+ fn write_scene(
+ mut self,
+ scene: &Scene
+ ) -> Result<String, String> {
+ self.write_line(&format!("# process-capture"));
+ self.write_line("---");
+ self.write_line("root:");
+ self.push_level();
+ self.write_line("items:");
+ self.push_level();
+
+ if let Some(root_pipeline_id) = scene.root_pipeline_id {
+ self.write_pipeline(scene, root_pipeline_id)?;
+ }
+
+ self.pop_level();
+ self.pop_level();
+ assert!(self.indent.is_empty());
+
+ if scene.pipelines.len() > 1 {
+ self.write_line("pipelines:");
+ self.push_level();
+ for (id, _) in &scene.pipelines {
+ if Some(*id) == scene.root_pipeline_id {
+ continue;
+ }
+
+ self.write_line(&format!("- id: [{}, {}]", id.0, id.1));
+ self.push_level();
+ self.write_line("items:");
+ self.push_level();
+ self.write_pipeline(scene, *id)?;
+ self.pop_level();
+ self.pop_level();
+ }
+ self.pop_level();
+ }
+
+ Ok(self.out)
+ }
+}
+
+pub fn scene_to_yaml(
+ scene: &Scene,
+) -> Result<String, String> {
+ let writer = YamlWriter::new();
+
+ writer.write_scene(scene)
+}