aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml2
-rw-r--r--Cargo.lock420
-rw-r--r--adapters/fparkan-render-vulkan/shaders/manifest.json2
-rw-r--r--adapters/fparkan-render-vulkan/shaders/triangle.frag.spvbin500 -> 500 bytes
-rw-r--r--adapters/fparkan-render-vulkan/shaders/triangle.vert.spvbin1012 -> 1012 bytes
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi.rs8
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/tests.rs6
-rw-r--r--xtask/Cargo.toml2
-rw-r--r--xtask/src/main.rs311
9 files changed, 733 insertions, 18 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3ca3066..a91fad1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -112,6 +112,8 @@ jobs:
echo "DYLD_FALLBACK_LIBRARY_PATH=$DYLD_FALLBACK_LIBRARY_PATH" >> "$GITHUB_ENV"
- name: Install cargo-deny
run: cargo install cargo-deny --version 0.19.9 --locked
+ - name: Verify shader provenance
+ run: cargo xtask shader-provenance
- name: Run canonical CI gate
run: cargo xtask ci
- name: Run native Vulkan smoke
diff --git a/Cargo.lock b/Cargo.lock
index c22ff74..1451a1d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -107,6 +107,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -188,22 +194,38 @@ dependencies = [
[[package]]
name = "cargo-platform"
-version = "0.3.3"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba"
+checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4"
dependencies = [
"serde",
- "serde_core",
+]
+
+[[package]]
+name = "cargo-util-schemas"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dc1a6f7b5651af85774ae5a34b4e8be397d9cf4bc063b7e6dbd99a841837830"
+dependencies = [
+ "semver",
+ "serde",
+ "serde-untagged",
+ "serde-value",
+ "thiserror 2.0.18",
+ "toml 0.8.23",
+ "unicode-xid",
+ "url",
]
[[package]]
name = "cargo_metadata"
-version = "0.23.1"
+version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9"
+checksum = "5cfca2aaa699835ba88faf58a06342a314a950d2b9686165e038286c30316868"
dependencies = [
"camino",
"cargo-platform",
+ "cargo-util-schemas",
"semver",
"serde",
"serde_json",
@@ -351,6 +373,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
+name = "displaydoc"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "dlib"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -387,6 +420,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
+name = "erased-serde"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
+dependencies = [
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -440,6 +484,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
name = "fparkan-animation"
version = "0.1.0"
@@ -790,6 +843,109 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
+name = "icu_collections"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
+
+[[package]]
+name = "icu_properties"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
+
+[[package]]
+name = "icu_provider"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -925,6 +1081,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
+name = "litemap"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+
+[[package]]
name = "log"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -995,6 +1157,15 @@ dependencies = [
]
[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "num_enum"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1245,6 +1416,15 @@ dependencies = [
]
[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
name = "owned_ttf_parser"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1312,12 +1492,21 @@ dependencies = [
]
[[package]]
+name = "potential_utf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
name = "proc-macro-crate"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
- "toml_edit",
+ "toml_edit 0.25.12+spec-1.1.0",
]
[[package]]
@@ -1479,6 +1668,28 @@ dependencies = [
]
[[package]]
+name = "serde-untagged"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058"
+dependencies = [
+ "erased-serde",
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
+[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1513,6 +1724,15 @@ dependencies = [
[[package]]
name = "serde_spanned"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
@@ -1595,6 +1815,12 @@ dependencies = [
]
[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1612,6 +1838,17 @@ dependencies = [
]
[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1677,6 +1914,28 @@ dependencies = [
]
[[package]]
+name = "tinystr"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
+dependencies = [
+ "serde",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.11",
+ "toml_edit 0.22.27",
+]
+
+[[package]]
name = "toml"
version = "0.9.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1684,7 +1943,7 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [
"indexmap",
"serde_core",
- "serde_spanned",
+ "serde_spanned 1.1.1",
"toml_datetime 0.7.5+spec-1.1.0",
"toml_parser",
"toml_writer",
@@ -1693,6 +1952,15 @@ dependencies = [
[[package]]
name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
@@ -1711,6 +1979,20 @@ dependencies = [
[[package]]
name = "toml_edit"
+version = "0.22.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.11",
+ "toml_write",
+ "winnow 0.7.15",
+]
+
+[[package]]
+name = "toml_edit"
version = "0.25.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
@@ -1731,6 +2013,12 @@ dependencies = [
]
[[package]]
+name = "toml_write"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
+
+[[package]]
name = "toml_writer"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1759,6 +2047,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
[[package]]
+name = "typeid"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+
+[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1771,6 +2065,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2142,6 +2460,9 @@ name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
+dependencies = [
+ "memchr",
+]
[[package]]
name = "winnow"
@@ -2159,6 +2480,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
+name = "writeable"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+
+[[package]]
name = "x11-dl"
version = "2.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2223,7 +2550,30 @@ dependencies = [
"fparkan-corpus",
"serde",
"serde_json",
- "toml",
+ "toml 0.9.12+spec-1.1.0",
+]
+
+[[package]]
+name = "yoke"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
]
[[package]]
@@ -2247,6 +2597,60 @@ dependencies = [
]
[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/adapters/fparkan-render-vulkan/shaders/manifest.json b/adapters/fparkan-render-vulkan/shaders/manifest.json
index ce0fa85..ee61039 100644
--- a/adapters/fparkan-render-vulkan/shaders/manifest.json
+++ b/adapters/fparkan-render-vulkan/shaders/manifest.json
@@ -1 +1 @@
-{"schema":2,"target_env":"vulkan1.1","compiler":{"name":"glslangValidator","version":"11:16.3.0","binary_sha256":"9bcd69d830b350aaa6e2254915ff74e46070e217b67f38daad27c1fc1f22910f"},"validator":{"name":"spirv-val","version":"SPIRV-Tools v2026.2 unknown hash, 2026-04-29T17:02:58+00:00","binary_sha256":"f6d5b96ff19f073f3af0c0bcfa0c18702d288d3ec598efc242d01cd104d8354f"},"modules":[{"name":"triangle.vert","stage":"vertex","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert","source_sha256":"1e57f14d193fc61457c0749081c452ad25669998913107df12f3ccc3c33e0341","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","word_count":253,"sha256":"9023b1cc856c98ecd21755596c4e9d1e62cc63e1787f8c43ada2101544e8d0d1","descriptor_sets":0,"push_constant_bytes":0,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S vert -e main adapters/fparkan-render-vulkan/shaders/triangle.vert -o adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","interface_hash":"23e1d3d9d32e7f7ec0b9ca87f8b86be8f8363c7eb5d745fc5a157cb8433eb138"},{"name":"triangle.frag","stage":"fragment","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag","source_sha256":"f19e74d001d07fb537d4b0f9e621f9b8bc40eeb68816130220853abea6bd4445","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","word_count":125,"sha256":"6efe2c9716ae845c471ecbaac2c83e56a17a37dc017dd63f0a05f0d9161f44ba","descriptor_sets":0,"push_constant_bytes":0,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S frag -e main adapters/fparkan-render-vulkan/shaders/triangle.frag -o adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","interface_hash":"f09342c22d58c8768151ab8579e54e49af586434a4005d16a24e816d881a64f0"}],"manifest_hash":"20fb84fb6edbd6897e2ea3c2ec3a6db3826a84b46c4efb69027c1cfc0119ccf2"}
+{"schema":2,"target_env":"vulkan1.1","compiler":{"name":"glslangValidator","version":"11:16.3.0","binary_sha256":"9bcd69d830b350aaa6e2254915ff74e46070e217b67f38daad27c1fc1f22910f"},"validator":{"name":"spirv-val","version":"SPIRV-Tools v2026.2 unknown hash, 2026-04-29T17:02:58+00:00","binary_sha256":"f6d5b96ff19f073f3af0c0bcfa0c18702d288d3ec598efc242d01cd104d8354f"},"modules":[{"name":"triangle.vert","stage":"vertex","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert","source_sha256":"1e57f14d193fc61457c0749081c452ad25669998913107df12f3ccc3c33e0341","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","word_count":253,"sha256":"4d3ceca7b42ebc971d831b0a0d816457397bd9aeda47fb8d44c4b1aeaa5e7ba0","descriptor_sets":0,"push_constant_bytes":0,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S vert -e main adapters/fparkan-render-vulkan/shaders/triangle.vert -o adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","interface_hash":"23e1d3d9d32e7f7ec0b9ca87f8b86be8f8363c7eb5d745fc5a157cb8433eb138"},{"name":"triangle.frag","stage":"fragment","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag","source_sha256":"f19e74d001d07fb537d4b0f9e621f9b8bc40eeb68816130220853abea6bd4445","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","word_count":125,"sha256":"5a7441be03cd3c25d557268b2e58d5aa50504c87bffcb4c3fd7cbcf007db0b96","descriptor_sets":0,"push_constant_bytes":0,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S frag -e main adapters/fparkan-render-vulkan/shaders/triangle.frag -o adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","interface_hash":"f09342c22d58c8768151ab8579e54e49af586434a4005d16a24e816d881a64f0"}],"manifest_hash":"11e3feb65200ebd2ac87b7e776e9c6433a5da9d71a651bfadea89a51be17ff05"} \ No newline at end of file
diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv b/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv
index c5d57ee..95f7c37 100644
--- a/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv
+++ b/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv
Binary files differ
diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv
index 04321ea..efa9481 100644
--- a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv
+++ b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv
Binary files differ
diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs
index 386cb68..ad65e81 100644
--- a/adapters/fparkan-render-vulkan/src/ffi.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi.rs
@@ -86,8 +86,8 @@ const VALIDATION_LAYER_NAME: &str = "VK_LAYER_KHRONOS_validation";
pub(crate) const SPIRV_MAGIC: u32 = 0x0723_0203;
pub(crate) const SPIRV_VERSION_1_0: u32 = 0x0001_0000;
pub(crate) const TRIANGLE_VERTEX_SHADER_WORDS: &[u32] = &[
- SPIRV_MAGIC,
- SPIRV_VERSION_1_0,
+ 0x0723_0203,
+ 0x0001_0300,
0x0008_000b,
0x0000_0021,
0x0000_0000,
@@ -341,8 +341,8 @@ pub(crate) const TRIANGLE_VERTEX_SHADER_WORDS: &[u32] = &[
0x0001_0038,
];
pub(crate) const TRIANGLE_FRAGMENT_SHADER_WORDS: &[u32] = &[
- SPIRV_MAGIC,
- SPIRV_VERSION_1_0,
+ 0x0723_0203,
+ 0x0001_0300,
0x0008_000b,
0x0000_0013,
0x0000_0000,
diff --git a/adapters/fparkan-render-vulkan/src/ffi/tests.rs b/adapters/fparkan-render-vulkan/src/ffi/tests.rs
index b04ce8c..9c927a8 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/tests.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/tests.rs
@@ -612,7 +612,7 @@ fn triangle_shader_manifest_hashes_are_stable() {
assert_eq!(report.modules[0].word_count, 253);
assert_eq!(
report.modules[0].sha256,
- "9023b1cc856c98ecd21755596c4e9d1e62cc63e1787f8c43ada2101544e8d0d1"
+ "4d3ceca7b42ebc971d831b0a0d816457397bd9aeda47fb8d44c4b1aeaa5e7ba0"
);
assert_eq!(report.modules[0].descriptor_sets, 0);
assert_eq!(report.modules[0].push_constant_bytes, 0);
@@ -627,11 +627,11 @@ fn triangle_shader_manifest_hashes_are_stable() {
assert!(!report.modules[0].interface_hash.is_empty());
assert_eq!(
report.modules[1].sha256,
- "6efe2c9716ae845c471ecbaac2c83e56a17a37dc017dd63f0a05f0d9161f44ba"
+ "5a7441be03cd3c25d557268b2e58d5aa50504c87bffcb4c3fd7cbcf007db0b96"
);
assert_eq!(
report.manifest_hash,
- "20fb84fb6edbd6897e2ea3c2ec3a6db3826a84b46c4efb69027c1cfc0119ccf2"
+ "11e3feb65200ebd2ac87b7e776e9c6433a5da9d71a651bfadea89a51be17ff05"
);
}
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index f3f3621..86dfe13 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -7,7 +7,7 @@ repository.workspace = true
[dependencies]
fparkan-corpus = { path = "../crates/fparkan-corpus" }
-cargo_metadata = "0.23"
+cargo_metadata = "0.21.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.9"
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index e1e5970..735ec94 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -29,6 +29,7 @@ use std::fmt;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
+use std::time::{SystemTime, UNIX_EPOCH};
const CORPORA_MANIFEST_ENV: &str = "FPARKAN_CORPORA_MANIFEST";
const PART1_ROOT_ENV: &str = "FPARKAN_CORPUS_PART1_ROOT";
@@ -36,6 +37,7 @@ const PART2_ROOT_ENV: &str = "FPARKAN_CORPUS_PART2_ROOT";
const CI_ACCEPTANCE_ROADMAP: &str = "fixtures/acceptance/stage_0_roadmap.md";
const CI_ACCEPTANCE_COVERAGE: &str = "fixtures/acceptance/coverage.tsv";
const CI_ACCEPTANCE_REPORT: &str = "target/fparkan/acceptance/stage-0-audit.json";
+const SHADER_MANIFEST_REPORT: &str = "adapters/fparkan-render-vulkan/shaders/manifest.json";
const STAGE_PACKAGE_MANIFEST: &str = "fixtures/acceptance/stage_packages.toml";
const SUPPLY_CHAIN_POLICY_CONFIG: &str = "deny.toml";
const REQUIRED_NATIVE_SMOKE_PLATFORMS: &[&str] = &["macos"];
@@ -73,6 +75,7 @@ fn run(args: &[String]) -> Result<(), String> {
[cmd] if cmd == "ci" => {
run_cargo_fmt_check()?;
run_policy(Path::new("."))?;
+ run_shader_provenance_verification()?;
cargo(&["test", "--workspace", "--all-targets", "--all-features", "--locked"])?;
cargo(&[
"clippy",
@@ -95,6 +98,7 @@ fn run(args: &[String]) -> Result<(), String> {
Ok(())
}
[cmd] if cmd == "policy" => run_policy(Path::new(".")),
+ [cmd] if cmd == "shader-provenance" => run_shader_provenance_verification(),
[cmd, subcmd, rest @ ..] if cmd == "acceptance" && subcmd == "report" => {
let options = parse_acceptance_options(rest)?;
run_acceptance_report(&options)
@@ -129,7 +133,7 @@ fn run(args: &[String]) -> Result<(), String> {
Ok(())
}
_ => Err(
- "usage: cargo xtask ci | policy | acceptance report --suite synthetic|licensed [--stage 0..5|all] [--manifest corpora.toml] [--out <path>] | acceptance audit [--roadmap <path>] [--coverage <path>] [--out <path>] [--strict] | native-smoke audit --dir <path> | package --target <triple> --app viewer|game|headless|cli | test synthetic|licensed [--stage 0..5|all] [--manifest corpora.toml] | corpus baseline --root <path>"
+ "usage: cargo xtask ci | policy | shader-provenance | acceptance report --suite synthetic|licensed [--stage 0..5|all] [--manifest corpora.toml] [--out <path>] | acceptance audit [--roadmap <path>] [--coverage <path>] [--out <path>] [--strict] | native-smoke audit --dir <path> | package --target <triple> --app viewer|game|headless|cli | test synthetic|licensed [--stage 0..5|all] [--manifest corpora.toml] | corpus baseline --root <path>"
.to_string(),
),
}
@@ -191,6 +195,285 @@ fn run_cargo_fmt_check() -> Result<(), String> {
}
}
+fn run_shader_provenance_verification() -> Result<(), String> {
+ let manifest_path = workspace_relative_path(SHADER_MANIFEST_REPORT);
+ let manifest = load_shader_manifest(&manifest_path)?;
+ let compiler_path = resolve_required_tool("FPARKAN_GLSLANG_VALIDATOR", &manifest.compiler)?;
+ let validator_path = resolve_required_tool("FPARKAN_SPIRV_VAL", &manifest.validator)?;
+
+ verify_tool_metadata(&compiler_path, &manifest.compiler)?;
+ verify_tool_metadata(&validator_path, &manifest.validator)?;
+
+ let out_dir = shader_provenance_output_dir();
+ if out_dir.exists() {
+ fs::remove_dir_all(&out_dir).map_err(|err| format!("{}: {err}", out_dir.display()))?;
+ }
+ fs::create_dir_all(&out_dir).map_err(|err| format!("{}: {err}", out_dir.display()))?;
+
+ for module in &manifest.modules {
+ verify_shader_module(&manifest, module, &compiler_path, &validator_path, &out_dir)?;
+ }
+ Ok(())
+}
+
+fn load_shader_manifest(path: &Path) -> Result<ShaderManifestJson, String> {
+ let text = fs::read_to_string(path).map_err(|err| format!("{}: {err}", path.display()))?;
+ let manifest = serde_json::from_str::<ShaderManifestJson>(&text)
+ .map_err(|err| format!("{}: invalid shader manifest JSON: {err}", path.display()))?;
+ if manifest.modules.is_empty() {
+ return Err(format!(
+ "{}: shader manifest must describe at least one module",
+ path.display()
+ ));
+ }
+ Ok(manifest)
+}
+
+fn resolve_required_tool(
+ env_var: &str,
+ manifest: &ShaderToolManifestJson,
+) -> Result<PathBuf, String> {
+ let requested = std::env::var(env_var).unwrap_or_else(|_| manifest.name.clone());
+ resolve_tool_path(&requested).ok_or_else(|| {
+ format!(
+ "required shader tool {} is unavailable (set {env_var} to override path)",
+ manifest.name
+ )
+ })
+}
+
+fn resolve_tool_path(tool: &str) -> Option<PathBuf> {
+ let candidate = Path::new(tool);
+ if candidate.components().count() > 1 {
+ return candidate.is_file().then(|| candidate.to_path_buf());
+ }
+ let output = Command::new("which").arg(tool).output().ok()?;
+ if !output.status.success() {
+ return None;
+ }
+ let resolved = String::from_utf8(output.stdout).ok()?;
+ let resolved = resolved.trim();
+ (!resolved.is_empty()).then(|| PathBuf::from(resolved))
+}
+
+fn verify_tool_metadata(path: &Path, manifest: &ShaderToolManifestJson) -> Result<(), String> {
+ let actual_name = path
+ .file_name()
+ .and_then(|value| value.to_str())
+ .ok_or_else(|| format!("{}: invalid tool filename", path.display()))?;
+ if actual_name != manifest.name {
+ return Err(format!(
+ "{}: tool name mismatch, expected {}, found {}",
+ path.display(),
+ manifest.name,
+ actual_name
+ ));
+ }
+ let actual_version = tool_version(path)?;
+ if actual_version != manifest.version {
+ return Err(format!(
+ "{}: tool version mismatch, expected {:?}, found {:?}",
+ path.display(),
+ manifest.version,
+ actual_version
+ ));
+ }
+ let actual_sha256 = sha256_file(path)?;
+ if actual_sha256 != manifest.binary_sha256 {
+ return Err(format!(
+ "{}: tool SHA-256 mismatch, expected {}, found {}",
+ path.display(),
+ manifest.binary_sha256,
+ actual_sha256
+ ));
+ }
+ Ok(())
+}
+
+fn tool_version(path: &Path) -> Result<String, String> {
+ let output = Command::new(path)
+ .arg("--version")
+ .output()
+ .map_err(|err| format!("{} --version: {err}", path.display()))?;
+ if !output.status.success() {
+ return Err(format!(
+ "{} --version exited with {}",
+ path.display(),
+ output.status
+ ));
+ }
+ let stdout = String::from_utf8(output.stdout)
+ .map_err(|err| format!("{} --version: invalid UTF-8: {err}", path.display()))?;
+ stdout
+ .lines()
+ .find(|line| !line.trim().is_empty())
+ .map(str::trim)
+ .map(|line| {
+ line.strip_prefix("Glslang Version: ")
+ .unwrap_or(line)
+ .to_string()
+ })
+ .ok_or_else(|| format!("{} --version returned no version line", path.display()))
+}
+
+fn verify_shader_module(
+ manifest: &ShaderManifestJson,
+ module: &ShaderModuleManifestJson,
+ compiler_path: &Path,
+ validator_path: &Path,
+ out_dir: &Path,
+) -> Result<(), String> {
+ let source_path = workspace_relative_path(&module.source_path);
+ let checked_in_spirv_path = workspace_relative_path(&module.spirv_path);
+ let generated_spirv_path = out_dir.join(format!("{}.spv", module.name));
+
+ let source_sha256 = sha256_file(&source_path)?;
+ if source_sha256 != module.source_sha256 {
+ return Err(format!(
+ "{}: source SHA-256 mismatch, expected {}, found {}",
+ source_path.display(),
+ module.source_sha256,
+ source_sha256
+ ));
+ }
+
+ let checked_in_spirv_sha256 = sha256_file(&checked_in_spirv_path)?;
+ if checked_in_spirv_sha256 != module.sha256 {
+ return Err(format!(
+ "{}: checked-in SPIR-V SHA-256 mismatch, expected {}, found {}",
+ checked_in_spirv_path.display(),
+ module.sha256,
+ checked_in_spirv_sha256
+ ));
+ }
+
+ compile_shader_module(
+ compiler_path,
+ &manifest.target_env,
+ module,
+ &source_path,
+ &generated_spirv_path,
+ )?;
+ validate_shader_module(validator_path, &manifest.target_env, &generated_spirv_path)?;
+
+ let generated_spirv_sha256 = sha256_file(&generated_spirv_path)?;
+ if generated_spirv_sha256 != module.sha256 {
+ return Err(format!(
+ "{}: generated SPIR-V SHA-256 mismatch, expected {}, found {}",
+ generated_spirv_path.display(),
+ module.sha256,
+ generated_spirv_sha256
+ ));
+ }
+ Ok(())
+}
+
+fn compile_shader_module(
+ compiler_path: &Path,
+ target_env: &str,
+ module: &ShaderModuleManifestJson,
+ source_path: &Path,
+ output_path: &Path,
+) -> Result<(), String> {
+ let stage = glslang_stage(&module.stage).ok_or_else(|| {
+ format!(
+ "{}: unsupported shader stage {:?}",
+ source_path.display(),
+ module.stage
+ )
+ })?;
+ let output = Command::new(compiler_path)
+ .args(["-V", "--target-env", target_env, "-S", stage, "-e"])
+ .arg(&module.entry_point)
+ .arg(source_path)
+ .arg("-o")
+ .arg(output_path)
+ .output()
+ .map_err(|err| {
+ format!(
+ "{}: shader compile failed to start: {err}",
+ source_path.display()
+ )
+ })?;
+ if !output.status.success() {
+ return Err(format!(
+ "{}: shader compile failed:\n{}{}",
+ source_path.display(),
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr)
+ ));
+ }
+ Ok(())
+}
+
+fn validate_shader_module(
+ validator_path: &Path,
+ target_env: &str,
+ module_path: &Path,
+) -> Result<(), String> {
+ let output = Command::new(validator_path)
+ .args(["--target-env", target_env])
+ .arg(module_path)
+ .output()
+ .map_err(|err| {
+ format!(
+ "{}: shader validation failed to start: {err}",
+ module_path.display()
+ )
+ })?;
+ if !output.status.success() {
+ return Err(format!(
+ "{}: shader validation failed:\n{}{}",
+ module_path.display(),
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr)
+ ));
+ }
+ Ok(())
+}
+
+fn glslang_stage(stage: &str) -> Option<&'static str> {
+ match stage {
+ "vertex" => Some("vert"),
+ "fragment" => Some("frag"),
+ _ => None,
+ }
+}
+
+fn sha256_file(path: &Path) -> Result<String, String> {
+ for command in [&["shasum", "-a", "256"][..], &["sha256sum"][..]] {
+ let mut process = Command::new(command[0]);
+ process.args(&command[1..]).arg(path);
+ let Ok(output) = process.output() else {
+ continue;
+ };
+ if !output.status.success() {
+ continue;
+ }
+ let stdout = String::from_utf8(output.stdout)
+ .map_err(|err| format!("{}: invalid checksum output: {err}", path.display()))?;
+ if let Some(sum) = stdout.split_whitespace().next() {
+ return Ok(sum.to_string());
+ }
+ }
+ Err(format!(
+ "{}: could not compute SHA-256 (tried shasum and sha256sum)",
+ path.display()
+ ))
+}
+
+fn shader_provenance_output_dir() -> PathBuf {
+ let nonce = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap_or_default()
+ .as_nanos();
+ workspace_root_path()
+ .join("target")
+ .join("fparkan")
+ .join("shader-provenance")
+ .join(format!("{}-{}", std::process::id(), nonce))
+}
+
fn run_cargo_deny() -> Result<(), String> {
validate_supply_chain_policy_config(&workspace_relative_path(SUPPLY_CHAIN_POLICY_CONFIG))?;
let cargo_deny = std::env::var_os("CARGO_DENY").unwrap_or_else(|| "cargo-deny".into());
@@ -1374,6 +1657,32 @@ struct NativeSmokeAuditOptions {
dir: PathBuf,
}
+#[derive(Debug, Deserialize)]
+struct ShaderManifestJson {
+ target_env: String,
+ compiler: ShaderToolManifestJson,
+ validator: ShaderToolManifestJson,
+ modules: Vec<ShaderModuleManifestJson>,
+}
+
+#[derive(Debug, Deserialize)]
+struct ShaderToolManifestJson {
+ name: String,
+ version: String,
+ binary_sha256: String,
+}
+
+#[derive(Debug, Deserialize)]
+struct ShaderModuleManifestJson {
+ name: String,
+ stage: String,
+ entry_point: String,
+ source_path: String,
+ source_sha256: String,
+ spirv_path: String,
+ sha256: String,
+}
+
fn parse_test_options(args: &[String], default_root: PathBuf) -> Result<TestOptions, String> {
let mut options = TestOptions {
stage: Stage::All,