Skip to main content

unity_formats/
pipeline.rs

1//! The Universal Render Pipeline's objects, decoded from the bytes of their MonoBehaviours (a build has no type tree for
2//! a script's fields): a pipeline asset's renderers and its lighting, shadow and texture settings
3//! ([`read_urp_settings`]), a renderer's features, layer masks and modes ([`read_urp_renderer`]), a renderer feature's
4//! `m_Active` ([`read_urp_feature_active`]) and the settings of the Decal and Full Screen Pass features
5//! ([`read_urp_decal_feature`], [`read_urp_full_screen_feature`]), a camera's URP data ([`read_urp_camera`]), and the
6//! pipeline's global settings with the graphics settings they hold: its runtime shaders and its render graph settings
7//! ([`read_urp_global_settings`]).
8//!
9//! The field order is each class's declaration order in the Universal RP package. A field is a 4-byte value, or a
10//! bool padded to 4 bytes; an object reference is 12 bytes (a [`PPtr`]: file id and a 64-bit path id) and an array is
11//! its count and elements. A value outside what its field can hold is refused as a sign of an unverified layout.
12//!
13//! - The asset's layout ([`UrpAssetLayout`]) is Universal RP 17.3's field order, read to the asset's last byte, and is
14//!   identified by the Unity version of the player before any field is read: it is verified on the players of Unity
15//!   6000.3.12f1 (Sledding Game) and 6000.3.15f1 (PEAK), each of whose assets reads to exactly its bytes, with the
16//!   renderers and settings an independent reading makes (tests/urp.rs) and the settings the keywords of the shader
17//!   variants the player stores agree with (as
18//!   [this test of primase](https://github.com/Kalmarv/primase/tree/main/worlds/unity/tests/resolve_shaders.rs)
19//!   compares). A player of another Unity version is refused by name: Big Walk's (6000.3.17f1) assets differ after
20//!   `m_ReflectionProbeBlending`. It holds for `k_AssetVersion` 13 only; another version is refused.
21//! - The renderer, feature and camera layouts are those of Universal RP 17.3 (the package of Unity 6000.3), checked
22//!   against PEAK's, Sledding Game's and Big Walk's players: every renderer, every Decal and Full Screen Pass feature
23//!   and every camera of their scenes reads to exactly its bytes, with the fields an independent reading makes
24//!   (tests/urp.rs). A length the layout does not account for is refused, and so is a renderer `m_AssetVersion`, a
25//!   Full Screen Pass `m_Version` or a camera `m_Version` other than the one read.
26//! - The global settings' layout (`m_AssetVersion` 10) and its graphics settings classes' are Universal RP 17.3's, Core
27//!   RP 17.3's and GPU Driven RP 17.3's, checked against PEAK's and Sledding Game's players: each object reads to exactly
28//!   its bytes, every entry of its managed-reference registry included (tests/urp.rs). A class whose layout is not
29//!   verified is refused by name (Big Walk's settings hold one of the game's own), as is another asset or class version.
30
31use crate::player::PPtr;
32use crate::reader::Reader;
33use crate::version::UnityVersion;
34use serde::Serialize;
35use std::collections::BTreeMap;
36
37use crate::{Result, invalid};
38
39/// The settings of a Universal Render Pipeline asset as stored (`UniversalRenderPipelineAsset`, asset version 13; this
40/// module's docs state the verified layout): its renderers and the one cameras use by default, whether it makes the
41/// depth and opaque textures, its MSAA sample count, and the lighting and shadow settings that decide which of a
42/// pass's keywords the pipeline sets: the light modes as `LightRenderingMode` numbers them (0 disabled, 1 per pixel,
43/// 2 per vertex) and the shadow cascade count (1 to 4).
44#[derive(Clone, Debug, PartialEq, Serialize)]
45pub struct UnityUrpSettings {
46    /// The renderers (`m_RendererDataList`), each a `ScriptableRendererData` asset, in the order a camera's renderer
47    /// index counts them.
48    pub renderers: Vec<PPtr>,
49    /// The index in [`UnityUrpSettings::renderers`] of the renderer a camera with renderer index -1 draws with
50    /// (`m_DefaultRendererIndex`).
51    pub default_renderer: i64,
52    /// Whether the pipeline makes the camera depth texture (`m_RequireDepthTexture`), for a camera whose depth texture
53    /// option is the pipeline's setting.
54    pub require_depth_texture: bool,
55    /// Whether the pipeline makes the camera opaque texture (`m_RequireOpaqueTexture`), likewise.
56    pub require_opaque_texture: bool,
57    /// The MSAA sample count (`m_MSAA`: 1, 2, 4 or 8).
58    pub msaa: i64,
59    /// The main light's rendering mode (`m_MainLightRenderingMode`).
60    pub main_light_mode: i64,
61    /// Whether the main light casts shadows.
62    pub main_light_shadows: bool,
63    /// The additional lights' rendering mode (`m_AdditionalLightsRenderingMode`).
64    pub additional_lights_mode: i64,
65    /// Whether additional lights cast shadows.
66    pub additional_light_shadows: bool,
67    /// Whether reflection probes blend.
68    pub reflection_probe_blending: bool,
69    /// Whether reflection probes use box projection.
70    pub reflection_probe_box_projection: bool,
71    /// Whether reflection probes are packed into an atlas.
72    pub reflection_probe_atlas: bool,
73    /// `m_ShadowDistance`.
74    pub shadow_distance: f32,
75    /// `m_ShadowCascadeCount`.
76    pub shadow_cascades: u8,
77    /// Whether soft shadows are supported.
78    pub soft_shadows: bool,
79    /// Whether mixed lighting is supported.
80    pub mixed_lighting: bool,
81    /// Whether light cookies are supported.
82    pub light_cookies: bool,
83    /// Whether light layers are supported.
84    pub light_layers: bool,
85    /// Whether LOD cross-fading is enabled.
86    pub lod_cross_fade: bool,
87    /// `ShEvalMode`: 0 auto, 1 per vertex, 2 mixed, 3 per pixel.
88    pub sh_eval_mode: i64,
89    /// `LightProbeSystem`: 0 legacy light probes, 1 probe volumes.
90    pub light_probe_system: i64,
91}
92
93/// The asset version the layout below is verified for.
94const ASSET_VERSION: i32 = 13;
95
96/// A serialized layout of `UniversalRenderPipelineAsset` whose every field is verified (this module's docs say on
97/// which players).
98#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99#[non_exhaustive]
100pub enum UrpAssetLayout {
101    /// Universal RP 17.3's, as Unity 6000.3.12f1 and 6000.3.15f1 players store it.
102    Urp17_3,
103}
104
105/// The Unity versions whose players' assets are verified to have [`UrpAssetLayout::Urp17_3`].
106const URP_17_3_PLAYERS: [&str; 2] = ["6000.3.12f1", "6000.3.15f1"];
107
108impl UrpAssetLayout {
109    /// The layout of the assets a player of Unity `unity` stores (its serialized files' version); a version on whose
110    /// players no layout is verified is refused, naming it.
111    pub fn of(unity: UnityVersion) -> Result<Self> {
112        for v in URP_17_3_PLAYERS {
113            if UnityVersion::parse(v)? == unity {
114                return Ok(UrpAssetLayout::Urp17_3);
115            }
116        }
117        Err(invalid(format!(
118            "a Universal Render Pipeline asset of a Unity {unity} player: its layout is verified only on the players \
119             of Unity {} (the package's fields are not recorded in the asset, and other players store others)",
120            URP_17_3_PLAYERS.join(" and ")
121        )))
122    }
123}
124
125/// The settings of the asset whose script fields are `bytes` (the bytes after the MonoBehaviour's base fields), stored
126/// in `layout` ([`UrpAssetLayout::of`] the player's Unity version; [`crate::player::Player::urp_settings`] reads an
127/// asset of a player so). Every field is read, to the last byte.
128pub fn read_urp_settings(bytes: &[u8], layout: UrpAssetLayout) -> Result<UnityUrpSettings> {
129    match layout {
130        UrpAssetLayout::Urp17_3 => {}
131    }
132    let mut r = Reader::new(bytes, false);
133    let version = r.i32()?;
134    if version != ASSET_VERSION {
135        return Err(invalid(format!(
136            "Universal Render Pipeline asset version {version}: only version {ASSET_VERSION} has a verified layout"
137        )));
138    }
139    r.i32()?; // k_AssetPreviousVersion
140    r.i32()?; // m_RendererType
141    r.take(12)?; // m_RendererData (a reference)
142    let renderers = pptrs(&mut r, "m_RendererDataList")?;
143    let default_renderer = r.i32()?;
144    let require_depth_texture = flag(&mut r)?;
145    let require_opaque_texture = flag(&mut r)?;
146    r.i32()?; // m_OpaqueDownsampling
147    r.take(8)?; // m_SupportsTerrainHoles, m_SupportsHDR
148    r.i32()?; // m_HDRColorBufferPrecision
149    let msaa = r.i32()?;
150    r.f32()?; // m_RenderScale
151    r.i32()?; // m_UpscalingFilter
152    r.take(4)?; // m_FsrOverrideSharpness
153    r.f32()?; // m_FsrSharpness
154    let lod_cross_fade = flag(&mut r)?;
155    r.i32()?; // m_LODCrossFadeDitheringType
156    let sh_eval_mode = r.i32()?;
157    let light_probe_system = r.i32()?;
158    r.i32()?; // m_ProbeVolumeMemoryBudget
159    r.i32()?; // m_ProbeVolumeBlendingMemoryBudget
160    r.take(16)?; // the four probe volume streaming and scenario flags
161    r.i32()?; // m_ProbeVolumeSHBands
162    let main_light_mode = r.i32()?;
163    let main_light_shadows = flag(&mut r)?;
164    r.i32()?; // m_MainLightShadowmapResolution
165    let additional_lights_mode = r.i32()?;
166    r.i32()?; // m_AdditionalLightsPerObjectLimit
167    let additional_light_shadows = flag(&mut r)?;
168    r.take(16)?; // the additional lights' shadowmap resolution and its three tiers
169    let reflection_probe_blending = flag(&mut r)?;
170    let reflection_probe_box_projection = flag(&mut r)?;
171    let reflection_probe_atlas = flag(&mut r)?;
172    let shadow_distance = r.f32()?;
173    let shadow_cascades = r.i32()?;
174    r.take(4 + 8 + 12 + 4 + 4 + 4)?; // the cascade splits and border, the depth and normal bias
175    let soft_shadows = flag(&mut r)?;
176    r.take(4)?; // m_ConservativeEnclosingSphere
177    r.i32()?; // m_NumIterationsEnclosingSphere
178    r.i32()?; // m_SoftShadowQuality
179    r.i32()?; // m_AdditionalLightsCookieResolution
180    r.i32()?; // m_AdditionalLightsCookieFormat
181    r.take(8)?; // m_UseSRPBatcher, m_SupportsDynamicBatching
182    let mixed_lighting = flag(&mut r)?;
183    let light_cookies = flag(&mut r)?;
184    let light_layers = flag(&mut r)?;
185    r.i32()?; // m_DebugLevel
186    r.i32()?; // m_StoreActionsOptimization
187    flag(&mut r)?; // m_UseAdaptivePerformance
188    r.i32()?; // m_ColorGradingMode
189    r.i32()?; // m_ColorGradingLutSize
190    for _ in 0..4 {
191        // m_AllowPostProcessAlphaOutput, m_UseFastSRGBLinearConversion and the two lens flare flags
192        flag(&mut r)?;
193    }
194    r.i32()?; // m_GPUResidentDrawerMode
195    r.f32()?; // m_SmallMeshScreenPercentage
196    flag(&mut r)?; // m_GPUResidentDrawerEnableOcclusionCullingInCameras
197    r.i32()?; // m_ShadowType
198    flag(&mut r)?; // m_LocalShadowsSupported
199    r.take(12)?; // m_LocalShadowsAtlasResolution, m_MaxPixelLights, m_ShadowAtlasResolution
200    r.i32()?; // m_VolumeFrameworkUpdateMode
201    pptr(&mut r)?; // m_VolumeProfile
202    // apvScenesData: two SerializedDictionary, each its keys' list and its values' list (scene GUIDs to bounds, and
203    // to whether the scene has probe volumes)
204    strings(&mut r, "apvScenesData.obsoleteSceneBounds.m_Keys")?;
205    let n = list_len(&mut r, "apvScenesData.obsoleteSceneBounds.m_Values", 24)?;
206    for _ in 0..n {
207        r.take(24)?; // a Bounds: its centre and extents
208    }
209    strings(&mut r, "apvScenesData.obsoleteHasProbeVolumes.m_Keys")?;
210    let n = list_len(&mut r, "apvScenesData.obsoleteHasProbeVolumes.m_Values", 1)?;
211    for _ in 0..n {
212        r.bool()?;
213    }
214    r.align(4)?;
215    r.i32()?; // m_ShaderVariantLogLevel
216    r.i32()?; // m_ShadowCascades
217    r.take(24)?; // m_Textures: its two textures
218    used_up(&r, "UniversalRenderPipelineAsset")?;
219    // a layout that does not hold reads values outside what the fields can be
220    let in_range = |what: &str, ok: bool| -> Result<()> {
221        ok.then_some(()).ok_or_else(|| {
222            invalid(format!(
223                "Universal Render Pipeline asset: {what} out of range (an unverified layout)"
224            ))
225        })
226    };
227    in_range("m_ShadowCascadeCount", (1..=4).contains(&shadow_cascades))?;
228    in_range(
229        "m_ShadowDistance",
230        shadow_distance.is_finite() && shadow_distance >= 0.0,
231    )?;
232    in_range("m_MainLightRenderingMode", (0..=2).contains(&main_light_mode))?;
233    in_range(
234        "m_AdditionalLightsRenderingMode",
235        (0..=2).contains(&additional_lights_mode),
236    )?;
237    in_range("m_ShEvalMode", (0..=3).contains(&sh_eval_mode))?;
238    in_range("m_MSAA", [1, 2, 4, 8].contains(&msaa))?;
239    in_range(
240        "m_DefaultRendererIndex",
241        usize::try_from(default_renderer).is_ok_and(|i| i < renderers.len()),
242    )?;
243    Ok(UnityUrpSettings {
244        renderers,
245        default_renderer: i64::from(default_renderer),
246        require_depth_texture,
247        require_opaque_texture,
248        msaa: i64::from(msaa),
249        main_light_mode: i64::from(main_light_mode),
250        main_light_shadows,
251        additional_lights_mode: i64::from(additional_lights_mode),
252        additional_light_shadows,
253        reflection_probe_blending,
254        reflection_probe_box_projection,
255        reflection_probe_atlas,
256        shadow_distance,
257        shadow_cascades: u8::try_from(shadow_cascades).map_err(|_| {
258            invalid(format!(
259                "Universal Render Pipeline asset: m_ShadowCascadeCount {shadow_cascades} out of range"
260            ))
261        })?,
262        soft_shadows,
263        mixed_lighting,
264        light_cookies,
265        light_layers,
266        lod_cross_fade,
267        sh_eval_mode: i64::from(sh_eval_mode),
268        light_probe_system: i64::from(light_probe_system),
269    })
270}
271
272/// A bool, padded to 4 bytes.
273fn flag(r: &mut Reader) -> Result<bool> {
274    let b = r.bool()?;
275    r.align(4)?;
276    Ok(b)
277}
278
279/// A list of strings, each its byte count and its UTF-8 bytes padded to 4 bytes.
280fn strings(r: &mut Reader, what: &str) -> Result<Vec<String>> {
281    let n = list_len(r, what, 4)?;
282    (0..n)
283        .map(|_| {
284            let len = list_len(r, what, 1)?;
285            let s = std::str::from_utf8(r.take(len)?)
286                .map_err(|_| invalid(format!("{what}: a string that is not UTF-8")))?
287                .to_owned();
288            r.align(4)?;
289            Ok(s)
290        })
291        .collect()
292}
293
294/// An object reference: its file id and path id.
295fn pptr(r: &mut Reader) -> Result<PPtr> {
296    Ok(PPtr {
297        file_id: r.i32()?,
298        path_id: r.i64()?,
299    })
300}
301
302/// A list's count, refused when its `size`-byte elements would run past the bytes left (`what` names the list).
303fn list_len(r: &mut Reader, what: &str, size: usize) -> Result<usize> {
304    let n = r.u32()?;
305    usize::try_from(n)
306        .ok()
307        .filter(|&n| n.checked_mul(size).is_some_and(|len| len <= r.remaining()))
308        .ok_or_else(|| {
309            invalid(format!(
310                "{what}: {n} entries of {size} bytes, {} bytes left",
311                r.remaining()
312            ))
313        })
314}
315
316/// A list of object references.
317fn pptrs(r: &mut Reader, what: &str) -> Result<Vec<PPtr>> {
318    let n = list_len(r, what, 12)?;
319    (0..n).map(|_| pptr(r)).collect()
320}
321
322/// A 4-byte enum value, refused unless it is one of `values` (`what` names the class and field).
323fn choice(r: &mut Reader, what: &str, values: &[i32]) -> Result<i64> {
324    let v = r.i32()?;
325    if values.contains(&v) {
326        Ok(i64::from(v))
327    } else {
328        Err(invalid(format!(
329            "{what} {v} (the values: {values:?}; an unverified layout)"
330        )))
331    }
332}
333
334/// The values `0..n` of an enum.
335fn first(n: i32) -> Vec<i32> {
336    (0..n).collect()
337}
338
339/// Refused unless the bytes were used up: a stored object longer than the layout is another layout.
340fn used_up(r: &Reader, what: &str) -> Result<()> {
341    match r.remaining() {
342        0 => Ok(()),
343        n => Err(invalid(format!(
344            "{what}: {n} bytes past the verified layout's {} (another layout)",
345            r.pos
346        ))),
347    }
348}
349
350/// The `m_AssetVersion` of `UniversalRendererData` the renderer layout is verified for.
351const RENDERER_ASSET_VERSION: i32 = 3;
352
353/// The bytes a `UniversalRendererData` stores after `m_UseNativeRenderPass` in the verified layout: `xrSystemData`,
354/// `postProcessData`, the asset version, the three layer masks, the stencil state and the eight modes. A Universal RP
355/// compiled without XR support (`ENABLE_VR && ENABLE_XR_MODULE` unset) stores no `xrSystemData` (84 bytes); no player
356/// read so far is one, so that layout is refused.
357const RENDERER_TAIL: usize = 96;
358
359/// The renderer's default stencil state (`m_DefaultStencilState`, a `StencilStateData`): Unity's `CompareFunction`
360/// (0 disabled, 1 never, 2 less, 3 equal, 4 less or equal, 5 greater, 6 not equal, 7 greater or equal, 8 always) and
361/// `StencilOp` (0 keep, 1 zero, 2 replace, 3 increment saturate, 4 decrement saturate, 5 invert, 6 increment wrap,
362/// 7 decrement wrap) numbers.
363#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
364pub struct UnityStencilState {
365    /// Whether the renderer's opaque and transparent draws use this state (`overrideStencilState`).
366    pub override_stencil: bool,
367    /// `stencilReference`.
368    pub reference: i64,
369    /// `stencilCompareFunction`.
370    pub compare: i64,
371    /// `passOperation`.
372    pub pass: i64,
373    /// `failOperation`.
374    pub fail: i64,
375    /// `zFailOperation`.
376    pub z_fail: i64,
377}
378
379/// A Universal Render Pipeline renderer as stored (`UniversalRendererData`, asset version 3, Universal RP 17.3; this
380/// module's docs say how the layout is verified). Its modes are numbered as the package's enums are:
381///
382/// - `RenderingMode`: 0 forward, 1 deferred, 2 Forward+, 3 Deferred+.
383/// - `DepthPrimingMode`: 0 disabled, 1 auto, 2 forced.
384/// - `CopyDepthMode`: 0 after opaques, 1 after transparents, 2 force prepass.
385/// - `DepthFormat`: 0 default, 90 16-bit, 91 24-bit, 93 32-bit, 151, 92 and 94 the same with 8 stencil bits.
386/// - `IntermediateTextureMode`: 0 auto, 1 always.
387#[derive(Clone, Debug, PartialEq, Serialize)]
388pub struct UnityUrpRenderer {
389    /// The renderer features (`m_RendererFeatures`), each a `ScriptableRendererFeature` asset, in the order the
390    /// renderer asks them for passes.
391    pub features: Vec<PPtr>,
392    /// `m_RendererFeatureMap` as stored: the editor's ids for the features. A player does not read it, and a stored
393    /// map need not match the features (Big Walk's second renderer has no features and one entry).
394    pub feature_map: Vec<i64>,
395    /// `m_UseNativeRenderPass`.
396    pub native_render_pass: bool,
397    /// `xrSystemData`.
398    pub xr_system_data: PPtr,
399    /// `postProcessData`.
400    pub post_process_data: PPtr,
401    /// The layers a depth prepass draws (`m_PrepassLayerMask`, one bit per layer).
402    pub prepass_layer_mask: u32,
403    /// The layers the opaque draws draw (`m_OpaqueLayerMask`).
404    pub opaque_layer_mask: u32,
405    /// The layers the transparent draws draw (`m_TransparentLayerMask`).
406    pub transparent_layer_mask: u32,
407    /// `m_DefaultStencilState`.
408    pub stencil: UnityStencilState,
409    /// `m_ShadowTransparentReceive`.
410    pub shadow_transparent_receive: bool,
411    /// `m_RenderingMode`.
412    pub rendering_mode: i64,
413    /// `m_DepthPrimingMode`.
414    pub depth_priming_mode: i64,
415    /// `m_CopyDepthMode`.
416    pub copy_depth_mode: i64,
417    /// `m_DepthAttachmentFormat`.
418    pub depth_attachment_format: i64,
419    /// `m_DepthTextureFormat`.
420    pub depth_texture_format: i64,
421    /// `m_AccurateGbufferNormals`.
422    pub accurate_gbuffer_normals: bool,
423    /// `m_IntermediateTextureMode`.
424    pub intermediate_texture_mode: i64,
425}
426
427/// The renderer whose script fields are `bytes` (the bytes after the MonoBehaviour's base fields).
428pub fn read_urp_renderer(bytes: &[u8]) -> Result<UnityUrpRenderer> {
429    const WHAT: &str = "Universal Render Pipeline renderer";
430    let mut r = Reader::new(bytes, false);
431    // `debugShaders` (two shaders) and `probeVolumeResources` (seven objects)
432    r.take(12 * 9)?;
433    let features = pptrs(&mut r, "m_RendererFeatures")?;
434    let n = list_len(&mut r, "m_RendererFeatureMap", 8)?;
435    let feature_map = (0..n).map(|_| r.i64()).collect::<Result<Vec<_>>>()?;
436    let native_render_pass = flag(&mut r)?;
437    if r.remaining() != RENDERER_TAIL {
438        return Err(invalid(format!(
439            "{WHAT}: {} bytes after m_UseNativeRenderPass, the verified layout stores {RENDERER_TAIL} (84 is the layout \
440             without xrSystemData, which is not verified)",
441            r.remaining()
442        )));
443    }
444    let xr_system_data = pptr(&mut r)?;
445    let post_process_data = pptr(&mut r)?;
446    let version = r.i32()?;
447    if version != RENDERER_ASSET_VERSION {
448        return Err(invalid(format!(
449            "{WHAT} asset version {version}: only version {RENDERER_ASSET_VERSION} has a verified layout"
450        )));
451    }
452    let prepass_layer_mask = r.u32()?;
453    let opaque_layer_mask = r.u32()?;
454    let transparent_layer_mask = r.u32()?;
455    let stencil = UnityStencilState {
456        override_stencil: flag(&mut r)?,
457        reference: i64::from(r.i32()?),
458        compare: choice(&mut r, "stencilCompareFunction", &first(9))?,
459        pass: choice(&mut r, "passOperation", &first(8))?,
460        fail: choice(&mut r, "failOperation", &first(8))?,
461        z_fail: choice(&mut r, "zFailOperation", &first(8))?,
462    };
463    let depth_formats = [0, 90, 91, 92, 93, 94, 151];
464    let renderer = UnityUrpRenderer {
465        features,
466        feature_map,
467        native_render_pass,
468        xr_system_data,
469        post_process_data,
470        prepass_layer_mask,
471        opaque_layer_mask,
472        transparent_layer_mask,
473        stencil,
474        shadow_transparent_receive: flag(&mut r)?,
475        rendering_mode: choice(&mut r, "m_RenderingMode", &first(4))?,
476        depth_priming_mode: choice(&mut r, "m_DepthPrimingMode", &first(3))?,
477        copy_depth_mode: choice(&mut r, "m_CopyDepthMode", &first(3))?,
478        depth_attachment_format: choice(&mut r, "m_DepthAttachmentFormat", &depth_formats)?,
479        depth_texture_format: choice(&mut r, "m_DepthTextureFormat", &depth_formats)?,
480        accurate_gbuffer_normals: flag(&mut r)?,
481        intermediate_texture_mode: choice(&mut r, "m_IntermediateTextureMode", &first(2))?,
482    };
483    used_up(&r, WHAT)?;
484    Ok(renderer)
485}
486
487/// Whether the renderer feature whose script fields are `bytes` is on (`ScriptableRendererFeature.m_Active`, the
488/// first field of every feature class). A renderer asks only its active features for passes.
489pub fn read_urp_feature_active(bytes: &[u8]) -> Result<bool> {
490    flag(&mut Reader::new(bytes, false))
491}
492
493/// The script class of Universal RP's Decal renderer feature.
494pub const URP_DECAL_FEATURE: &str = "UnityEngine.Rendering.Universal.DecalRendererFeature";
495
496/// The script class of Universal RP's Full Screen Pass renderer feature.
497pub const URP_FULL_SCREEN_FEATURE: &str = "UnityEngine.Rendering.Universal.FullScreenPassRendererFeature";
498
499/// A Decal renderer feature as stored ([`URP_DECAL_FEATURE`]: `m_Active` and its `DecalSettings`, Universal RP
500/// 17.3).
501#[derive(Clone, Debug, PartialEq, Serialize)]
502pub struct UnityUrpDecalFeature {
503    /// `m_Active`.
504    pub active: bool,
505    /// `technique`, a `DecalTechniqueOption`: 0 automatic, 1 DBuffer, 2 screen space.
506    pub technique: i64,
507    /// `maxDrawDistance`.
508    pub max_draw_distance: f32,
509    /// `decalLayers`.
510    pub decal_layers: bool,
511    /// `dBufferSettings.surfaceData`, a `DecalSurfaceData`: 0 albedo, 1 albedo and normal, 2 albedo, normal,
512    /// metallic, occlusion and smoothness.
513    pub dbuffer_surface_data: i64,
514    /// `screenSpaceSettings.normalBlend`, a `DecalNormalBlend`: 0 low, 1 medium, 2 high.
515    pub screen_space_normal_blend: i64,
516}
517
518/// The Decal feature whose script fields are `bytes`.
519pub fn read_urp_decal_feature(bytes: &[u8]) -> Result<UnityUrpDecalFeature> {
520    let mut r = Reader::new(bytes, false);
521    let feature = UnityUrpDecalFeature {
522        active: flag(&mut r)?,
523        technique: choice(&mut r, "DecalSettings.technique", &first(3))?,
524        max_draw_distance: r.f32()?,
525        decal_layers: flag(&mut r)?,
526        dbuffer_surface_data: choice(&mut r, "DBufferSettings.surfaceData", &first(3))?,
527        screen_space_normal_blend: choice(&mut r, "DecalScreenSpaceSettings.normalBlend", &first(3))?,
528    };
529    used_up(&r, "Decal renderer feature")?;
530    Ok(feature)
531}
532
533/// The `m_Version` of a Full Screen Pass feature the layout is verified for (`AddFetchColorBufferCheckbox`).
534const FULL_SCREEN_VERSION: i32 = 1;
535
536/// A Full Screen Pass renderer feature as stored ([`URP_FULL_SCREEN_FEATURE`], `m_Version` 1, Universal RP 17.3).
537#[derive(Clone, Debug, PartialEq, Serialize)]
538pub struct UnityUrpFullScreenFeature {
539    /// `m_Active`.
540    pub active: bool,
541    /// When its pass runs (`injectionPoint`, a `RenderPassEvent`): 450 before transparents, 550 before
542    /// post-processing, 600 after post-processing.
543    pub injection_point: i64,
544    /// Whether the pass copies the camera colour for its material to read (`fetchColorBuffer`).
545    pub fetch_color_buffer: bool,
546    /// The textures the pass asks the renderer for (`requirements`, `ScriptableRenderPassInput` flags: 1 depth,
547    /// 2 normals, 4 colour, 8 motion vectors; the editor's "everything" stores -1, every bit set).
548    pub requirements: i64,
549    /// `passMaterial`.
550    pub pass_material: PPtr,
551    /// `passIndex`.
552    pub pass_index: i64,
553    /// `bindDepthStencilAttachment`.
554    pub bind_depth_stencil_attachment: bool,
555}
556
557/// The Full Screen Pass feature whose script fields are `bytes`.
558pub fn read_urp_full_screen_feature(bytes: &[u8]) -> Result<UnityUrpFullScreenFeature> {
559    const WHAT: &str = "Full Screen Pass renderer feature";
560    let mut r = Reader::new(bytes, false);
561    let active = flag(&mut r)?;
562    let injection_point = choice(&mut r, "injectionPoint", &[450, 550, 600])?;
563    let fetch_color_buffer = flag(&mut r)?;
564    let requirements = i64::from(r.i32()?);
565    let pass_material = pptr(&mut r)?;
566    let pass_index = i64::from(r.i32()?);
567    let bind_depth_stencil_attachment = flag(&mut r)?;
568    let version = r.i32()?;
569    if version != FULL_SCREEN_VERSION {
570        return Err(invalid(format!(
571            "{WHAT} m_Version {version}: only version {FULL_SCREEN_VERSION} is verified"
572        )));
573    }
574    used_up(&r, WHAT)?;
575    Ok(UnityUrpFullScreenFeature {
576        active,
577        injection_point,
578        fetch_color_buffer,
579        requirements,
580        pass_material,
581        pass_index,
582        bind_depth_stencil_attachment,
583    })
584}
585
586/// The script class of the URP data Universal RP keeps beside a Camera.
587pub const URP_CAMERA_DATA: &str = "UnityEngine.Rendering.Universal.UniversalAdditionalCameraData";
588
589/// The `m_Version` of a camera's URP data the layout is verified for (`DepthAndOpaqueTextureOptions`).
590const CAMERA_VERSION: i32 = 2;
591
592/// A camera's temporal anti-aliasing settings (`TemporalAA.Settings`).
593#[derive(Clone, Debug, PartialEq, Serialize)]
594pub struct UnityTaaSettings {
595    /// `m_Quality`, a `TemporalAAQuality`: 0 very low to 4 very high.
596    pub quality: i64,
597    /// `m_FrameInfluence`.
598    pub frame_influence: f32,
599    /// `m_JitterScale`.
600    pub jitter_scale: f32,
601    /// `m_MipBias`.
602    pub mip_bias: f32,
603    /// `m_VarianceClampScale`.
604    pub variance_clamp_scale: f32,
605    /// `m_ContrastAdaptiveSharpening`.
606    pub contrast_adaptive_sharpening: f32,
607}
608
609/// A camera's URP data as stored ([`URP_CAMERA_DATA`], `m_Version` 2, Universal RP 17.3). The options are
610/// `CameraOverrideOption`s: 0 off, 1 on, 2 the pipeline asset's setting.
611#[derive(Clone, Debug, PartialEq, Serialize)]
612pub struct UnityUrpCamera {
613    /// `m_RenderShadows`.
614    pub render_shadows: bool,
615    /// Whether the camera makes the depth texture (`m_RequiresDepthTextureOption`).
616    pub depth_texture_option: i64,
617    /// Whether the camera makes the opaque texture (`m_RequiresOpaqueTextureOption`).
618    pub opaque_texture_option: i64,
619    /// `m_CameraType`, a `CameraRenderType`: 0 base, 1 overlay.
620    pub camera_type: i64,
621    /// The overlay cameras stacked on a base camera (`m_Cameras`).
622    pub stack: Vec<PPtr>,
623    /// The index of the pipeline asset's renderer the camera draws with (`m_RendererIndex`); -1 for the asset's
624    /// default renderer.
625    pub renderer_index: i64,
626    /// `m_VolumeLayerMask`.
627    pub volume_layer_mask: u32,
628    /// `m_VolumeTrigger`.
629    pub volume_trigger: PPtr,
630    /// `m_VolumeFrameworkUpdateModeOption`: 0 every frame, 1 by script, 2 the pipeline asset's setting.
631    pub volume_update_mode: i64,
632    /// `m_RenderPostProcessing`.
633    pub render_post_processing: bool,
634    /// `m_Antialiasing`, an `AntialiasingMode`: 0 none, 1 FXAA, 2 SMAA, 3 temporal.
635    pub antialiasing: i64,
636    /// `m_AntialiasingQuality`: 0 low, 1 medium, 2 high.
637    pub antialiasing_quality: i64,
638    /// `m_StopNaN`.
639    pub stop_nan: bool,
640    /// `m_Dithering`.
641    pub dithering: bool,
642    /// `m_ClearDepth`.
643    pub clear_depth: bool,
644    /// `m_AllowXRRendering`.
645    pub allow_xr_rendering: bool,
646    /// `m_AllowHDROutput`.
647    pub allow_hdr_output: bool,
648    /// `m_UseScreenCoordOverride`.
649    pub use_screen_coord_override: bool,
650    /// `m_ScreenSizeOverride`.
651    pub screen_size_override: [f32; 4],
652    /// `m_ScreenCoordScaleBias`.
653    pub screen_coord_scale_bias: [f32; 4],
654    /// `m_RequiresDepthTexture`, the field the depth texture option replaced.
655    pub requires_depth_texture: bool,
656    /// `m_RequiresColorTexture`, the field the opaque texture option replaced.
657    pub requires_color_texture: bool,
658    /// `m_TaaSettings`.
659    pub taa: UnityTaaSettings,
660}
661
662/// The camera URP data whose script fields are `bytes`.
663pub fn read_urp_camera(bytes: &[u8]) -> Result<UnityUrpCamera> {
664    const WHAT: &str = "Universal Render Pipeline camera data";
665    let mut r = Reader::new(bytes, false);
666    let vec4 = |r: &mut Reader| -> Result<[f32; 4]> { Ok([r.f32()?, r.f32()?, r.f32()?, r.f32()?]) };
667    let camera = UnityUrpCamera {
668        render_shadows: flag(&mut r)?,
669        depth_texture_option: choice(&mut r, "m_RequiresDepthTextureOption", &first(3))?,
670        opaque_texture_option: choice(&mut r, "m_RequiresOpaqueTextureOption", &first(3))?,
671        camera_type: choice(&mut r, "m_CameraType", &first(2))?,
672        stack: pptrs(&mut r, "m_Cameras")?,
673        renderer_index: i64::from(r.i32()?),
674        volume_layer_mask: r.u32()?,
675        volume_trigger: pptr(&mut r)?,
676        volume_update_mode: choice(&mut r, "m_VolumeFrameworkUpdateModeOption", &first(3))?,
677        render_post_processing: flag(&mut r)?,
678        antialiasing: choice(&mut r, "m_Antialiasing", &first(4))?,
679        antialiasing_quality: choice(&mut r, "m_AntialiasingQuality", &first(3))?,
680        stop_nan: flag(&mut r)?,
681        dithering: flag(&mut r)?,
682        clear_depth: flag(&mut r)?,
683        allow_xr_rendering: flag(&mut r)?,
684        allow_hdr_output: flag(&mut r)?,
685        use_screen_coord_override: flag(&mut r)?,
686        screen_size_override: vec4(&mut r)?,
687        screen_coord_scale_bias: vec4(&mut r)?,
688        requires_depth_texture: flag(&mut r)?,
689        requires_color_texture: flag(&mut r)?,
690        taa: UnityTaaSettings {
691            quality: choice(&mut r, "TemporalAA.Settings.m_Quality", &first(5))?,
692            frame_influence: r.f32()?,
693            jitter_scale: r.f32()?,
694            mip_bias: r.f32()?,
695            variance_clamp_scale: r.f32()?,
696            contrast_adaptive_sharpening: r.f32()?,
697        },
698    };
699    let version = r.i32()?;
700    if version != CAMERA_VERSION {
701        return Err(invalid(format!(
702            "{WHAT} m_Version {version}: only version {CAMERA_VERSION} is verified"
703        )));
704    }
705    used_up(&r, WHAT)?;
706    Ok(camera)
707}
708
709/// The script class of Universal RP's global settings (the object GraphicsSettings'
710/// `m_CurrentRenderPipelineGlobalSettings` names; [`crate::player::Player::urp_global_settings`]).
711pub const URP_GLOBAL_SETTINGS: &str = "UnityEngine.Rendering.Universal.UniversalRenderPipelineGlobalSettings";
712
713/// The `m_AssetVersion` of the global settings the layout is verified for.
714const GLOBAL_SETTINGS_VERSION: i32 = 10;
715
716/// The version of the managed-reference registry a script's `[SerializeReference]` fields are stored in.
717const REFERENCES_VERSION: i32 = 2;
718
719/// Universal RP's global settings as stored (`UniversalRenderPipelineGlobalSettings`, `m_AssetVersion` 10, Universal
720/// RP 17.3): the rendering layers and the graphics settings the pipeline reads its runtime resources and options from.
721/// The settings are `[SerializeReference]` entries, each stored once in the object's managed-reference registry with
722/// its class; every one is read to its last byte, so a class whose layout is not verified is refused by name
723/// ([`verified_graphics_settings`] lists the verified ones).
724#[derive(Clone, Debug, PartialEq, Serialize)]
725pub struct UnityUrpGlobalSettings {
726    /// `m_RenderingLayerNames`.
727    pub rendering_layer_names: Vec<String>,
728    /// `m_ValidRenderingLayers`.
729    pub valid_rendering_layers: u32,
730    /// The graphics settings (`m_Settings.m_RuntimeSettings.m_List`), in the list's order.
731    pub settings: Vec<UnityGraphicsSettings>,
732}
733
734/// One graphics settings entry (an `IRenderPipelineGraphicsSettings`) as stored.
735#[derive(Clone, Debug, PartialEq, Serialize)]
736pub struct UnityGraphicsSettings {
737    /// Its managed-reference id.
738    pub rid: i64,
739    /// Its class, `namespace.class` as the registry names it (a nested class `Outer/Inner`).
740    pub class: String,
741    /// The assembly the class is in.
742    pub assembly: String,
743    /// Its serialized fields, in declaration order, each by its name in the source.
744    pub fields: Vec<(&'static str, UnityField)>,
745}
746
747/// A field of a graphics settings entry.
748#[derive(Clone, Debug, PartialEq, Serialize)]
749#[serde(rename_all = "snake_case")]
750pub enum UnityField {
751    /// A 4-byte integer or enum value.
752    Int(i64),
753    /// A bool, padded to 4 bytes.
754    Bool(bool),
755    /// An object reference.
756    Ref(PPtr),
757    /// A list of object references.
758    Refs(Vec<PPtr>),
759    /// A list of colours (`Color[]`), RGBA.
760    Colours(Vec<[f32; 4]>),
761}
762
763/// How a field of a graphics settings class is stored.
764#[derive(Clone, Copy, Debug)]
765enum FieldKind {
766    /// A 4-byte value that must be this one: a class's `m_Version`, whose other values are other layouts.
767    Version(i32),
768    Int,
769    Bool,
770    Ref,
771    Refs,
772    Colours,
773}
774
775use FieldKind as F;
776
777/// The fields of `ShaderStrippingSetting`.
778const SHADER_STRIPPING_FIELDS: &[(&str, FieldKind)] = &[
779    ("m_Version", F::Version(0)),
780    ("m_ExportShaderVariants", F::Bool),
781    ("m_ShaderVariantLogLevel", F::Int),
782    ("m_StripRuntimeDebugShaders", F::Bool),
783];
784
785/// The graphics settings classes whose layout is verified, each with its serialized fields in declaration order
786/// (Universal RP 17.3's, Core RP 17.3's and GPU Driven RP 17.3's classes as Unity 6000.3's players store them): each
787/// reads to the byte where the registry's next entry starts on PEAK's and Sledding Game's players.
788const GRAPHICS_SETTINGS: [(&str, &[(&str, FieldKind)]); 18] = [
789    (
790        "UnityEngine.Rendering.Universal.UniversalRenderPipelineRuntimeTerrainShaders",
791        &[
792            ("m_Version", F::Version(0)),
793            ("m_TerrainDetailLit", F::Ref),
794            ("m_TerrainDetailGrassBillboard", F::Ref),
795            ("m_TerrainDetailGrass", F::Ref),
796        ],
797    ),
798    (
799        "UnityEngine.Rendering.URPReflectionProbeSettings",
800        &[
801            ("version", F::Version(1)),
802            ("useReflectionProbeRotation", F::Bool),
803        ],
804    ),
805    (
806        "UnityEngine.Rendering.VrsRenderPipelineRuntimeResources",
807        &[
808            ("m_TextureComputeShader", F::Ref),
809            ("m_VisualizationShader", F::Ref),
810            ("m_VisualizationLookupTable", F::Colours),
811            ("m_ConversionLookupTable", F::Colours),
812        ],
813    ),
814    (
815        "UnityEngine.Rendering.LightmapSamplingSettings",
816        &[
817            ("m_Version", F::Version(1)),
818            ("m_UseBicubicLightmapSampling", F::Bool),
819        ],
820    ),
821    (
822        URP_RENDER_GRAPH_SETTINGS,
823        &[
824            ("m_Version", F::Version(0)),
825            ("m_EnableRenderCompatibilityMode", F::Bool),
826        ],
827    ),
828    (
829        URP_RENDERER_RESOURCES,
830        &[
831            ("m_Version", F::Version(0)),
832            ("m_CopyDepthPS", F::Ref),
833            ("m_CameraMotionVector", F::Ref),
834            ("m_StencilDeferredPS", F::Ref),
835            ("m_ClusterDeferred", F::Ref),
836            ("m_StencilDitherMaskSeedPS", F::Ref),
837            ("m_DBufferClear", F::Ref),
838        ],
839    ),
840    (
841        "UnityEngine.Rendering.Universal.URPDefaultVolumeProfileSettings",
842        &[("m_Version", F::Version(0)), ("m_VolumeProfile", F::Ref)],
843    ),
844    (
845        "UnityEngine.Rendering.Universal.UniversalRenderPipelineRuntimeShaders",
846        &[
847            ("m_Version", F::Version(0)),
848            ("m_FallbackErrorShader", F::Ref),
849            ("m_BlitHDROverlay", F::Ref),
850            ("m_CoreBlitPS", F::Ref),
851            ("m_CoreBlitColorAndDepthPS", F::Ref),
852            ("m_SamplingPS", F::Ref),
853            ("m_TerrainDetailLit", F::Ref),
854            ("m_TerrainDetailGrassBillboard", F::Ref),
855            ("m_TerrainDetailGrass", F::Ref),
856        ],
857    ),
858    (
859        "UnityEngine.Rendering.Universal.UniversalRenderPipelineRuntimeXRResources",
860        &[
861            ("m_xrOcclusionMeshPS", F::Ref),
862            ("m_xrMirrorViewPS", F::Ref),
863            ("m_xrMotionVector", F::Ref),
864        ],
865    ),
866    (
867        "UnityEngine.Rendering.Universal.UniversalRenderPipelineDebugShaders",
868        &[
869            ("m_DebugReplacementPS", F::Ref),
870            ("m_HdrDebugViewPS", F::Ref),
871            ("m_ProbeVolumeSamplingDebugComputeShader", F::Ref),
872        ],
873    ),
874    (
875        "UnityEngine.Rendering.Universal.UniversalRenderPipelineRuntimeTextures",
876        &[
877            ("m_Version", F::Version(1)),
878            ("m_BlueNoise64LTex", F::Ref),
879            ("m_BayerMatrixTex", F::Ref),
880            ("m_DebugFontTex", F::Ref),
881        ],
882    ),
883    (
884        "UnityEngine.Rendering.RenderGraphGlobalSettings",
885        &[
886            ("m_version", F::Version(0)),
887            ("m_EnableCompilationCaching", F::Bool),
888            ("m_EnableValidityChecks", F::Bool),
889        ],
890    ),
891    (
892        "UnityEngine.Rendering.RenderGraphModule.Util.RenderGraphUtilsResources",
893        &[("m_Version", F::Version(0)), ("m_CoreCopyPS", F::Ref)],
894    ),
895    (
896        "UnityEngine.Rendering.ShaderStrippingSetting",
897        SHADER_STRIPPING_FIELDS,
898    ),
899    (
900        "UnityEngine.Rendering.GPUResidentDrawerResources",
901        &[
902            ("m_Version", F::Version(0)),
903            ("m_InstanceDataBufferCopyKernels", F::Ref),
904            ("m_InstanceDataBufferUploadKernels", F::Ref),
905            ("m_TransformUpdaterKernels", F::Ref),
906            ("m_WindDataUpdaterKernels", F::Ref),
907            ("m_OccluderDepthPyramidKernels", F::Ref),
908            ("m_InstanceOcclusionCullingKernels", F::Ref),
909            ("m_OcclusionCullingDebugKernels", F::Ref),
910            ("m_DebugOcclusionTestPS", F::Ref),
911            ("m_DebugOccluderPS", F::Ref),
912        ],
913    ),
914    (
915        "UnityEngine.Rendering.STP/RuntimeResources",
916        &[("m_setupCS", F::Ref), ("m_preTaaCS", F::Ref), ("m_taaCS", F::Ref)],
917    ),
918    (
919        "UnityEngine.Rendering.Universal.ScreenSpaceAmbientOcclusionPersistentResources",
920        &[("m_Shader", F::Ref), ("m_Version", F::Version(0))],
921    ),
922    (
923        "UnityEngine.Rendering.Universal.ScreenSpaceAmbientOcclusionDynamicResources",
924        &[("m_BlueNoise256Textures", F::Refs), ("m_Version", F::Version(0))],
925    ),
926];
927
928/// The graphics settings classes whose layout is verified ([`read_urp_global_settings`] refuses the others by name).
929pub fn verified_graphics_settings() -> impl Iterator<Item = &'static str> {
930    GRAPHICS_SETTINGS.iter().map(|(c, _)| *c)
931}
932
933/// The class of Universal RP's render graph settings.
934pub const URP_RENDER_GRAPH_SETTINGS: &str = "UnityEngine.Rendering.Universal.RenderGraphSettings";
935
936/// The class of the Universal Renderer's runtime resources.
937pub const URP_RENDERER_RESOURCES: &str = "UnityEngine.Rendering.Universal.UniversalRendererResources";
938
939/// The Universal Renderer's runtime shaders (`UniversalRendererResources`), each a `Shader` reference.
940#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
941pub struct UnityUrpRendererResources {
942    /// `m_CopyDepthPS`: the copy of the camera depth (`CopyDepthPass`).
943    pub copy_depth: PPtr,
944    /// `m_CameraMotionVector`.
945    pub camera_motion_vector: PPtr,
946    /// `m_StencilDeferredPS`.
947    pub stencil_deferred: PPtr,
948    /// `m_ClusterDeferred`.
949    pub cluster_deferred: PPtr,
950    /// `m_StencilDitherMaskSeedPS`.
951    pub stencil_dither_mask_seed: PPtr,
952    /// `m_DBufferClear`: the DBuffer's clear (`DBufferRenderPass`).
953    pub dbuffer_clear: PPtr,
954}
955
956impl UnityGraphicsSettings {
957    /// The field named `name`, where the class has one.
958    pub fn field(&self, name: &str) -> Option<&UnityField> {
959        self.fields.iter().find(|(n, _)| *n == name).map(|(_, v)| v)
960    }
961}
962
963impl UnityUrpGlobalSettings {
964    /// The entry of class `class`, where the list has one (a class is listed once).
965    pub fn entry(&self, class: &str) -> Option<&UnityGraphicsSettings> {
966        self.settings.iter().find(|s| s.class == class)
967    }
968
969    /// The Universal Renderer's runtime shaders, where the settings list them.
970    pub fn renderer_resources(&self) -> Option<UnityUrpRendererResources> {
971        let e = self.entry(URP_RENDERER_RESOURCES)?;
972        let r = |name: &str| match e.field(name) {
973            Some(UnityField::Ref(p)) => Some(*p),
974            _ => None,
975        };
976        Some(UnityUrpRendererResources {
977            copy_depth: r("m_CopyDepthPS")?,
978            camera_motion_vector: r("m_CameraMotionVector")?,
979            stencil_deferred: r("m_StencilDeferredPS")?,
980            cluster_deferred: r("m_ClusterDeferred")?,
981            stencil_dither_mask_seed: r("m_StencilDitherMaskSeedPS")?,
982            dbuffer_clear: r("m_DBufferClear")?,
983        })
984    }
985
986    /// The stored `m_EnableRenderCompatibilityMode` of the render graph settings, where the settings list them: whether
987    /// the project asks Universal RP to draw without the render graph. A build honours it only where its Universal RP
988    /// is compiled with `URP_COMPATIBILITY_MODE` ([`crate::player::Player::urp_compatibility_code`]); without, the
989    /// pipeline always draws through the render graph.
990    pub fn render_compatibility_mode(&self) -> Option<bool> {
991        match self
992            .entry(URP_RENDER_GRAPH_SETTINGS)?
993            .field("m_EnableRenderCompatibilityMode")
994        {
995            Some(UnityField::Bool(b)) => Some(*b),
996            _ => None,
997        }
998    }
999}
1000
1001/// One string: its byte count and its UTF-8 bytes padded to 4 bytes.
1002fn string(r: &mut Reader, what: &str) -> Result<String> {
1003    let len = list_len(r, what, 1)?;
1004    let s = std::str::from_utf8(r.take(len)?)
1005        .map_err(|_| invalid(format!("{what}: a string that is not UTF-8")))?
1006        .to_owned();
1007    r.align(4)?;
1008    Ok(s)
1009}
1010
1011/// The fields `fields` of an object of `class`, in order.
1012fn settings_fields(
1013    r: &mut Reader,
1014    class: &str,
1015    fields: &[(&'static str, FieldKind)],
1016) -> Result<Vec<(&'static str, UnityField)>> {
1017    fields
1018        .iter()
1019        .map(|&(name, kind)| {
1020            let what = format!("{class}.{name}");
1021            let value = match kind {
1022                FieldKind::Version(want) => {
1023                    let v = r.i32()?;
1024                    if v != want {
1025                        return Err(invalid(format!(
1026                            "{what} {v}: only version {want} has a verified layout"
1027                        )));
1028                    }
1029                    UnityField::Int(i64::from(v))
1030                }
1031                FieldKind::Int => UnityField::Int(i64::from(r.i32()?)),
1032                FieldKind::Bool => UnityField::Bool(flag(r)?),
1033                FieldKind::Ref => UnityField::Ref(pptr(r)?),
1034                FieldKind::Refs => UnityField::Refs(pptrs(r, &what)?),
1035                FieldKind::Colours => {
1036                    let n = list_len(r, &what, 16)?;
1037                    UnityField::Colours(
1038                        (0..n)
1039                            .map(|_| Ok([r.f32()?, r.f32()?, r.f32()?, r.f32()?]))
1040                            .collect::<Result<_>>()?,
1041                    )
1042                }
1043            };
1044            Ok((name, value))
1045        })
1046        .collect()
1047}
1048
1049/// An empty list, refused when it holds anything: its elements' layout is not verified (no player read stores any).
1050fn empty_list(r: &mut Reader, what: &str) -> Result<()> {
1051    match r.u32()? {
1052        0 => Ok(()),
1053        n => Err(invalid(format!(
1054            "{what}: {n} entries, whose layout is not verified (only empty lists are read)"
1055        ))),
1056    }
1057}
1058
1059/// The global settings whose script fields are `bytes` ([`URP_GLOBAL_SETTINGS`]), read to the last byte with every
1060/// graphics settings entry of the registry: a class not in [`verified_graphics_settings`] is refused by name, as is an entry
1061/// the list and the registry do not both hold.
1062pub fn read_urp_global_settings(bytes: &[u8]) -> Result<UnityUrpGlobalSettings> {
1063    const WHAT: &str = "Universal Render Pipeline global settings";
1064    let mut r = Reader::new(bytes, false);
1065    // the migration copies of the stripping settings and the obsolete flags, kept by the class and read past
1066    settings_fields(&mut r, "m_ShaderStrippingSetting", SHADER_STRIPPING_FIELDS)?;
1067    settings_fields(
1068        &mut r,
1069        "m_URPShaderStrippingSetting",
1070        &[
1071            ("m_Version", F::Version(0)),
1072            ("m_StripUnusedPostProcessingVariants", F::Bool),
1073            ("m_StripUnusedVariants", F::Bool),
1074            ("m_StripScreenCoordOverrideVariants", F::Bool),
1075        ],
1076    )?;
1077    settings_fields(
1078        &mut r,
1079        WHAT,
1080        &[
1081            ("m_ShaderVariantLogLevel", F::Int),
1082            ("m_ExportShaderVariants", F::Bool),
1083            ("m_StripDebugVariants", F::Bool),
1084            ("m_StripUnusedPostProcessingVariants", F::Bool),
1085            ("m_StripUnusedVariants", F::Bool),
1086            ("m_StripScreenCoordOverrideVariants", F::Bool),
1087            ("supportRuntimeDebugDisplay", F::Bool),
1088            ("m_EnableRenderGraph", F::Bool),
1089        ],
1090    )?;
1091    // m_Settings.m_RuntimeSettings.m_List: the entries' managed-reference ids
1092    let n = list_len(&mut r, "m_Settings.m_RuntimeSettings.m_List", 8)?;
1093    let list: Vec<i64> = (0..n).map(|_| r.i64()).collect::<Result<_>>()?;
1094    let version = r.i32()?;
1095    if version != GLOBAL_SETTINGS_VERSION {
1096        return Err(invalid(format!(
1097            "{WHAT} m_AssetVersion {version}: only version {GLOBAL_SETTINGS_VERSION} has a verified layout"
1098        )));
1099    }
1100    pptr(&mut r)?; // m_ObsoleteDefaultVolumeProfile
1101    let rendering_layer_names = strings(&mut r, "m_RenderingLayerNames")?;
1102    let valid_rendering_layers = r.u32()?;
1103    for i in 0..8 {
1104        string(&mut r, &format!("lightLayerName{i}"))?;
1105    }
1106    // apvScenesData: two serialized dictionaries' key and value lists
1107    for list in [
1108        "obsoleteSceneBounds keys",
1109        "obsoleteSceneBounds values",
1110        "obsoleteHasProbeVolumes keys",
1111        "obsoleteHasProbeVolumes values",
1112    ] {
1113        empty_list(&mut r, &format!("apvScenesData.{list}"))?;
1114    }
1115    // the managed-reference registry: each entry's id, class and fields
1116    let registry = r.i32()?;
1117    if registry != REFERENCES_VERSION {
1118        return Err(invalid(format!(
1119            "{WHAT}: managed-reference registry version {registry} (read: {REFERENCES_VERSION})"
1120        )));
1121    }
1122    let n = list_len(&mut r, "the managed-reference registry", 20)?;
1123    let mut entries: BTreeMap<i64, UnityGraphicsSettings> = BTreeMap::new();
1124    for _ in 0..n {
1125        let rid = r.i64()?;
1126        let class = string(&mut r, "a managed reference's class")?;
1127        let namespace = string(&mut r, "a managed reference's namespace")?;
1128        let assembly = string(&mut r, "a managed reference's assembly")?;
1129        let class = match namespace.as_str() {
1130            "" => class,
1131            ns => format!("{ns}.{class}"),
1132        };
1133        let (_, layout) = GRAPHICS_SETTINGS.iter().find(|(c, _)| *c == class).ok_or_else(|| {
1134            invalid(format!(
1135                "{WHAT}: a graphics settings entry of class {class} ({assembly}), whose layout is not verified"
1136            ))
1137        })?;
1138        let fields = settings_fields(&mut r, &class, layout)?;
1139        let entry = UnityGraphicsSettings {
1140            rid,
1141            class,
1142            assembly,
1143            fields,
1144        };
1145        if let Some(first) = entries.insert(rid, entry) {
1146            return Err(invalid(format!(
1147                "{WHAT}: managed reference {rid} stored twice ({})",
1148                first.class
1149            )));
1150        }
1151    }
1152    used_up(&r, WHAT)?;
1153    let settings = list
1154        .iter()
1155        .map(|rid| {
1156            entries.remove(rid).ok_or_else(|| {
1157                invalid(format!(
1158                    "{WHAT}: the settings list names managed reference {rid}, which the registry does not hold"
1159                ))
1160            })
1161        })
1162        .collect::<Result<Vec<_>>>()?;
1163    if let Some(e) = entries.values().next() {
1164        return Err(invalid(format!(
1165            "{WHAT}: managed reference {} ({}) that the settings list does not name",
1166            e.rid, e.class
1167        )));
1168    }
1169    let mut classes = std::collections::BTreeSet::new();
1170    for s in &settings {
1171        if !classes.insert(s.class.as_str()) {
1172            return Err(invalid(format!("{WHAT}: the class {} listed twice", s.class)));
1173        }
1174    }
1175    Ok(UnityUrpGlobalSettings {
1176        rendering_layer_names,
1177        valid_rendering_layers,
1178        settings,
1179    })
1180}
1181
1182#[cfg(test)]
1183mod tests {
1184    use super::*;
1185
1186    /// A synthetic asset in the layout: each field at the position the real bytes of a player's asset put it.
1187    fn asset(cascades: i32, default_renderer: i32) -> Vec<u8> {
1188        let mut b: Vec<u8> = Vec::new();
1189        let int = |b: &mut Vec<u8>, v: i32| b.extend(v.to_le_bytes());
1190        let float = |b: &mut Vec<u8>, v: f32| b.extend(v.to_le_bytes());
1191        let bool4 = |b: &mut Vec<u8>, v: bool| b.extend(i32::from(v).to_le_bytes());
1192        int(&mut b, 13); // k_AssetVersion
1193        int(&mut b, 13);
1194        int(&mut b, 1); // renderer type
1195        b.extend([0; 12]); // m_RendererData
1196        int(&mut b, 1); // one renderer
1197        int(&mut b, 0);
1198        b.extend(6168i64.to_le_bytes());
1199        int(&mut b, default_renderer);
1200        bool4(&mut b, true); // depth texture
1201        bool4(&mut b, false); // opaque texture
1202        int(&mut b, 1);
1203        bool4(&mut b, false); // terrain holes
1204        bool4(&mut b, true); // HDR
1205        int(&mut b, 0);
1206        int(&mut b, 4); // MSAA
1207        float(&mut b, 1.0);
1208        int(&mut b, 0); // upscaling filter
1209        bool4(&mut b, false);
1210        float(&mut b, 0.92);
1211        bool4(&mut b, true); // LOD cross fade
1212        int(&mut b, 1);
1213        int(&mut b, 2); // SH evaluation: mixed
1214        int(&mut b, 0); // legacy light probes
1215        int(&mut b, 1024);
1216        int(&mut b, 256);
1217        for _ in 0..4 {
1218            bool4(&mut b, false);
1219        }
1220        int(&mut b, 1); // SH bands
1221        int(&mut b, 1); // main light: per pixel
1222        bool4(&mut b, true); // main light shadows
1223        int(&mut b, 4096);
1224        int(&mut b, 1); // additional lights: per pixel
1225        int(&mut b, 8);
1226        bool4(&mut b, true); // additional light shadows
1227        for v in [1024, 256, 512, 1024] {
1228            int(&mut b, v);
1229        }
1230        bool4(&mut b, true); // reflection probe blending
1231        bool4(&mut b, false); // box projection
1232        bool4(&mut b, true); // atlas
1233        float(&mut b, 400.0);
1234        int(&mut b, cascades);
1235        float(&mut b, 0.25);
1236        for v in [0.1, 0.3, 0.123, 0.29, 0.54, 0.11, 0.1, 0.5] {
1237            float(&mut b, v);
1238        }
1239        bool4(&mut b, true); // soft shadows
1240        bool4(&mut b, true); // conservative sphere
1241        int(&mut b, 64);
1242        int(&mut b, 3);
1243        int(&mut b, 2048);
1244        int(&mut b, 3);
1245        bool4(&mut b, true);
1246        bool4(&mut b, false);
1247        bool4(&mut b, true); // mixed lighting
1248        bool4(&mut b, false); // light cookies
1249        bool4(&mut b, true); // light layers
1250        // the fields after them, which no setting is read from: debug level to the volume framework's update mode
1251        int(&mut b, 0);
1252        int(&mut b, 0);
1253        bool4(&mut b, true);
1254        int(&mut b, 0);
1255        int(&mut b, 32);
1256        for _ in 0..4 {
1257            bool4(&mut b, true);
1258        }
1259        int(&mut b, 0);
1260        float(&mut b, 0.0);
1261        bool4(&mut b, false);
1262        int(&mut b, 1);
1263        bool4(&mut b, false);
1264        for v in [256, 0, 256, 2] {
1265            int(&mut b, v);
1266        }
1267        b.extend([0; 12]); // m_VolumeProfile
1268        // apvScenesData: one scene's bounds, one scene's flag (a 5-byte key, padded)
1269        int(&mut b, 1);
1270        int(&mut b, 5);
1271        b.extend(b"scene\0\0\0");
1272        int(&mut b, 1);
1273        b.extend([0; 24]);
1274        int(&mut b, 1);
1275        int(&mut b, 5);
1276        b.extend(b"scene\0\0\0");
1277        int(&mut b, 1);
1278        b.extend([1, 0, 0, 0]);
1279        int(&mut b, 0); // m_ShaderVariantLogLevel
1280        int(&mut b, 1); // m_ShadowCascades
1281        b.extend([0; 24]); // m_Textures
1282        b
1283    }
1284
1285    /// The fields the keywords depend on are read from where the layout puts them, bools padded to 4 bytes and the
1286    /// renderer list's length moving what follows it.
1287    #[test]
1288    fn a_urp_assets_settings_are_read_by_the_verified_layout() {
1289        let read = |b: &[u8]| read_urp_settings(b, UrpAssetLayout::Urp17_3);
1290        let s = read(&asset(4, 0)).unwrap();
1291        let renderer = PPtr {
1292            file_id: 0,
1293            path_id: 6168,
1294        };
1295        assert_eq!((s.renderers.as_slice(), s.default_renderer), (&[renderer][..], 0));
1296        assert!(s.require_depth_texture && !s.require_opaque_texture);
1297        assert_eq!(s.msaa, 4);
1298        assert_eq!((s.shadow_cascades, s.shadow_distance), (4, 400.0));
1299        assert!(s.main_light_shadows && s.soft_shadows && s.additional_light_shadows);
1300        assert!(
1301            s.reflection_probe_blending && !s.reflection_probe_box_projection && s.reflection_probe_atlas
1302        );
1303        assert!(s.mixed_lighting && !s.light_cookies && s.light_layers && s.lod_cross_fade);
1304        assert_eq!(
1305            (s.main_light_mode, s.additional_lights_mode, s.sh_eval_mode),
1306            (1, 1, 2)
1307        );
1308        // another version, and values no field can hold: a fifth cascade, a default renderer past the list
1309        let mut other = asset(4, 0);
1310        other[0] = 12;
1311        assert!(read(&other).is_err());
1312        assert!(read(&asset(7, 0)).is_err());
1313        let past = read(&asset(4, 1)).unwrap_err().to_string();
1314        assert!(past.contains("m_DefaultRendererIndex"), "{past}");
1315        // the asset is read to its last byte: one short, or one more, is another layout
1316        let whole = asset(4, 0);
1317        assert!(read(&whole[..whole.len() - 4]).is_err());
1318        let mut longer = whole.clone();
1319        longer.extend([0; 4]);
1320        let longer = read(&longer).unwrap_err().to_string();
1321        assert!(longer.contains("past the verified layout"), "{longer}");
1322    }
1323
1324    /// The layout is the player's Unity version's, known before any field is read: the versions it is verified on,
1325    /// and no other.
1326    #[test]
1327    fn a_urp_assets_layout_is_identified_by_the_players_unity_version() {
1328        let of = |v: &str| UrpAssetLayout::of(UnityVersion::parse(v).unwrap());
1329        assert_eq!(of("6000.3.12f1").unwrap(), UrpAssetLayout::Urp17_3);
1330        assert_eq!(of("6000.3.15f1").unwrap(), UrpAssetLayout::Urp17_3);
1331        let refused = of("6000.3.17f1").unwrap_err().to_string();
1332        assert!(refused.contains("Unity 6000.3.17f1 player"), "{refused}");
1333        assert!(of("6000.6.4f1").is_err());
1334    }
1335
1336    fn int(b: &mut Vec<u8>, v: i32) {
1337        b.extend(v.to_le_bytes());
1338    }
1339
1340    fn reference(b: &mut Vec<u8>, path_id: i64) {
1341        int(b, 0);
1342        b.extend(path_id.to_le_bytes());
1343    }
1344
1345    /// A synthetic renderer in the layout: two features, the masks, the stencil state and the modes after them.
1346    fn renderer(version: i32, mode: i32) -> Vec<u8> {
1347        let mut b = vec![0; 12 * 9]; // debugShaders, probeVolumeResources
1348        int(&mut b, 2);
1349        reference(&mut b, 6001);
1350        reference(&mut b, 6002);
1351        int(&mut b, 2);
1352        b.extend(7i64.to_le_bytes());
1353        b.extend((-8i64).to_le_bytes());
1354        int(&mut b, 1); // native render pass
1355        reference(&mut b, 0); // xrSystemData
1356        reference(&mut b, 6170); // postProcessData
1357        int(&mut b, version);
1358        for mask in [0x8a30_9437u32, 0x8a31_d437, u32::MAX] {
1359            b.extend(mask.to_le_bytes());
1360        }
1361        for v in [1, 3, 8, 2, 0, 0] {
1362            int(&mut b, v); // stencil: overridden, reference 3, always, replace
1363        }
1364        int(&mut b, 1); // shadow transparent receive
1365        int(&mut b, mode);
1366        int(&mut b, 0); // depth priming disabled
1367        int(&mut b, 1); // copy depth after transparents
1368        int(&mut b, 92);
1369        int(&mut b, 0);
1370        int(&mut b, 0);
1371        int(&mut b, 1); // intermediate texture always
1372        b
1373    }
1374
1375    /// A renderer's fields are read from where the layout puts them, the feature list's length moving what follows;
1376    /// a feature map is read as stored, whatever its length; another asset version, a mode no field can hold and a
1377    /// length the layout does not account for (extra bytes, or the layout without `xrSystemData`) are refused.
1378    #[test]
1379    fn a_urp_renderer_is_read_by_the_verified_layout() {
1380        let r = read_urp_renderer(&renderer(3, 2)).unwrap();
1381        let ids: Vec<i64> = r.features.iter().map(|f| f.path_id).collect();
1382        assert_eq!((ids, r.feature_map.clone()), (vec![6001, 6002], vec![7, -8]));
1383        assert!(r.native_render_pass && r.shadow_transparent_receive && !r.accurate_gbuffer_normals);
1384        assert_eq!((r.xr_system_data.path_id, r.post_process_data.path_id), (0, 6170));
1385        assert_eq!(
1386            (
1387                r.prepass_layer_mask,
1388                r.opaque_layer_mask,
1389                r.transparent_layer_mask
1390            ),
1391            (0x8a30_9437, 0x8a31_d437, u32::MAX)
1392        );
1393        assert_eq!(
1394            r.stencil,
1395            UnityStencilState {
1396                override_stencil: true,
1397                reference: 3,
1398                compare: 8,
1399                pass: 2,
1400                fail: 0,
1401                z_fail: 0
1402            }
1403        );
1404        assert_eq!(
1405            (r.rendering_mode, r.depth_priming_mode, r.copy_depth_mode),
1406            (2, 0, 1)
1407        );
1408        assert_eq!(
1409            (
1410                r.depth_attachment_format,
1411                r.depth_texture_format,
1412                r.intermediate_texture_mode
1413            ),
1414            (92, 0, 1)
1415        );
1416        let refusal = |b: &[u8]| read_urp_renderer(b).unwrap_err().to_string();
1417        assert!(refusal(&renderer(2, 2)).contains("asset version 2"));
1418        assert!(refusal(&renderer(3, 4)).contains("m_RenderingMode 4"));
1419        let mut longer = renderer(3, 2);
1420        longer.extend([0; 4]);
1421        assert!(refusal(&longer).contains("100 bytes after m_UseNativeRenderPass"));
1422        // the layout without xrSystemData: the same renderer 12 bytes shorter
1423        let mut no_xr = renderer(3, 2);
1424        no_xr.drain(160..172);
1425        assert!(refusal(&no_xr).contains("84 bytes after"), "{}", refusal(&no_xr));
1426        // a map of one entry for the two features: the bytes after it move back by one entry, as they are read
1427        let mut map = renderer(3, 2);
1428        map[136] = 1; // the map's count: one entry
1429        map.drain(148..156);
1430        let short = read_urp_renderer(&map).unwrap();
1431        assert_eq!((short.features.len(), short.feature_map), (2, vec![7]));
1432        assert_eq!(short.post_process_data.path_id, 6170);
1433        assert!(read_urp_renderer(&renderer(3, 2)[..150]).is_err());
1434    }
1435
1436    /// A feature's `m_Active` is its first field; the Decal and Full Screen Pass features' settings follow it, and a
1437    /// value outside a field's enum, another version or a length past the layout is refused.
1438    #[test]
1439    fn urp_features_are_read_by_the_verified_layout() {
1440        assert!(read_urp_feature_active(&[1, 0, 0, 0, 9, 9]).unwrap());
1441        assert!(!read_urp_feature_active(&[0, 0, 0, 0]).unwrap());
1442        assert!(read_urp_feature_active(&[2, 0, 0, 0]).is_err());
1443        let decal = |technique: i32| {
1444            let mut b = Vec::new();
1445            int(&mut b, 1);
1446            int(&mut b, technique);
1447            b.extend(500f32.to_le_bytes());
1448            int(&mut b, 0);
1449            int(&mut b, 2);
1450            int(&mut b, 0);
1451            b
1452        };
1453        assert_eq!(
1454            read_urp_decal_feature(&decal(1)).unwrap(),
1455            UnityUrpDecalFeature {
1456                active: true,
1457                technique: 1,
1458                max_draw_distance: 500.0,
1459                decal_layers: false,
1460                dbuffer_surface_data: 2,
1461                screen_space_normal_blend: 0
1462            }
1463        );
1464        assert!(read_urp_decal_feature(&decal(3)).is_err());
1465        let mut longer = decal(1);
1466        longer.extend([0; 4]);
1467        assert!(read_urp_decal_feature(&longer).is_err());
1468        let full_screen = |injection: i32, version: i32| {
1469            let mut b = Vec::new();
1470            int(&mut b, 1);
1471            int(&mut b, injection);
1472            int(&mut b, 1);
1473            int(&mut b, -1); // every input
1474            reference(&mut b, 7);
1475            int(&mut b, 0);
1476            int(&mut b, 0);
1477            int(&mut b, version);
1478            b
1479        };
1480        let f = read_urp_full_screen_feature(&full_screen(600, 1)).unwrap();
1481        assert!(f.active && f.fetch_color_buffer && !f.bind_depth_stencil_attachment);
1482        assert_eq!(
1483            (
1484                f.injection_point,
1485                f.requirements,
1486                f.pass_material.path_id,
1487                f.pass_index
1488            ),
1489            (600, -1, 7, 0)
1490        );
1491        let refusal = |b: &[u8]| read_urp_full_screen_feature(b).unwrap_err().to_string();
1492        assert!(refusal(&full_screen(300, 1)).contains("injectionPoint 300"));
1493        assert!(refusal(&full_screen(600, 0)).contains("m_Version 0"));
1494    }
1495
1496    /// A synthetic camera's URP data in the layout, with `stack` overlay cameras.
1497    fn camera(stack: i32, version: i32) -> Vec<u8> {
1498        let mut b = Vec::new();
1499        int(&mut b, 1); // shadows
1500        int(&mut b, 2);
1501        int(&mut b, 2);
1502        int(&mut b, 0); // base
1503        int(&mut b, stack);
1504        for i in 0..stack {
1505            reference(&mut b, i64::from(i) + 100);
1506        }
1507        int(&mut b, -1); // the default renderer
1508        b.extend(0x8000_0000u32.to_le_bytes());
1509        reference(&mut b, 0);
1510        int(&mut b, 2);
1511        int(&mut b, 1); // post-processing
1512        int(&mut b, 3); // temporal anti-aliasing
1513        int(&mut b, 2);
1514        for v in [0, 1, 1, 1, 1, 0] {
1515            int(&mut b, v);
1516        }
1517        for v in [0f32, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0] {
1518            b.extend(v.to_le_bytes());
1519        }
1520        int(&mut b, 0);
1521        int(&mut b, 0);
1522        int(&mut b, 3); // TAA quality high
1523        for v in [0.1f32, 1.0, 0.0, 0.9, 0.0] {
1524            b.extend(v.to_le_bytes());
1525        }
1526        int(&mut b, version);
1527        b
1528    }
1529
1530    /// A camera's fields are read from where the layout puts them, the stack's length moving what follows; another
1531    /// version and a length past the layout are refused.
1532    #[test]
1533    fn a_urp_camera_is_read_by_the_verified_layout() {
1534        assert_eq!(camera(0, 2).len(), 148);
1535        let c = read_urp_camera(&camera(2, 2)).unwrap();
1536        let stack: Vec<i64> = c.stack.iter().map(|p| p.path_id).collect();
1537        assert_eq!(stack, vec![100, 101]);
1538        assert_eq!(
1539            (
1540                c.renderer_index,
1541                c.depth_texture_option,
1542                c.antialiasing,
1543                c.taa.quality
1544            ),
1545            (-1, 2, 3, 3)
1546        );
1547        assert!(c.render_shadows && c.render_post_processing && c.clear_depth && !c.stop_nan && c.dithering);
1548        assert_eq!(
1549            (c.volume_layer_mask, c.screen_coord_scale_bias),
1550            (0x8000_0000, [1.0, 1.0, 0.0, 0.0])
1551        );
1552        assert_eq!(c.taa.variance_clamp_scale, 0.9);
1553        assert!(
1554            read_urp_camera(&camera(0, 1))
1555                .unwrap_err()
1556                .to_string()
1557                .contains("m_Version 1")
1558        );
1559        let mut longer = camera(0, 2);
1560        longer.extend([0; 4]);
1561        assert!(read_urp_camera(&longer).is_err());
1562    }
1563}