refactor: consolidate render graph architecture
Amp-Thread-ID: https://ampcode.com/threads/T-019f9d91-77c1-7206-a60f-ed6554ce92ab Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
+1834
-982
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,414 @@
|
|||||||
|
use super::MeshFlag;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum SemanticType {
|
||||||
|
SurfaceTarget,
|
||||||
|
TextureSpec,
|
||||||
|
Texture,
|
||||||
|
SceneTable,
|
||||||
|
LocalAabbBuffer,
|
||||||
|
CameraFrustum,
|
||||||
|
BooleanFlagBuffer,
|
||||||
|
DrawStream,
|
||||||
|
DepthStencilConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum ExecutionClass {
|
||||||
|
Source,
|
||||||
|
CpuPreparation,
|
||||||
|
Compute,
|
||||||
|
Render,
|
||||||
|
Present,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum InputCardinality {
|
||||||
|
RequiredOne,
|
||||||
|
OptionalOne,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||||
|
#[serde(tag = "kind", content = "types", rename_all = "snake_case")]
|
||||||
|
pub enum TypeConstraint {
|
||||||
|
Exact(SemanticType),
|
||||||
|
OneOf(&'static [SemanticType]),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||||
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
pub enum InputRole {
|
||||||
|
SemanticRead,
|
||||||
|
UniformRead,
|
||||||
|
StorageRead,
|
||||||
|
IndirectRead,
|
||||||
|
SampledTexture,
|
||||||
|
ColorTarget { location: u32 },
|
||||||
|
DepthTarget,
|
||||||
|
Present,
|
||||||
|
Configuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||||
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
pub enum OutputMetadata {
|
||||||
|
None,
|
||||||
|
BooleanFlag { flag: MeshFlag },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct InputSocketContract {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub accepted: TypeConstraint,
|
||||||
|
pub cardinality: InputCardinality,
|
||||||
|
pub role: InputRole,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OutputSocketContract {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub semantic_type: SemanticType,
|
||||||
|
pub metadata: OutputMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Contract {
|
||||||
|
pub key: &'static str,
|
||||||
|
pub version: u32,
|
||||||
|
pub execution: ExecutionClass,
|
||||||
|
pub inputs: &'static [InputSocketContract],
|
||||||
|
pub outputs: &'static [OutputSocketContract],
|
||||||
|
pub inherently_observable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
use SemanticType::*;
|
||||||
|
|
||||||
|
const fn input(
|
||||||
|
name: &'static str,
|
||||||
|
accepted: TypeConstraint,
|
||||||
|
cardinality: InputCardinality,
|
||||||
|
role: InputRole,
|
||||||
|
) -> InputSocketContract {
|
||||||
|
InputSocketContract {
|
||||||
|
name,
|
||||||
|
accepted,
|
||||||
|
cardinality,
|
||||||
|
role,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn output(
|
||||||
|
name: &'static str,
|
||||||
|
semantic_type: SemanticType,
|
||||||
|
metadata: OutputMetadata,
|
||||||
|
) -> OutputSocketContract {
|
||||||
|
OutputSocketContract {
|
||||||
|
name,
|
||||||
|
semantic_type,
|
||||||
|
metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const REQUIRED: InputCardinality = InputCardinality::RequiredOne;
|
||||||
|
const OPTIONAL: InputCardinality = InputCardinality::OptionalOne;
|
||||||
|
const NONE_IN: &[InputSocketContract] = &[];
|
||||||
|
const NONE_OUT: &[OutputSocketContract] = &[];
|
||||||
|
const SURFACE_OUT: &[OutputSocketContract] =
|
||||||
|
&[output("surface", SurfaceTarget, OutputMetadata::None)];
|
||||||
|
const SPEC_OUT: &[OutputSocketContract] = &[output("spec", TextureSpec, OutputMetadata::None)];
|
||||||
|
const SCENE_OUT: &[OutputSocketContract] = &[output("scene", SceneTable, OutputMetadata::None)];
|
||||||
|
const AABB_OUT: &[OutputSocketContract] =
|
||||||
|
&[output("localAabbs", LocalAabbBuffer, OutputMetadata::None)];
|
||||||
|
const FRUSTUM_OUT: &[OutputSocketContract] =
|
||||||
|
&[output("frustum", CameraFrustum, OutputMetadata::None)];
|
||||||
|
const VISIBLE_OUT: &[OutputSocketContract] = &[output(
|
||||||
|
"flags",
|
||||||
|
BooleanFlagBuffer,
|
||||||
|
OutputMetadata::BooleanFlag {
|
||||||
|
flag: MeshFlag::IsVisible,
|
||||||
|
},
|
||||||
|
)];
|
||||||
|
const CULLED_OUT: &[OutputSocketContract] = &[output(
|
||||||
|
"flags",
|
||||||
|
BooleanFlagBuffer,
|
||||||
|
OutputMetadata::BooleanFlag {
|
||||||
|
flag: MeshFlag::IsFrustumCulled,
|
||||||
|
},
|
||||||
|
)];
|
||||||
|
const DRAW_OUT: &[OutputSocketContract] = &[output("draws", DrawStream, OutputMetadata::None)];
|
||||||
|
const CONFIG_OUT: &[OutputSocketContract] =
|
||||||
|
&[output("config", DepthStencilConfig, OutputMetadata::None)];
|
||||||
|
const FORWARD_OUT: &[OutputSocketContract] = &[
|
||||||
|
output("color", Texture, OutputMetadata::None),
|
||||||
|
output("depth", Texture, OutputMetadata::None),
|
||||||
|
];
|
||||||
|
const FULLSCREEN_COPY_OUT: &[OutputSocketContract] =
|
||||||
|
&[output("color", Texture, OutputMetadata::None)];
|
||||||
|
const LOCAL_IN: &[InputSocketContract] = &[input(
|
||||||
|
"scene",
|
||||||
|
TypeConstraint::Exact(SceneTable),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::SemanticRead,
|
||||||
|
)];
|
||||||
|
const VISIBILITY_IN: &[InputSocketContract] = LOCAL_IN;
|
||||||
|
const CULL_IN: &[InputSocketContract] = &[
|
||||||
|
input(
|
||||||
|
"scene",
|
||||||
|
TypeConstraint::Exact(SceneTable),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::StorageRead,
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"localAabbs",
|
||||||
|
TypeConstraint::Exact(LocalAabbBuffer),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::StorageRead,
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"frustum",
|
||||||
|
TypeConstraint::Exact(CameraFrustum),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::UniformRead,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const QUERY_IN: &[InputSocketContract] = &[
|
||||||
|
input(
|
||||||
|
"scene",
|
||||||
|
TypeConstraint::Exact(SceneTable),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::StorageRead,
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"isVisible",
|
||||||
|
TypeConstraint::Exact(BooleanFlagBuffer),
|
||||||
|
OPTIONAL,
|
||||||
|
InputRole::StorageRead,
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"isFrustumCulled",
|
||||||
|
TypeConstraint::Exact(BooleanFlagBuffer),
|
||||||
|
OPTIONAL,
|
||||||
|
InputRole::StorageRead,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const FORWARD_IN: &[InputSocketContract] = &[
|
||||||
|
input(
|
||||||
|
"scene",
|
||||||
|
TypeConstraint::Exact(SceneTable),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::SemanticRead,
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"draws",
|
||||||
|
TypeConstraint::Exact(DrawStream),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::IndirectRead,
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"colorTarget",
|
||||||
|
TypeConstraint::OneOf(&[SurfaceTarget, TextureSpec, Texture]),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::ColorTarget { location: 0 },
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"depthTarget",
|
||||||
|
TypeConstraint::OneOf(&[TextureSpec, Texture]),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::DepthTarget,
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"depthStencil",
|
||||||
|
TypeConstraint::Exact(DepthStencilConfig),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::Configuration,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const FULLSCREEN_COPY_IN: &[InputSocketContract] = &[
|
||||||
|
input(
|
||||||
|
"source",
|
||||||
|
TypeConstraint::Exact(Texture),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::SampledTexture,
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"colorTarget",
|
||||||
|
TypeConstraint::OneOf(&[SurfaceTarget, TextureSpec, Texture]),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::ColorTarget { location: 0 },
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const BLOOM_COMPOSITE_IN: &[InputSocketContract] = &[
|
||||||
|
input(
|
||||||
|
"source",
|
||||||
|
TypeConstraint::Exact(Texture),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::SampledTexture,
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"bloom",
|
||||||
|
TypeConstraint::Exact(Texture),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::SampledTexture,
|
||||||
|
),
|
||||||
|
input(
|
||||||
|
"colorTarget",
|
||||||
|
TypeConstraint::OneOf(&[SurfaceTarget, TextureSpec, Texture]),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::ColorTarget { location: 0 },
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const PRESENT_IN: &[InputSocketContract] = &[input(
|
||||||
|
"surface",
|
||||||
|
TypeConstraint::Exact(Texture),
|
||||||
|
REQUIRED,
|
||||||
|
InputRole::Present,
|
||||||
|
)];
|
||||||
|
|
||||||
|
pub static CONTRACTS: &[Contract] = &[
|
||||||
|
Contract {
|
||||||
|
key: "surface_target",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Source,
|
||||||
|
inputs: NONE_IN,
|
||||||
|
outputs: SURFACE_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "texture_spec",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Source,
|
||||||
|
inputs: NONE_IN,
|
||||||
|
outputs: SPEC_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "scene_table",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Source,
|
||||||
|
inputs: NONE_IN,
|
||||||
|
outputs: SCENE_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "local_aabb_buffer",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Source,
|
||||||
|
inputs: LOCAL_IN,
|
||||||
|
outputs: AABB_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "camera_frustum",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Source,
|
||||||
|
inputs: NONE_IN,
|
||||||
|
outputs: FRUSTUM_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "visibility_flags",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Source,
|
||||||
|
inputs: VISIBILITY_IN,
|
||||||
|
outputs: VISIBLE_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "frustum_cull",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Compute,
|
||||||
|
inputs: CULL_IN,
|
||||||
|
outputs: CULLED_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "mesh_query",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Compute,
|
||||||
|
inputs: QUERY_IN,
|
||||||
|
outputs: DRAW_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "depth_stencil_config",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Source,
|
||||||
|
inputs: NONE_IN,
|
||||||
|
outputs: CONFIG_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "legacy_forward",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Render,
|
||||||
|
inputs: FORWARD_IN,
|
||||||
|
outputs: FORWARD_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "fullscreen_copy",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Render,
|
||||||
|
inputs: FULLSCREEN_COPY_IN,
|
||||||
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "tone_map",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Render,
|
||||||
|
inputs: FULLSCREEN_COPY_IN,
|
||||||
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "bloom_extract",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Render,
|
||||||
|
inputs: FULLSCREEN_COPY_IN,
|
||||||
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "bloom_blur",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Render,
|
||||||
|
inputs: FULLSCREEN_COPY_IN,
|
||||||
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "bloom_composite",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Render,
|
||||||
|
inputs: BLOOM_COMPOSITE_IN,
|
||||||
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "luminance_edge",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Render,
|
||||||
|
inputs: FULLSCREEN_COPY_IN,
|
||||||
|
outputs: FULLSCREEN_COPY_OUT,
|
||||||
|
inherently_observable: false,
|
||||||
|
},
|
||||||
|
Contract {
|
||||||
|
key: "present",
|
||||||
|
version: 1,
|
||||||
|
execution: ExecutionClass::Present,
|
||||||
|
inputs: PRESENT_IN,
|
||||||
|
outputs: NONE_OUT,
|
||||||
|
inherently_observable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn contract(key: &str) -> Option<&'static Contract> {
|
||||||
|
CONTRACTS.iter().find(|contract| contract.key == key)
|
||||||
|
}
|
||||||
@@ -1,417 +0,0 @@
|
|||||||
use super::MeshFlagV2;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum SemanticTypeV2 {
|
|
||||||
SurfaceTarget,
|
|
||||||
TextureSpec,
|
|
||||||
Texture,
|
|
||||||
SceneTable,
|
|
||||||
LocalAabbBuffer,
|
|
||||||
CameraFrustum,
|
|
||||||
BooleanFlagBuffer,
|
|
||||||
DrawStream,
|
|
||||||
DepthStencilConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum ExecutionClassV2 {
|
|
||||||
Source,
|
|
||||||
CpuPreparation,
|
|
||||||
Compute,
|
|
||||||
Render,
|
|
||||||
Present,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum InputCardinalityV2 {
|
|
||||||
RequiredOne,
|
|
||||||
OptionalOne,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
|
||||||
#[serde(tag = "kind", content = "types", rename_all = "snake_case")]
|
|
||||||
pub enum TypeConstraintV2 {
|
|
||||||
Exact(SemanticTypeV2),
|
|
||||||
OneOf(&'static [SemanticTypeV2]),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
||||||
pub enum InputRoleV2 {
|
|
||||||
SemanticRead,
|
|
||||||
UniformRead,
|
|
||||||
StorageRead,
|
|
||||||
IndirectRead,
|
|
||||||
SampledTexture,
|
|
||||||
ColorTarget { location: u32 },
|
|
||||||
DepthTarget,
|
|
||||||
Present,
|
|
||||||
Configuration,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
||||||
pub enum OutputMetadataV2 {
|
|
||||||
None,
|
|
||||||
BooleanFlag { flag: MeshFlagV2 },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, serde::Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct InputSocketContractV2 {
|
|
||||||
pub name: &'static str,
|
|
||||||
pub accepted: TypeConstraintV2,
|
|
||||||
pub cardinality: InputCardinalityV2,
|
|
||||||
pub role: InputRoleV2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, serde::Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct OutputSocketContractV2 {
|
|
||||||
pub name: &'static str,
|
|
||||||
pub semantic_type: SemanticTypeV2,
|
|
||||||
pub metadata: OutputMetadataV2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ContractV2 {
|
|
||||||
pub key: &'static str,
|
|
||||||
pub version: u32,
|
|
||||||
pub execution: ExecutionClassV2,
|
|
||||||
pub inputs: &'static [InputSocketContractV2],
|
|
||||||
pub outputs: &'static [OutputSocketContractV2],
|
|
||||||
pub inherently_observable: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
use SemanticTypeV2::*;
|
|
||||||
|
|
||||||
const fn input(
|
|
||||||
name: &'static str,
|
|
||||||
accepted: TypeConstraintV2,
|
|
||||||
cardinality: InputCardinalityV2,
|
|
||||||
role: InputRoleV2,
|
|
||||||
) -> InputSocketContractV2 {
|
|
||||||
InputSocketContractV2 {
|
|
||||||
name,
|
|
||||||
accepted,
|
|
||||||
cardinality,
|
|
||||||
role,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn output(
|
|
||||||
name: &'static str,
|
|
||||||
semantic_type: SemanticTypeV2,
|
|
||||||
metadata: OutputMetadataV2,
|
|
||||||
) -> OutputSocketContractV2 {
|
|
||||||
OutputSocketContractV2 {
|
|
||||||
name,
|
|
||||||
semantic_type,
|
|
||||||
metadata,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const REQUIRED: InputCardinalityV2 = InputCardinalityV2::RequiredOne;
|
|
||||||
const OPTIONAL: InputCardinalityV2 = InputCardinalityV2::OptionalOne;
|
|
||||||
const NONE_IN: &[InputSocketContractV2] = &[];
|
|
||||||
const NONE_OUT: &[OutputSocketContractV2] = &[];
|
|
||||||
const SURFACE_OUT: &[OutputSocketContractV2] =
|
|
||||||
&[output("surface", SurfaceTarget, OutputMetadataV2::None)];
|
|
||||||
const SPEC_OUT: &[OutputSocketContractV2] = &[output("spec", TextureSpec, OutputMetadataV2::None)];
|
|
||||||
const SCENE_OUT: &[OutputSocketContractV2] = &[output("scene", SceneTable, OutputMetadataV2::None)];
|
|
||||||
const AABB_OUT: &[OutputSocketContractV2] = &[output(
|
|
||||||
"localAabbs",
|
|
||||||
LocalAabbBuffer,
|
|
||||||
OutputMetadataV2::None,
|
|
||||||
)];
|
|
||||||
const FRUSTUM_OUT: &[OutputSocketContractV2] =
|
|
||||||
&[output("frustum", CameraFrustum, OutputMetadataV2::None)];
|
|
||||||
const VISIBLE_OUT: &[OutputSocketContractV2] = &[output(
|
|
||||||
"flags",
|
|
||||||
BooleanFlagBuffer,
|
|
||||||
OutputMetadataV2::BooleanFlag {
|
|
||||||
flag: MeshFlagV2::IsVisible,
|
|
||||||
},
|
|
||||||
)];
|
|
||||||
const CULLED_OUT: &[OutputSocketContractV2] = &[output(
|
|
||||||
"flags",
|
|
||||||
BooleanFlagBuffer,
|
|
||||||
OutputMetadataV2::BooleanFlag {
|
|
||||||
flag: MeshFlagV2::IsFrustumCulled,
|
|
||||||
},
|
|
||||||
)];
|
|
||||||
const DRAW_OUT: &[OutputSocketContractV2] = &[output("draws", DrawStream, OutputMetadataV2::None)];
|
|
||||||
const CONFIG_OUT: &[OutputSocketContractV2] =
|
|
||||||
&[output("config", DepthStencilConfig, OutputMetadataV2::None)];
|
|
||||||
const FORWARD_OUT: &[OutputSocketContractV2] = &[
|
|
||||||
output("color", Texture, OutputMetadataV2::None),
|
|
||||||
output("depth", Texture, OutputMetadataV2::None),
|
|
||||||
];
|
|
||||||
const FULLSCREEN_COPY_OUT: &[OutputSocketContractV2] =
|
|
||||||
&[output("color", Texture, OutputMetadataV2::None)];
|
|
||||||
const LOCAL_IN: &[InputSocketContractV2] = &[input(
|
|
||||||
"scene",
|
|
||||||
TypeConstraintV2::Exact(SceneTable),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::SemanticRead,
|
|
||||||
)];
|
|
||||||
const VISIBILITY_IN: &[InputSocketContractV2] = LOCAL_IN;
|
|
||||||
const CULL_IN: &[InputSocketContractV2] = &[
|
|
||||||
input(
|
|
||||||
"scene",
|
|
||||||
TypeConstraintV2::Exact(SceneTable),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::StorageRead,
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"localAabbs",
|
|
||||||
TypeConstraintV2::Exact(LocalAabbBuffer),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::StorageRead,
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"frustum",
|
|
||||||
TypeConstraintV2::Exact(CameraFrustum),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::UniformRead,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
const QUERY_IN: &[InputSocketContractV2] = &[
|
|
||||||
input(
|
|
||||||
"scene",
|
|
||||||
TypeConstraintV2::Exact(SceneTable),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::StorageRead,
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"isVisible",
|
|
||||||
TypeConstraintV2::Exact(BooleanFlagBuffer),
|
|
||||||
OPTIONAL,
|
|
||||||
InputRoleV2::StorageRead,
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"isFrustumCulled",
|
|
||||||
TypeConstraintV2::Exact(BooleanFlagBuffer),
|
|
||||||
OPTIONAL,
|
|
||||||
InputRoleV2::StorageRead,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
const FORWARD_IN: &[InputSocketContractV2] = &[
|
|
||||||
input(
|
|
||||||
"scene",
|
|
||||||
TypeConstraintV2::Exact(SceneTable),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::SemanticRead,
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"draws",
|
|
||||||
TypeConstraintV2::Exact(DrawStream),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::IndirectRead,
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"colorTarget",
|
|
||||||
TypeConstraintV2::OneOf(&[SurfaceTarget, TextureSpec, Texture]),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::ColorTarget { location: 0 },
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"depthTarget",
|
|
||||||
TypeConstraintV2::OneOf(&[TextureSpec, Texture]),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::DepthTarget,
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"depthStencil",
|
|
||||||
TypeConstraintV2::Exact(DepthStencilConfig),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::Configuration,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
const FULLSCREEN_COPY_IN: &[InputSocketContractV2] = &[
|
|
||||||
input(
|
|
||||||
"source",
|
|
||||||
TypeConstraintV2::Exact(Texture),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::SampledTexture,
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"colorTarget",
|
|
||||||
TypeConstraintV2::OneOf(&[SurfaceTarget, TextureSpec, Texture]),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::ColorTarget { location: 0 },
|
|
||||||
),
|
|
||||||
];
|
|
||||||
const BLOOM_COMPOSITE_IN: &[InputSocketContractV2] = &[
|
|
||||||
input(
|
|
||||||
"source",
|
|
||||||
TypeConstraintV2::Exact(Texture),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::SampledTexture,
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"bloom",
|
|
||||||
TypeConstraintV2::Exact(Texture),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::SampledTexture,
|
|
||||||
),
|
|
||||||
input(
|
|
||||||
"colorTarget",
|
|
||||||
TypeConstraintV2::OneOf(&[SurfaceTarget, TextureSpec, Texture]),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::ColorTarget { location: 0 },
|
|
||||||
),
|
|
||||||
];
|
|
||||||
const PRESENT_IN: &[InputSocketContractV2] = &[input(
|
|
||||||
"surface",
|
|
||||||
TypeConstraintV2::Exact(Texture),
|
|
||||||
REQUIRED,
|
|
||||||
InputRoleV2::Present,
|
|
||||||
)];
|
|
||||||
|
|
||||||
pub static CONTRACTS_V2: &[ContractV2] = &[
|
|
||||||
ContractV2 {
|
|
||||||
key: "surface_target",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Source,
|
|
||||||
inputs: NONE_IN,
|
|
||||||
outputs: SURFACE_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "texture_spec",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Source,
|
|
||||||
inputs: NONE_IN,
|
|
||||||
outputs: SPEC_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "scene_table",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Source,
|
|
||||||
inputs: NONE_IN,
|
|
||||||
outputs: SCENE_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "local_aabb_buffer",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Source,
|
|
||||||
inputs: LOCAL_IN,
|
|
||||||
outputs: AABB_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "camera_frustum",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Source,
|
|
||||||
inputs: NONE_IN,
|
|
||||||
outputs: FRUSTUM_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "visibility_flags",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Source,
|
|
||||||
inputs: VISIBILITY_IN,
|
|
||||||
outputs: VISIBLE_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "frustum_cull",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Compute,
|
|
||||||
inputs: CULL_IN,
|
|
||||||
outputs: CULLED_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "mesh_query",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Compute,
|
|
||||||
inputs: QUERY_IN,
|
|
||||||
outputs: DRAW_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "depth_stencil_config",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Source,
|
|
||||||
inputs: NONE_IN,
|
|
||||||
outputs: CONFIG_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "legacy_forward",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Render,
|
|
||||||
inputs: FORWARD_IN,
|
|
||||||
outputs: FORWARD_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "fullscreen_copy",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Render,
|
|
||||||
inputs: FULLSCREEN_COPY_IN,
|
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "tone_map",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Render,
|
|
||||||
inputs: FULLSCREEN_COPY_IN,
|
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "bloom_extract",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Render,
|
|
||||||
inputs: FULLSCREEN_COPY_IN,
|
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "bloom_blur",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Render,
|
|
||||||
inputs: FULLSCREEN_COPY_IN,
|
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "bloom_composite",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Render,
|
|
||||||
inputs: BLOOM_COMPOSITE_IN,
|
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "luminance_edge",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Render,
|
|
||||||
inputs: FULLSCREEN_COPY_IN,
|
|
||||||
outputs: FULLSCREEN_COPY_OUT,
|
|
||||||
inherently_observable: false,
|
|
||||||
},
|
|
||||||
ContractV2 {
|
|
||||||
key: "present",
|
|
||||||
version: 1,
|
|
||||||
execution: ExecutionClassV2::Present,
|
|
||||||
inputs: PRESENT_IN,
|
|
||||||
outputs: NONE_OUT,
|
|
||||||
inherently_observable: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn contract(key: &str) -> Option<&'static ContractV2> {
|
|
||||||
CONTRACTS_V2.iter().find(|contract| contract.key == key)
|
|
||||||
}
|
|
||||||
@@ -1,59 +1,21 @@
|
|||||||
//! Device-free V1 render graph compiler and compiled graph registry.
|
//! Device-free render graph compiler and compiled graph registry.
|
||||||
|
|
||||||
mod compiler;
|
mod compiler;
|
||||||
mod compiler_v2;
|
mod contracts;
|
||||||
mod contracts_v2;
|
mod plan;
|
||||||
mod plan_v2;
|
|
||||||
mod registry;
|
mod registry;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
mod runtime_v2;
|
|
||||||
mod schema;
|
mod schema;
|
||||||
mod schema_v2;
|
|
||||||
|
|
||||||
pub use compiler::{
|
pub use compiler::{compile, mesh_predicate_matches, parse_and_compile};
|
||||||
compile, compile_with, parse_and_compile, AllocationClass, CompiledGraph, CompiledOutput,
|
pub use contracts::*;
|
||||||
CompiledPass, CompiledRead, CompiledResource, CompiledWrite, ExecutorContract,
|
pub use plan::*;
|
||||||
ExecutorRegistry, ExecutorResolution, Lifetime, NormalizedParameters, SceneForwardExecutors,
|
pub use registry::{CompiledGraphId, Registry};
|
||||||
TextureAllocationKey, TextureUsage, TransientAllocation,
|
pub use runtime::*;
|
||||||
};
|
|
||||||
pub use compiler_v2::{compile_v2, mesh_predicate_matches, parse_and_compile_v2};
|
|
||||||
pub use contracts_v2::*;
|
|
||||||
pub use plan_v2::*;
|
|
||||||
pub use registry::{CompiledGraphId, RegisteredGraph, Registry};
|
|
||||||
pub use runtime::{
|
|
||||||
class_offsets, resolve_extent, runtime_texture_key, validate_activatable, ResolvedExtent,
|
|
||||||
RuntimeTextureKey,
|
|
||||||
};
|
|
||||||
pub use runtime_v2::*;
|
|
||||||
pub use schema::*;
|
pub use schema::*;
|
||||||
pub use schema_v2::*;
|
|
||||||
|
|
||||||
pub fn parse_and_compile_any(bytes: &[u8]) -> Result<RegisteredGraph, GraphError> {
|
|
||||||
if bytes.len() > MAX_JSON_BYTES {
|
|
||||||
return Err(GraphError::new(
|
|
||||||
"GRAPH_PAYLOAD_TOO_LARGE",
|
|
||||||
"graph payload exceeds 1 MiB",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let text = std::str::from_utf8(bytes)
|
|
||||||
.map_err(|_| GraphError::new("GRAPH_ENCODING_INVALID", "graph payload is not UTF-8"))?;
|
|
||||||
let value: serde_json::Value = serde_json::from_str(text)
|
|
||||||
.map_err(|e| GraphError::new("GRAPH_JSON_INVALID", e.to_string()))?;
|
|
||||||
match value.get("schemaVersion").and_then(|v| v.as_u64()) {
|
|
||||||
Some(1) => parse_and_compile(bytes).map(RegisteredGraph::V1),
|
|
||||||
Some(2) => parse_and_compile_v2(bytes).map(RegisteredGraph::V2),
|
|
||||||
_ => Err(GraphError::new(
|
|
||||||
"GRAPH_SCHEMA_UNSUPPORTED",
|
|
||||||
"schemaVersion must be exactly 1 or 2",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const MAX_JSON_BYTES: usize = 1024 * 1024;
|
pub const MAX_JSON_BYTES: usize = 1024 * 1024;
|
||||||
pub const MAX_RESOURCES: usize = 1024;
|
pub const MAX_EXECUTIONS: usize = 1024;
|
||||||
pub const MAX_PASSES: usize = 1024;
|
|
||||||
pub const MAX_USES: usize = 8192;
|
|
||||||
pub const MAX_OUTPUTS: usize = 64;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
|
||||||
pub struct GraphError {
|
pub struct GraphError {
|
||||||
@@ -71,6 +33,7 @@ impl GraphError {
|
|||||||
message,
|
message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn at(
|
pub(crate) fn at(
|
||||||
code: &'static str,
|
code: &'static str,
|
||||||
message: impl Into<String>,
|
message: impl Into<String>,
|
||||||
@@ -87,5 +50,3 @@ impl GraphError {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
#[cfg(test)]
|
|
||||||
mod tests_v2;
|
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ use super::*;
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompiledGraphV2 {
|
pub struct CompiledGraph {
|
||||||
pub schema_version: u32,
|
pub schema_version: u32,
|
||||||
pub graph_id: String,
|
pub graph_id: String,
|
||||||
pub revision: u32,
|
pub revision: u32,
|
||||||
pub node_count: u32,
|
pub node_count: u32,
|
||||||
pub resources: Vec<CompiledResourceV2>,
|
pub resources: Vec<CompiledResource>,
|
||||||
pub executions: Vec<CompiledExecutionV2>,
|
pub executions: Vec<CompiledExecution>,
|
||||||
pub texture_families: Vec<TextureFamilyV2>,
|
pub texture_families: Vec<TextureFamily>,
|
||||||
pub allocation_classes: Vec<AllocationClassV2>,
|
pub allocation_classes: Vec<AllocationClass>,
|
||||||
pub culled_node_count: u32,
|
pub culled_node_count: u32,
|
||||||
pub culled_resource_count: u32,
|
pub culled_resource_count: u32,
|
||||||
pub transient_slot_count: u32,
|
pub transient_slot_count: u32,
|
||||||
@@ -20,26 +20,26 @@ pub struct CompiledGraphV2 {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompiledResourceV2 {
|
pub struct CompiledResource {
|
||||||
pub original_node_index: u32,
|
pub original_node_index: u32,
|
||||||
pub output_ordinal: u16,
|
pub output_ordinal: u16,
|
||||||
pub origin: NodeOutputRef,
|
pub origin: NodeOutputRef,
|
||||||
pub semantic_type: SemanticTypeV2,
|
pub semantic_type: SemanticType,
|
||||||
pub producer_execution: Option<u32>,
|
pub producer_execution: Option<u32>,
|
||||||
pub lifetime: Option<LifetimeV2>,
|
pub lifetime: Option<Lifetime>,
|
||||||
pub plan: ResourcePlanV2,
|
pub plan: ResourcePlan,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum ResourcePlanV2 {
|
pub enum ResourcePlan {
|
||||||
SurfaceTarget {
|
SurfaceTarget {
|
||||||
family: u32,
|
family: u32,
|
||||||
},
|
},
|
||||||
TextureSpec {
|
TextureSpec {
|
||||||
family: u32,
|
family: u32,
|
||||||
residency: TextureResidencyV2,
|
residency: TextureResidency,
|
||||||
descriptor: NormalizedTextureDescriptorV2,
|
descriptor: NormalizedTextureDescriptor,
|
||||||
},
|
},
|
||||||
Texture {
|
Texture {
|
||||||
family: u32,
|
family: u32,
|
||||||
@@ -47,7 +47,7 @@ pub enum ResourcePlanV2 {
|
|||||||
target: u32,
|
target: u32,
|
||||||
initialized: bool,
|
initialized: bool,
|
||||||
stored: bool,
|
stored: bool,
|
||||||
allocation: Option<AllocationRefV2>,
|
allocation: Option<AllocationRef>,
|
||||||
},
|
},
|
||||||
SceneTable,
|
SceneTable,
|
||||||
LocalAabbBuffer {
|
LocalAabbBuffer {
|
||||||
@@ -56,53 +56,53 @@ pub enum ResourcePlanV2 {
|
|||||||
CameraFrustum,
|
CameraFrustum,
|
||||||
BooleanFlagBuffer {
|
BooleanFlagBuffer {
|
||||||
scene: u32,
|
scene: u32,
|
||||||
flag: MeshFlagV2,
|
flag: MeshFlag,
|
||||||
},
|
},
|
||||||
DrawStream {
|
DrawStream {
|
||||||
scene: u32,
|
scene: u32,
|
||||||
},
|
},
|
||||||
DepthStencilConfig {
|
DepthStencilConfig {
|
||||||
config: NormalizedDepthStencilV2,
|
config: NormalizedDepthStencil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompiledExecutionV2 {
|
pub struct CompiledExecution {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub original_node_index: u32,
|
pub original_node_index: u32,
|
||||||
pub executor: ExecutorRefV2,
|
pub executor: ExecutorRef,
|
||||||
pub parameters: NormalizedParametersV2,
|
pub parameters: NormalizedParameters,
|
||||||
pub kind: ExecutionKindV2,
|
pub kind: ExecutionKind,
|
||||||
pub inputs: Vec<CompiledSocketInputV2>,
|
pub inputs: Vec<CompiledSocketInput>,
|
||||||
pub outputs: Vec<CompiledSocketOutputV2>,
|
pub outputs: Vec<CompiledSocketOutput>,
|
||||||
pub accesses: Vec<CompiledAccessV2>,
|
pub accesses: Vec<CompiledAccess>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompiledSocketInputV2 {
|
pub struct CompiledSocketInput {
|
||||||
pub socket: String,
|
pub socket: String,
|
||||||
pub resource: u32,
|
pub resource: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompiledSocketOutputV2 {
|
pub struct CompiledSocketOutput {
|
||||||
pub socket: String,
|
pub socket: String,
|
||||||
pub resource: u32,
|
pub resource: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum ExecutionKindV2 {
|
pub enum ExecutionKind {
|
||||||
CpuPreparation,
|
CpuPreparation,
|
||||||
Compute {
|
Compute {
|
||||||
work: ComputeWorkV2,
|
work: ComputeWork,
|
||||||
},
|
},
|
||||||
Render {
|
Render {
|
||||||
color_attachments: Vec<ColorAttachmentPlanV2>,
|
color_attachments: Vec<ColorAttachmentPlan>,
|
||||||
depth_stencil: Option<DepthStencilAttachmentPlanV2>,
|
depth_stencil: Option<DepthStencilAttachmentPlan>,
|
||||||
},
|
},
|
||||||
Present {
|
Present {
|
||||||
surface: u32,
|
surface: u32,
|
||||||
@@ -111,60 +111,60 @@ pub enum ExecutionKindV2 {
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ComputeWorkV2 {
|
pub enum ComputeWork {
|
||||||
FrustumCull,
|
FrustumCull,
|
||||||
MeshQuery,
|
MeshQuery,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ColorAttachmentPlanV2 {
|
pub struct ColorAttachmentPlan {
|
||||||
pub resource: u32,
|
pub resource: u32,
|
||||||
pub location: u32,
|
pub location: u32,
|
||||||
pub load: NormalizedColorLoadV2,
|
pub load: NormalizedColorLoad,
|
||||||
pub store: StoreOpV2,
|
pub store: StoreOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DepthStencilAttachmentPlanV2 {
|
pub struct DepthStencilAttachmentPlan {
|
||||||
pub resource: u32,
|
pub resource: u32,
|
||||||
pub load: NormalizedDepthLoadV2,
|
pub load: NormalizedDepthLoad,
|
||||||
pub store: StoreOpV2,
|
pub store: StoreOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum NormalizedColorLoadV2 {
|
pub enum NormalizedColorLoad {
|
||||||
Load,
|
Load,
|
||||||
Clear { value: [f64; 4] },
|
Clear { value: [f64; 4] },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum NormalizedDepthLoadV2 {
|
pub enum NormalizedDepthLoad {
|
||||||
Load,
|
Load,
|
||||||
Clear { value: f32 },
|
Clear { value: f32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum StoreOpV2 {
|
pub enum StoreOp {
|
||||||
Store,
|
Store,
|
||||||
Discard,
|
Discard,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompiledAccessV2 {
|
pub struct CompiledAccess {
|
||||||
pub socket: String,
|
pub socket: String,
|
||||||
pub resource: u32,
|
pub resource: u32,
|
||||||
pub mode: AccessModeV2,
|
pub mode: AccessMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum AccessModeV2 {
|
pub enum AccessMode {
|
||||||
SemanticRead,
|
SemanticRead,
|
||||||
UniformRead,
|
UniformRead,
|
||||||
StorageRead,
|
StorageRead,
|
||||||
@@ -175,13 +175,13 @@ pub enum AccessModeV2 {
|
|||||||
SampledTexture,
|
SampledTexture,
|
||||||
ColorAttachment {
|
ColorAttachment {
|
||||||
location: u32,
|
location: u32,
|
||||||
load: NormalizedColorLoadV2,
|
load: NormalizedColorLoad,
|
||||||
store: StoreOpV2,
|
store: StoreOp,
|
||||||
full_overwrite: bool,
|
full_overwrite: bool,
|
||||||
},
|
},
|
||||||
DepthAttachment {
|
DepthAttachment {
|
||||||
load: NormalizedDepthLoadV2,
|
load: NormalizedDepthLoad,
|
||||||
store: StoreOpV2,
|
store: StoreOp,
|
||||||
full_overwrite: bool,
|
full_overwrite: bool,
|
||||||
},
|
},
|
||||||
Present,
|
Present,
|
||||||
@@ -189,11 +189,11 @@ pub enum AccessModeV2 {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum NormalizedParametersV2 {
|
pub enum NormalizedParameters {
|
||||||
SurfaceTarget,
|
SurfaceTarget,
|
||||||
TextureSpec {
|
TextureSpec {
|
||||||
residency: TextureResidencyV2,
|
residency: TextureResidency,
|
||||||
texture: NormalizedTextureDescriptorV2,
|
texture: NormalizedTextureDescriptor,
|
||||||
},
|
},
|
||||||
SceneTable,
|
SceneTable,
|
||||||
LocalAabbBuffer,
|
LocalAabbBuffer,
|
||||||
@@ -201,10 +201,10 @@ pub enum NormalizedParametersV2 {
|
|||||||
VisibilityFlags,
|
VisibilityFlags,
|
||||||
FrustumCull,
|
FrustumCull,
|
||||||
MeshQuery {
|
MeshQuery {
|
||||||
filters: [NormalizedMeshFilterV2; 2],
|
filters: [NormalizedMeshFilter; 2],
|
||||||
},
|
},
|
||||||
DepthStencilConfig {
|
DepthStencilConfig {
|
||||||
config: NormalizedDepthStencilV2,
|
config: NormalizedDepthStencil,
|
||||||
},
|
},
|
||||||
LegacyForward {
|
LegacyForward {
|
||||||
clear_color: [f64; 4],
|
clear_color: [f64; 4],
|
||||||
@@ -232,117 +232,117 @@ pub enum NormalizedParametersV2 {
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NormalizedMeshFilterV2 {
|
pub struct NormalizedMeshFilter {
|
||||||
pub flag: MeshFlagV2,
|
pub flag: MeshFlag,
|
||||||
pub predicate: TriStatePredicate,
|
pub predicate: TriStatePredicate,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NormalizedDepthStencilV2 {
|
pub struct NormalizedDepthStencil {
|
||||||
pub depth_compare: CompareFunctionV2,
|
pub depth_compare: CompareFunction,
|
||||||
pub depth_write_enabled: bool,
|
pub depth_write_enabled: bool,
|
||||||
pub clear_depth: f32,
|
pub clear_depth: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NormalizedTextureDescriptorV2 {
|
pub struct NormalizedTextureDescriptor {
|
||||||
pub dimension: TextureDimensionV2,
|
pub dimension: TextureDimension,
|
||||||
pub format: TextureFormatV2,
|
pub format: TextureFormat,
|
||||||
pub extent: NormalizedTextureExtentV2,
|
pub extent: NormalizedTextureExtent,
|
||||||
pub mip_level_count: u32,
|
pub mip_level_count: u32,
|
||||||
pub sample_count: u32,
|
pub sample_count: u32,
|
||||||
pub view_formats: Vec<TextureFormatV2>,
|
pub view_formats: Vec<TextureFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum NormalizedTextureExtentV2 {
|
pub enum NormalizedTextureExtent {
|
||||||
Absolute {
|
Absolute {
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
depth_or_array_layers: u32,
|
depth_or_array_layers: u32,
|
||||||
},
|
},
|
||||||
SurfaceRelative {
|
SurfaceRelative {
|
||||||
width: RatioV2,
|
width: Ratio,
|
||||||
height: RatioV2,
|
height: Ratio,
|
||||||
depth_or_array_layers: u32,
|
depth_or_array_layers: u32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct LifetimeV2 {
|
pub struct Lifetime {
|
||||||
pub first_use: u32,
|
pub first_use: u32,
|
||||||
pub last_use: u32,
|
pub last_use: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TextureFamilyKeyV2 {
|
pub struct TextureFamilyKey {
|
||||||
pub source_node: u32,
|
pub source_node: u32,
|
||||||
pub source_socket: u16,
|
pub source_socket: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum TextureFamilySourceV2 {
|
pub enum TextureFamilySource {
|
||||||
ImportedSurface {
|
ImportedSurface {
|
||||||
resource: u32,
|
resource: u32,
|
||||||
},
|
},
|
||||||
AuthoredTexture {
|
AuthoredTexture {
|
||||||
resource: u32,
|
resource: u32,
|
||||||
residency: TextureResidencyV2,
|
residency: TextureResidency,
|
||||||
descriptor: NormalizedTextureDescriptorV2,
|
descriptor: NormalizedTextureDescriptor,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TextureFamilyV2 {
|
pub struct TextureFamily {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub key: TextureFamilyKeyV2,
|
pub key: TextureFamilyKey,
|
||||||
pub source: TextureFamilySourceV2,
|
pub source: TextureFamilySource,
|
||||||
pub lifetime: LifetimeV2,
|
pub lifetime: Lifetime,
|
||||||
pub versions: Vec<TextureVersionV2>,
|
pub versions: Vec<TextureVersion>,
|
||||||
pub usage: Vec<TextureUsageV2>,
|
pub usage: Vec<TextureUsage>,
|
||||||
pub allocation: Option<AllocationRefV2>,
|
pub allocation: Option<AllocationRef>,
|
||||||
pub aliasable: bool,
|
pub aliasable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TextureVersionV2 {
|
pub struct TextureVersion {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub resource: u32,
|
pub resource: u32,
|
||||||
pub target: u32,
|
pub target: u32,
|
||||||
pub initialized: bool,
|
pub initialized: bool,
|
||||||
pub stored: bool,
|
pub stored: bool,
|
||||||
pub lifetime: LifetimeV2,
|
pub lifetime: Lifetime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TextureCompatibilityKeyV2 {
|
pub struct TextureCompatibilityKey {
|
||||||
pub dimension: TextureDimensionV2,
|
pub dimension: TextureDimension,
|
||||||
pub format: TextureFormatV2,
|
pub format: TextureFormat,
|
||||||
pub extent: NormalizedTextureExtentV2,
|
pub extent: NormalizedTextureExtent,
|
||||||
pub mip_level_count: u32,
|
pub mip_level_count: u32,
|
||||||
pub sample_count: u32,
|
pub sample_count: u32,
|
||||||
pub view_formats: Vec<TextureFormatV2>,
|
pub view_formats: Vec<TextureFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AllocationClassV2 {
|
pub struct AllocationClass {
|
||||||
pub key: TextureCompatibilityKeyV2,
|
pub key: TextureCompatibilityKey,
|
||||||
pub slots: Vec<AllocationSlotV2>,
|
pub slots: Vec<AllocationSlot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum AllocationKindV2 {
|
pub enum AllocationKind {
|
||||||
AliasedTransient,
|
AliasedTransient,
|
||||||
DedicatedTransient,
|
DedicatedTransient,
|
||||||
Persistent,
|
Persistent,
|
||||||
@@ -350,22 +350,22 @@ pub enum AllocationKindV2 {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AllocationSlotV2 {
|
pub struct AllocationSlot {
|
||||||
pub kind: AllocationKindV2,
|
pub kind: AllocationKind,
|
||||||
pub usage: Vec<TextureUsageV2>,
|
pub usage: Vec<TextureUsage>,
|
||||||
pub occupants: Vec<u32>,
|
pub occupants: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AllocationRefV2 {
|
pub struct AllocationRef {
|
||||||
pub class: u32,
|
pub class: u32,
|
||||||
pub slot: u32,
|
pub slot: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum TextureUsageV2 {
|
pub enum TextureUsage {
|
||||||
Sampled,
|
Sampled,
|
||||||
Storage,
|
Storage,
|
||||||
CopySrc,
|
CopySrc,
|
||||||
@@ -374,7 +374,7 @@ pub enum TextureUsageV2 {
|
|||||||
DepthAttachment,
|
DepthAttachment,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompiledGraphV2 {
|
impl CompiledGraph {
|
||||||
pub fn summary(&self, id: [u32; 2]) -> serde_json::Value {
|
pub fn summary(&self, id: [u32; 2]) -> serde_json::Value {
|
||||||
serde_json::json!({"compiledId":id,"graphId":self.graph_id,"revision":self.revision,"schemaVersion":self.schema_version,"nodeCount":self.node_count,"executionCount":self.executions.len(),"resourceCount":self.resources.len(),"culledNodeCount":self.culled_node_count,"culledResourceCount":self.culled_resource_count,"transientSlotCount":self.transient_slot_count})
|
serde_json::json!({"compiledId":id,"graphId":self.graph_id,"revision":self.revision,"schemaVersion":self.schema_version,"nodeCount":self.node_count,"executionCount":self.executions.len(),"resourceCount":self.resources.len(),"culledNodeCount":self.culled_node_count,"culledResourceCount":self.culled_resource_count,"transientSlotCount":self.transient_slot_count})
|
||||||
}
|
}
|
||||||
@@ -1,51 +1,39 @@
|
|||||||
use super::{parse_and_compile_any, CompiledGraph, CompiledGraphV2, GraphError};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum RegisteredGraph {
|
use super::{parse_and_compile, CompiledGraph, GraphError};
|
||||||
V1(CompiledGraph),
|
|
||||||
V2(CompiledGraphV2),
|
|
||||||
}
|
|
||||||
impl RegisteredGraph {
|
|
||||||
fn identity(&self) -> (&str, u32) {
|
|
||||||
match self {
|
|
||||||
Self::V1(g) => (g.graph_id.as_str(), g.revision),
|
|
||||||
Self::V2(g) => (g.graph_id.as_str(), g.revision),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn summary(&self, id: [u32; 2]) -> serde_json::Value {
|
|
||||||
match self {
|
|
||||||
Self::V1(g) => g.summary(id),
|
|
||||||
Self::V2(g) => g.summary(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct CompiledGraphId {
|
pub struct CompiledGraphId {
|
||||||
pub slot: u32,
|
pub slot: u32,
|
||||||
pub generation: u32,
|
pub generation: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CompiledGraphId> for [u32; 2] {
|
impl From<CompiledGraphId> for [u32; 2] {
|
||||||
fn from(x: CompiledGraphId) -> Self {
|
fn from(id: CompiledGraphId) -> Self {
|
||||||
[x.slot, x.generation]
|
[id.slot, id.generation]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Slot {
|
struct Slot {
|
||||||
generation: u32,
|
generation: u32,
|
||||||
value: Option<RegisteredGraph>,
|
value: Option<CompiledGraph>,
|
||||||
retired: bool,
|
retired: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Registry {
|
pub struct Registry {
|
||||||
slots: Vec<Slot>,
|
slots: Vec<Slot>,
|
||||||
capacity: u32,
|
capacity: u32,
|
||||||
latest_revisions: HashMap<String, u32>,
|
latest_revisions: HashMap<String, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Registry {
|
impl Default for Registry {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(16)
|
Self::new(16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registry {
|
impl Registry {
|
||||||
pub fn new(capacity: u32) -> Self {
|
pub fn new(capacity: u32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -54,28 +42,28 @@ impl Registry {
|
|||||||
latest_revisions: HashMap::new(),
|
latest_revisions: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile(
|
pub fn compile(
|
||||||
&mut self,
|
&mut self,
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
) -> Result<(CompiledGraphId, serde_json::Value), GraphError> {
|
) -> Result<(CompiledGraphId, serde_json::Value), GraphError> {
|
||||||
let graph = parse_and_compile_any(bytes)?;
|
let graph = parse_and_compile(bytes)?;
|
||||||
let (graph_id, revision) = graph.identity();
|
|
||||||
if self
|
if self
|
||||||
.latest_revisions
|
.latest_revisions
|
||||||
.get(graph_id)
|
.get(&graph.graph_id)
|
||||||
.is_some_and(|latest| revision <= *latest)
|
.is_some_and(|latest| graph.revision <= *latest)
|
||||||
{
|
{
|
||||||
return Err(GraphError::new(
|
return Err(GraphError::new(
|
||||||
"GRAPH_REVISION_CONFLICT",
|
"GRAPH_REVISION_CONFLICT",
|
||||||
"revision must increase",
|
"revision must increase",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let i = if let Some(i) = self
|
let index = if let Some(index) = self
|
||||||
.slots
|
.slots
|
||||||
.iter()
|
.iter()
|
||||||
.position(|s| s.value.is_none() && !s.retired)
|
.position(|slot| slot.value.is_none() && !slot.retired)
|
||||||
{
|
{
|
||||||
i
|
index
|
||||||
} else {
|
} else {
|
||||||
if u32::try_from(self.slots.len()).map_or(true, |len| len >= self.capacity) {
|
if u32::try_from(self.slots.len()).map_or(true, |len| len >= self.capacity) {
|
||||||
return Err(GraphError::new(
|
return Err(GraphError::new(
|
||||||
@@ -91,51 +79,40 @@ impl Registry {
|
|||||||
self.slots.len() - 1
|
self.slots.len() - 1
|
||||||
};
|
};
|
||||||
let id = CompiledGraphId {
|
let id = CompiledGraphId {
|
||||||
slot: u32::try_from(i)
|
slot: u32::try_from(index)
|
||||||
.map_err(|_| GraphError::new("GRAPH_LIMIT_EXCEEDED", "registry slot overflow"))?,
|
.map_err(|_| GraphError::new("GRAPH_LIMIT_EXCEEDED", "registry slot overflow"))?,
|
||||||
generation: self.slots[i].generation,
|
generation: self.slots[index].generation,
|
||||||
};
|
};
|
||||||
let summary = graph.summary(id.into());
|
let summary = graph.summary(id.into());
|
||||||
self.latest_revisions.insert(graph_id.to_owned(), revision);
|
self.latest_revisions
|
||||||
self.slots[i].value = Some(graph);
|
.insert(graph.graph_id.clone(), graph.revision);
|
||||||
|
self.slots[index].value = Some(graph);
|
||||||
Ok((id, summary))
|
Ok((id, summary))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: CompiledGraphId) -> Result<&CompiledGraph, GraphError> {
|
pub fn get(&self, id: CompiledGraphId) -> Result<&CompiledGraph, GraphError> {
|
||||||
match self
|
|
||||||
.slots
|
|
||||||
.get(id.slot as usize)
|
|
||||||
.filter(|s| s.generation == id.generation)
|
|
||||||
.and_then(|s| s.value.as_ref())
|
|
||||||
.ok_or_else(|| GraphError::new("STALE_GRAPH_ID", "stale compiled graph id"))?
|
|
||||||
{
|
|
||||||
RegisteredGraph::V1(g) => Ok(g),
|
|
||||||
RegisteredGraph::V2(_) => Err(GraphError::new(
|
|
||||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
|
||||||
"schemaVersion 2 activation is unavailable until Phase 4",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_registered(&self, id: CompiledGraphId) -> Result<&RegisteredGraph, GraphError> {
|
|
||||||
self.slots
|
self.slots
|
||||||
.get(id.slot as usize)
|
.get(id.slot as usize)
|
||||||
.filter(|s| s.generation == id.generation)
|
.filter(|slot| slot.generation == id.generation)
|
||||||
.and_then(|s| s.value.as_ref())
|
.and_then(|slot| slot.value.as_ref())
|
||||||
.ok_or_else(|| GraphError::new("STALE_GRAPH_ID", "stale compiled graph id"))
|
.ok_or_else(|| GraphError::new("STALE_GRAPH_ID", "stale compiled graph id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(&self, id: CompiledGraphId) -> bool {
|
pub fn contains(&self, id: CompiledGraphId) -> bool {
|
||||||
self.get_registered(id).is_ok()
|
self.get(id).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drop_graph(&mut self, id: CompiledGraphId) -> Result<(), GraphError> {
|
pub fn drop_graph(&mut self, id: CompiledGraphId) -> Result<(), GraphError> {
|
||||||
let s = self
|
let slot = self
|
||||||
.slots
|
.slots
|
||||||
.get_mut(id.slot as usize)
|
.get_mut(id.slot as usize)
|
||||||
.filter(|s| s.generation == id.generation && s.value.is_some())
|
.filter(|slot| slot.generation == id.generation && slot.value.is_some())
|
||||||
.ok_or_else(|| GraphError::new("STALE_GRAPH_ID", "stale compiled graph id"))?;
|
.ok_or_else(|| GraphError::new("STALE_GRAPH_ID", "stale compiled graph id"))?;
|
||||||
s.value = None;
|
slot.value = None;
|
||||||
if s.generation == u32::MAX {
|
if slot.generation == u32::MAX {
|
||||||
s.retired = true
|
slot.retired = true
|
||||||
} else {
|
} else {
|
||||||
s.generation += 1
|
slot.generation += 1
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,215 +1,604 @@
|
|||||||
use std::collections::BTreeMap;
|
use super::*;
|
||||||
|
|
||||||
use super::{
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
CompiledGraph, Dimension, Extent, ExternalSource, Format, GraphError, Residency,
|
|
||||||
TextureAllocationKey, TextureUsage,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
pub struct ResolvedExtent {
|
pub struct ResolvedExtent {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub depth_or_array_layers: u32,
|
pub depth_or_array_layers: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct RuntimeTextureKey {
|
pub struct RuntimeTextureDescriptor {
|
||||||
pub dimension: Dimension,
|
pub dimension: wgpu::TextureDimension,
|
||||||
pub format: Format,
|
pub format: wgpu::TextureFormat,
|
||||||
pub extent: ResolvedExtent,
|
pub extent: ResolvedExtent,
|
||||||
pub mip_level_count: u32,
|
pub mip_level_count: u32,
|
||||||
pub sample_count: u32,
|
pub sample_count: u32,
|
||||||
pub usage: Vec<TextureUsage>,
|
pub usage: wgpu::TextureUsages,
|
||||||
pub view_formats: Vec<Format>,
|
pub view_formats: Vec<wgpu::TextureFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scaled(value: u32, numerator: u32, denominator: u32) -> Result<u32, GraphError> {
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
if denominator == 0 {
|
pub struct RuntimeSurfaceContract {
|
||||||
return Err(GraphError::new(
|
pub format: wgpu::TextureFormat,
|
||||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub usage: wgpu::TextureUsages,
|
||||||
|
pub view_formats: Vec<wgpu::TextureFormat>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RuntimeAllocationSlot {
|
||||||
|
pub kind: AllocationKind,
|
||||||
|
pub descriptor: RuntimeTextureDescriptor,
|
||||||
|
pub occupants: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RuntimeAllocationClass {
|
||||||
|
pub key: TextureCompatibilityKey,
|
||||||
|
pub slots: Vec<RuntimeAllocationSlot>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct MeshQueryRuntimeKey {
|
||||||
|
pub visible: TriStatePredicate,
|
||||||
|
pub frustum_culled: TriStatePredicate,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RuntimeExecution {
|
||||||
|
pub execution: u32,
|
||||||
|
pub executor: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RuntimeAllocationPlan {
|
||||||
|
pub classes: Vec<RuntimeAllocationClass>,
|
||||||
|
pub resource_allocations: Vec<Option<AllocationRef>>,
|
||||||
|
pub surface_family: u32,
|
||||||
|
pub surface_resource: u32,
|
||||||
|
pub query: MeshQueryRuntimeKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RuntimePlan {
|
||||||
|
pub allocations: RuntimeAllocationPlan,
|
||||||
|
pub executions: Vec<RuntimeExecution>,
|
||||||
|
pub surface: RuntimeSurfaceContract,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error(code: &'static str, message: impl Into<String>, path: impl Into<String>) -> GraphError {
|
||||||
|
GraphError::at(code, message, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn texture_dimension(value: TextureDimension) -> wgpu::TextureDimension {
|
||||||
|
match value {
|
||||||
|
TextureDimension::D1 => wgpu::TextureDimension::D1,
|
||||||
|
TextureDimension::D2 => wgpu::TextureDimension::D2,
|
||||||
|
TextureDimension::D3 => wgpu::TextureDimension::D3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn texture_format(value: TextureFormat) -> wgpu::TextureFormat {
|
||||||
|
match value {
|
||||||
|
TextureFormat::Rgba8Unorm => wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
TextureFormat::Rgba8UnormSrgb => wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
TextureFormat::Bgra8Unorm => wgpu::TextureFormat::Bgra8Unorm,
|
||||||
|
TextureFormat::Bgra8UnormSrgb => wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||||
|
TextureFormat::Rgba16Float => wgpu::TextureFormat::Rgba16Float,
|
||||||
|
TextureFormat::R32Float => wgpu::TextureFormat::R32Float,
|
||||||
|
TextureFormat::Depth32Float => wgpu::TextureFormat::Depth32Float,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn texture_usage(value: TextureUsage) -> wgpu::TextureUsages {
|
||||||
|
match value {
|
||||||
|
TextureUsage::Sampled => wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
TextureUsage::Storage => wgpu::TextureUsages::STORAGE_BINDING,
|
||||||
|
TextureUsage::CopySrc => wgpu::TextureUsages::COPY_SRC,
|
||||||
|
TextureUsage::CopyDst => wgpu::TextureUsages::COPY_DST,
|
||||||
|
TextureUsage::ColorAttachment | TextureUsage::DepthAttachment => {
|
||||||
|
wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn texture_usages(values: &[TextureUsage]) -> wgpu::TextureUsages {
|
||||||
|
values
|
||||||
|
.iter()
|
||||||
|
.fold(wgpu::TextureUsages::empty(), |usage, value| {
|
||||||
|
usage | texture_usage(*value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scaled(value: u32, ratio: Ratio, path: &str) -> Result<u32, GraphError> {
|
||||||
|
if ratio.denominator == 0 {
|
||||||
|
return Err(error(
|
||||||
|
"GRAPH_RESOURCE_LIMIT",
|
||||||
"zero extent denominator",
|
"zero extent denominator",
|
||||||
|
path,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let product = u64::from(value)
|
let product = u64::from(value)
|
||||||
.checked_mul(u64::from(numerator))
|
.checked_mul(u64::from(ratio.numerator))
|
||||||
.ok_or_else(|| GraphError::new("GRAPH_EXECUTION_UNSUPPORTED", "extent overflow"))?;
|
.ok_or_else(|| error("GRAPH_RESOURCE_LIMIT", "extent arithmetic overflow", path))?;
|
||||||
let result = product
|
let result = product
|
||||||
.checked_add(u64::from(denominator) - 1)
|
.checked_add(u64::from(ratio.denominator) - 1)
|
||||||
.ok_or_else(|| GraphError::new("GRAPH_EXECUTION_UNSUPPORTED", "extent overflow"))?
|
.ok_or_else(|| error("GRAPH_RESOURCE_LIMIT", "extent arithmetic overflow", path))?
|
||||||
/ u64::from(denominator);
|
/ u64::from(ratio.denominator);
|
||||||
u32::try_from(result.max(1))
|
u32::try_from(result.max(1))
|
||||||
.map_err(|_| GraphError::new("GRAPH_EXECUTION_UNSUPPORTED", "extent overflow"))
|
.map_err(|_| error("GRAPH_RESOURCE_LIMIT", "extent exceeds u32", path))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_extent(extent: &Extent, surface: [u32; 2]) -> Result<ResolvedExtent, GraphError> {
|
pub fn resolve_extent(
|
||||||
let (width, height, depth_or_array_layers) = match extent {
|
extent: &NormalizedTextureExtent,
|
||||||
Extent::Absolute {
|
surface: [u32; 2],
|
||||||
|
) -> Result<ResolvedExtent, GraphError> {
|
||||||
|
let resolved = match extent {
|
||||||
|
NormalizedTextureExtent::Absolute {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
depth_or_array_layers,
|
depth_or_array_layers,
|
||||||
} => (*width, *height, *depth_or_array_layers),
|
} => ResolvedExtent {
|
||||||
Extent::SurfaceRelative {
|
width: *width,
|
||||||
|
height: *height,
|
||||||
|
depth_or_array_layers: *depth_or_array_layers,
|
||||||
|
},
|
||||||
|
NormalizedTextureExtent::SurfaceRelative {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
depth_or_array_layers,
|
depth_or_array_layers,
|
||||||
} => (
|
} => ResolvedExtent {
|
||||||
scaled(surface[0], width.numerator, width.denominator)?,
|
width: scaled(surface[0], *width, "extent.width")?,
|
||||||
scaled(surface[1], height.numerator, height.denominator)?,
|
height: scaled(surface[1], *height, "extent.height")?,
|
||||||
*depth_or_array_layers,
|
depth_or_array_layers: *depth_or_array_layers,
|
||||||
),
|
},
|
||||||
};
|
};
|
||||||
if width == 0 || height == 0 || depth_or_array_layers == 0 {
|
if resolved.width == 0 || resolved.height == 0 || resolved.depth_or_array_layers == 0 {
|
||||||
return Err(GraphError::new(
|
return Err(error(
|
||||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
"GRAPH_RESOURCE_LIMIT",
|
||||||
"texture extent must be nonzero",
|
"texture extent is zero",
|
||||||
|
"extent",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(ResolvedExtent {
|
Ok(resolved)
|
||||||
width,
|
|
||||||
height,
|
|
||||||
depth_or_array_layers,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn runtime_texture_key(
|
pub fn resolved_mip_level_count(extent: ResolvedExtent) -> u32 {
|
||||||
key: &TextureAllocationKey,
|
32 - extent
|
||||||
surface: [u32; 2],
|
.width
|
||||||
) -> Result<RuntimeTextureKey, GraphError> {
|
.max(extent.height)
|
||||||
Ok(RuntimeTextureKey {
|
.max(extent.depth_or_array_layers)
|
||||||
dimension: key.descriptor.dimension,
|
.leading_zeros()
|
||||||
format: key.descriptor.format,
|
|
||||||
extent: resolve_extent(&key.descriptor.extent, surface)?,
|
|
||||||
mip_level_count: key.descriptor.mip_level_count,
|
|
||||||
sample_count: key.descriptor.sample_count,
|
|
||||||
usage: key.usage.clone(),
|
|
||||||
view_formats: key.view_formats.clone(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assigns disjoint physical ranges after merging symbolic allocation classes that
|
fn validate_limits(
|
||||||
/// resolve to the same concrete descriptor key.
|
dimension: TextureDimension,
|
||||||
pub fn class_offsets(
|
extent: ResolvedExtent,
|
||||||
classes: &[(TextureAllocationKey, u32)],
|
mip_count: u32,
|
||||||
surface: [u32; 2],
|
limits: Option<&wgpu::Limits>,
|
||||||
) -> Result<Vec<u32>, GraphError> {
|
path: &str,
|
||||||
let mut next = BTreeMap::new();
|
) -> Result<(), GraphError> {
|
||||||
let mut offsets = Vec::with_capacity(classes.len());
|
let max_mips = resolved_mip_level_count(extent);
|
||||||
for (key, count) in classes {
|
if mip_count == 0 || mip_count > max_mips {
|
||||||
let concrete = runtime_texture_key(key, surface)?;
|
return Err(error(
|
||||||
let offset = next.entry(concrete).or_insert(0u32);
|
"GRAPH_RESOURCE_LIMIT",
|
||||||
offsets.push(*offset);
|
"invalid mip level count",
|
||||||
*offset = offset.checked_add(*count).ok_or_else(|| {
|
path,
|
||||||
GraphError::new("GRAPH_EXECUTION_UNSUPPORTED", "transient slot overflow")
|
));
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
Ok(offsets)
|
if let Some(l) = limits {
|
||||||
|
let valid = match dimension {
|
||||||
|
TextureDimension::D1 => extent.width <= l.max_texture_dimension_1d,
|
||||||
|
TextureDimension::D2 => {
|
||||||
|
extent.width <= l.max_texture_dimension_2d
|
||||||
|
&& extent.height <= l.max_texture_dimension_2d
|
||||||
|
&& extent.depth_or_array_layers <= l.max_texture_array_layers
|
||||||
|
}
|
||||||
|
TextureDimension::D3 => {
|
||||||
|
extent.width <= l.max_texture_dimension_3d
|
||||||
|
&& extent.height <= l.max_texture_dimension_3d
|
||||||
|
&& extent.depth_or_array_layers <= l.max_texture_dimension_3d
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_activatable(graph: &CompiledGraph) -> Result<(), GraphError> {
|
|
||||||
let unsupported = || {
|
|
||||||
GraphError::new(
|
|
||||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
|
||||||
"graph is outside the activatable Phase 6 subset",
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
if graph.passes.is_empty() || graph.outputs.is_empty() {
|
if !valid {
|
||||||
return Err(unsupported());
|
return Err(error(
|
||||||
}
|
"GRAPH_RESOURCE_LIMIT",
|
||||||
let surface_outputs = graph
|
"texture exceeds device limits",
|
||||||
.outputs
|
path,
|
||||||
.iter()
|
));
|
||||||
.filter(|o| {
|
|
||||||
matches!(
|
|
||||||
graph.resources[o.resource as usize].residency,
|
|
||||||
Residency::External {
|
|
||||||
source: ExternalSource::SurfaceColor
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.count();
|
|
||||||
if surface_outputs == 0 {
|
|
||||||
return Err(unsupported());
|
|
||||||
}
|
|
||||||
for pass in &graph.passes {
|
|
||||||
if pass.executor.key != "scene_forward"
|
|
||||||
|| pass.executor.version != 1
|
|
||||||
|| !pass.reads.is_empty()
|
|
||||||
{
|
|
||||||
return Err(unsupported());
|
|
||||||
}
|
|
||||||
let color = pass
|
|
||||||
.writes
|
|
||||||
.iter()
|
|
||||||
.find(|w| w.binding == "color")
|
|
||||||
.ok_or_else(&unsupported)?;
|
|
||||||
let depth = pass
|
|
||||||
.writes
|
|
||||||
.iter()
|
|
||||||
.find(|w| w.binding == "depth")
|
|
||||||
.ok_or_else(&unsupported)?;
|
|
||||||
let c = &graph.resources[color.resource as usize];
|
|
||||||
let d = &graph.resources[depth.resource as usize];
|
|
||||||
if !matches!(
|
|
||||||
c.residency,
|
|
||||||
Residency::External {
|
|
||||||
source: ExternalSource::SurfaceColor
|
|
||||||
}
|
|
||||||
) || !matches!(d.residency, Residency::Transient)
|
|
||||||
|| d.descriptor.format != Format::Depth32Float
|
|
||||||
|| d.descriptor.dimension != Dimension::D2
|
|
||||||
|| d.descriptor.mip_level_count != 1
|
|
||||||
|| d.descriptor.sample_count != 1
|
|
||||||
|| d.descriptor.extent != c.descriptor.extent
|
|
||||||
{
|
|
||||||
return Err(unsupported());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
pub fn runtime_texture_descriptor(
|
||||||
mod tests {
|
key: &TextureCompatibilityKey,
|
||||||
use super::*;
|
usage: &[TextureUsage],
|
||||||
use crate::render_graph::{Dimension, Ratio, TextureDescriptor, TextureUsage};
|
surface: [u32; 2],
|
||||||
fn key(n: u32, d: u32) -> TextureAllocationKey {
|
limits: Option<&wgpu::Limits>,
|
||||||
TextureAllocationKey {
|
) -> Result<RuntimeTextureDescriptor, GraphError> {
|
||||||
descriptor: TextureDescriptor {
|
let extent = resolve_extent(&key.extent, surface)?;
|
||||||
dimension: Dimension::D2,
|
validate_limits(
|
||||||
format: Format::Depth32Float,
|
key.dimension,
|
||||||
extent: Extent::SurfaceRelative {
|
extent,
|
||||||
width: Ratio {
|
key.mip_level_count,
|
||||||
numerator: n,
|
limits,
|
||||||
denominator: d,
|
"allocationClasses.key",
|
||||||
},
|
)?;
|
||||||
height: Ratio {
|
Ok(RuntimeTextureDescriptor {
|
||||||
numerator: n,
|
dimension: texture_dimension(key.dimension),
|
||||||
denominator: d,
|
format: texture_format(key.format),
|
||||||
},
|
extent,
|
||||||
|
mip_level_count: key.mip_level_count,
|
||||||
|
sample_count: key.sample_count,
|
||||||
|
usage: texture_usages(usage),
|
||||||
|
view_formats: key
|
||||||
|
.view_formats
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(texture_format)
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid(message: impl Into<String>, path: impl Into<String>) -> GraphError {
|
||||||
|
error("GRAPH_RUNTIME_PLAN_INVALID", message, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_runtime_plan(
|
||||||
|
graph: &CompiledGraph,
|
||||||
|
surface: RuntimeSurfaceContract,
|
||||||
|
limits: Option<&wgpu::Limits>,
|
||||||
|
) -> Result<RuntimePlan, GraphError> {
|
||||||
|
if surface.width == 0 || surface.height == 0 {
|
||||||
|
return Err(error(
|
||||||
|
"GRAPH_SURFACE_INCOMPATIBLE",
|
||||||
|
"surface extent is zero",
|
||||||
|
"surface",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !surface
|
||||||
|
.usage
|
||||||
|
.contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
|
||||||
|
{
|
||||||
|
return Err(error(
|
||||||
|
"GRAPH_SURFACE_INCOMPATIBLE",
|
||||||
|
"surface lacks render attachment usage",
|
||||||
|
"surface.usage",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut present_count = 0;
|
||||||
|
let mut query = None;
|
||||||
|
let mut executions = Vec::with_capacity(graph.executions.len());
|
||||||
|
for (i, execution) in graph.executions.iter().enumerate() {
|
||||||
|
let path = format!("executions[{i}]");
|
||||||
|
match execution.executor.key.as_str() {
|
||||||
|
"mesh_query" => {
|
||||||
|
let NormalizedParameters::MeshQuery { filters } = &execution.parameters else {
|
||||||
|
return Err(invalid("mesh query parameters mismatch", &path));
|
||||||
|
};
|
||||||
|
let key = MeshQueryRuntimeKey {
|
||||||
|
visible: filters[0].predicate,
|
||||||
|
frustum_culled: filters[1].predicate,
|
||||||
|
};
|
||||||
|
if query.replace(key).is_some() {
|
||||||
|
return Err(error(
|
||||||
|
"GRAPH_EXECUTION_UNSUPPORTED",
|
||||||
|
"multiple draw stream queries",
|
||||||
|
&path,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"legacy_forward" => {}
|
||||||
|
"fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur" | "bloom_composite"
|
||||||
|
| "luminance_edge" => {}
|
||||||
|
"frustum_cull" => {}
|
||||||
|
"present" => present_count += 1,
|
||||||
|
_ => {
|
||||||
|
return Err(error(
|
||||||
|
"GRAPH_EXECUTION_UNSUPPORTED",
|
||||||
|
"unsupported execution",
|
||||||
|
&path,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
executions.push(RuntimeExecution {
|
||||||
|
execution: u32::try_from(i).map_err(|_| invalid("execution index overflow", &path))?,
|
||||||
|
executor: execution.executor.key.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if present_count != 1 {
|
||||||
|
return Err(error(
|
||||||
|
"GRAPH_EXECUTION_UNSUPPORTED",
|
||||||
|
"exactly one present is required",
|
||||||
|
"executions",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let query = query.ok_or_else(|| {
|
||||||
|
error(
|
||||||
|
"GRAPH_EXECUTION_UNSUPPORTED",
|
||||||
|
"one mesh query is required",
|
||||||
|
"executions",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut surface_pair = None;
|
||||||
|
let mut resource_allocations = vec![None; graph.resources.len()];
|
||||||
|
for (fi, family) in graph.texture_families.iter().enumerate() {
|
||||||
|
if family.id as usize != fi {
|
||||||
|
return Err(invalid(
|
||||||
|
"texture family id does not match index",
|
||||||
|
format!("textureFamilies[{fi}].id"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
match &family.source {
|
||||||
|
TextureFamilySource::ImportedSurface { resource } => {
|
||||||
|
if family.allocation.is_some()
|
||||||
|
|| surface_pair.replace((family.id, *resource)).is_some()
|
||||||
|
{
|
||||||
|
return Err(invalid(
|
||||||
|
"invalid imported surface allocation",
|
||||||
|
format!("textureFamilies[{fi}]"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextureFamilySource::AuthoredTexture {
|
||||||
|
residency,
|
||||||
|
descriptor,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if !matches!(
|
||||||
|
residency,
|
||||||
|
TextureResidency::Transient | TextureResidency::Persistent
|
||||||
|
) || descriptor.dimension != TextureDimension::D2
|
||||||
|
|| descriptor.mip_level_count != 1
|
||||||
|
|| descriptor.sample_count != 1
|
||||||
|
|| !matches!(
|
||||||
|
descriptor.extent,
|
||||||
|
NormalizedTextureExtent::Absolute {
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
},
|
..
|
||||||
mip_level_count: 1,
|
} | NormalizedTextureExtent::SurfaceRelative {
|
||||||
sample_count: 1,
|
depth_or_array_layers: 1,
|
||||||
},
|
..
|
||||||
usage: vec![TextureUsage::DepthAttachment],
|
}
|
||||||
view_formats: vec![],
|
)
|
||||||
|
{
|
||||||
|
return Err(error(
|
||||||
|
"GRAPH_EXECUTION_UNSUPPORTED",
|
||||||
|
"unsupported runtime texture descriptor",
|
||||||
|
format!("textureFamilies[{fi}]"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if family.allocation.is_none() {
|
||||||
|
return Err(invalid(
|
||||||
|
"authored family has no allocation",
|
||||||
|
format!("textureFamilies[{fi}].allocation"),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[test]
|
}
|
||||||
fn extent_uses_checked_ceil_and_minimum_one() {
|
for (vi, version) in family.versions.iter().enumerate() {
|
||||||
assert_eq!(
|
if version.version as usize != vi {
|
||||||
resolve_extent(&key(1, 2).descriptor.extent, [3, 1]).unwrap(),
|
return Err(invalid(
|
||||||
ResolvedExtent {
|
"texture version does not match index",
|
||||||
width: 2,
|
format!("textureFamilies[{fi}].versions[{vi}]"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let resource = graph
|
||||||
|
.resources
|
||||||
|
.get(version.resource as usize)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
invalid(
|
||||||
|
"version resource is out of bounds",
|
||||||
|
format!("textureFamilies[{fi}].versions[{vi}].resource"),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let ResourcePlan::Texture {
|
||||||
|
family: rf,
|
||||||
|
version: rv,
|
||||||
|
allocation,
|
||||||
|
..
|
||||||
|
} = &resource.plan
|
||||||
|
else {
|
||||||
|
return Err(invalid(
|
||||||
|
"version resource is not a texture",
|
||||||
|
format!("resources[{}].plan", version.resource),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if *rf != family.id || *rv != version.version || *allocation != family.allocation {
|
||||||
|
return Err(invalid(
|
||||||
|
"texture resource and family disagree",
|
||||||
|
format!("resources[{}].plan", version.resource),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
resource_allocations[version.resource as usize] = *allocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (surface_family, surface_resource) = surface_pair
|
||||||
|
.ok_or_else(|| invalid("missing imported surface family", "textureFamilies"))?;
|
||||||
|
|
||||||
|
// Validate the resource-to-family direction as well; compiled plans are public and may be
|
||||||
|
// cloned and modified by callers.
|
||||||
|
for (ri, resource) in graph.resources.iter().enumerate() {
|
||||||
|
if let ResourcePlan::Texture {
|
||||||
|
family,
|
||||||
|
version,
|
||||||
|
allocation,
|
||||||
|
..
|
||||||
|
} = resource.plan
|
||||||
|
{
|
||||||
|
let family_plan = graph.texture_families.get(family as usize).ok_or_else(|| {
|
||||||
|
invalid(
|
||||||
|
"texture resource family is out of bounds",
|
||||||
|
format!("resources[{ri}].plan.family"),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let family_version = family_plan.versions.get(version as usize).ok_or_else(|| {
|
||||||
|
invalid(
|
||||||
|
"texture resource version is out of bounds",
|
||||||
|
format!("resources[{ri}].plan.version"),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if family_version.resource as usize != ri || allocation != family_plan.allocation {
|
||||||
|
return Err(invalid(
|
||||||
|
"texture resource is inconsistent with its family",
|
||||||
|
format!("resources[{ri}].plan"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut classes = Vec::with_capacity(graph.allocation_classes.len());
|
||||||
|
for (ci, class) in graph.allocation_classes.iter().enumerate() {
|
||||||
|
let mut slots = Vec::with_capacity(class.slots.len());
|
||||||
|
for (si, slot) in class.slots.iter().enumerate() {
|
||||||
|
let allocation = AllocationRef {
|
||||||
|
class: ci as u32,
|
||||||
|
slot: si as u32,
|
||||||
|
};
|
||||||
|
for &family_id in &slot.occupants {
|
||||||
|
let family = graph
|
||||||
|
.texture_families
|
||||||
|
.get(family_id as usize)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
invalid(
|
||||||
|
"slot occupant is out of bounds",
|
||||||
|
format!("allocationClasses[{ci}].slots[{si}].occupants"),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if family.allocation != Some(allocation) {
|
||||||
|
return Err(invalid(
|
||||||
|
"slot occupant allocation disagrees",
|
||||||
|
format!("allocationClasses[{ci}].slots[{si}].occupants"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let TextureFamilySource::AuthoredTexture { descriptor, .. } = &family.source else {
|
||||||
|
return Err(invalid(
|
||||||
|
"imported family occupies a slot",
|
||||||
|
format!("allocationClasses[{ci}].slots[{si}]"),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if descriptor.dimension != class.key.dimension
|
||||||
|
|| descriptor.format != class.key.format
|
||||||
|
|| descriptor.extent != class.key.extent
|
||||||
|
|| descriptor.mip_level_count != class.key.mip_level_count
|
||||||
|
|| descriptor.sample_count != class.key.sample_count
|
||||||
|
|| descriptor.view_formats != class.key.view_formats
|
||||||
|
{
|
||||||
|
return Err(invalid(
|
||||||
|
"occupant descriptor does not match class key",
|
||||||
|
format!("allocationClasses[{ci}].key"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slots.push(RuntimeAllocationSlot {
|
||||||
|
kind: slot.kind,
|
||||||
|
descriptor: runtime_texture_descriptor(
|
||||||
|
&class.key,
|
||||||
|
&slot.usage,
|
||||||
|
[surface.width, surface.height],
|
||||||
|
limits,
|
||||||
|
)?,
|
||||||
|
occupants: slot.occupants.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
classes.push(RuntimeAllocationClass {
|
||||||
|
key: class.key.clone(),
|
||||||
|
slots,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (fi, family) in graph.texture_families.iter().enumerate() {
|
||||||
|
if let Some(allocation) = family.allocation {
|
||||||
|
let slot = graph
|
||||||
|
.allocation_classes
|
||||||
|
.get(allocation.class as usize)
|
||||||
|
.and_then(|c| c.slots.get(allocation.slot as usize))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
invalid(
|
||||||
|
"family allocation is out of bounds",
|
||||||
|
format!("textureFamilies[{fi}].allocation"),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if slot.occupants.iter().filter(|&&id| id == family.id).count() != 1 {
|
||||||
|
return Err(invalid(
|
||||||
|
"family is not exactly once in allocation occupants",
|
||||||
|
format!("textureFamilies[{fi}].allocation"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let imported = graph
|
||||||
|
.resources
|
||||||
|
.get(surface_resource as usize)
|
||||||
|
.ok_or_else(|| invalid("surface resource is out of bounds", "textureFamilies"))?;
|
||||||
|
if !matches!(imported.plan, ResourcePlan::SurfaceTarget { family } if family == surface_family)
|
||||||
|
{
|
||||||
|
return Err(invalid(
|
||||||
|
"surface source resource mismatch",
|
||||||
|
format!("resources[{surface_resource}]"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// The imported family may only flow through texture versions and the single present.
|
||||||
|
for (ri, resource) in graph.resources.iter().enumerate() {
|
||||||
|
if let ResourcePlan::Texture {
|
||||||
|
family, allocation, ..
|
||||||
|
} = resource.plan
|
||||||
|
{
|
||||||
|
if family == surface_family && allocation.is_some() {
|
||||||
|
return Err(invalid(
|
||||||
|
"surface texture has an allocation",
|
||||||
|
format!("resources[{ri}].plan"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let present = graph
|
||||||
|
.executions
|
||||||
|
.iter()
|
||||||
|
.find(|execution| execution.executor.key == "present")
|
||||||
|
.ok_or_else(|| invalid("present execution disappeared", "executions"))?;
|
||||||
|
let ExecutionKind::Present { surface: presented } = present.kind else {
|
||||||
|
return Err(invalid("present execution kind mismatch", "executions"));
|
||||||
|
};
|
||||||
|
let presented_resource = graph.resources.get(presented as usize).ok_or_else(|| {
|
||||||
|
invalid(
|
||||||
|
"present resource is out of bounds",
|
||||||
|
"executions.present.surface",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if !matches!(presented_resource.plan, ResourcePlan::Texture { family, .. } if family == surface_family)
|
||||||
|
{
|
||||||
|
return Err(error(
|
||||||
|
"GRAPH_SURFACE_INCOMPATIBLE",
|
||||||
|
"present does not resolve to the imported surface",
|
||||||
|
"executions.present.surface",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RuntimePlan {
|
||||||
|
allocations: RuntimeAllocationPlan {
|
||||||
|
classes,
|
||||||
|
resource_allocations,
|
||||||
|
surface_family,
|
||||||
|
surface_resource,
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
executions,
|
||||||
|
surface,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_activatable(graph: &CompiledGraph) -> Result<(), GraphError> {
|
||||||
|
let surface = RuntimeSurfaceContract {
|
||||||
|
format: wgpu::TextureFormat::Bgra8Unorm,
|
||||||
|
width: 1,
|
||||||
height: 1,
|
height: 1,
|
||||||
depth_or_array_layers: 1
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
}
|
view_formats: Vec::new(),
|
||||||
);
|
};
|
||||||
}
|
prepare_runtime_plan(graph, surface, None).map(|_| ())
|
||||||
#[test]
|
|
||||||
fn equivalent_symbolic_classes_are_disjoint() {
|
|
||||||
assert_eq!(
|
|
||||||
class_offsets(&[(key(1, 2), 2), (key(2, 4), 3)], [100, 100]).unwrap(),
|
|
||||||
vec![0, 2]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,605 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct ResolvedExtentV2 {
|
|
||||||
pub width: u32,
|
|
||||||
pub height: u32,
|
|
||||||
pub depth_or_array_layers: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct RuntimeTextureDescriptorV2 {
|
|
||||||
pub dimension: wgpu::TextureDimension,
|
|
||||||
pub format: wgpu::TextureFormat,
|
|
||||||
pub extent: ResolvedExtentV2,
|
|
||||||
pub mip_level_count: u32,
|
|
||||||
pub sample_count: u32,
|
|
||||||
pub usage: wgpu::TextureUsages,
|
|
||||||
pub view_formats: Vec<wgpu::TextureFormat>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct RuntimeSurfaceContractV2 {
|
|
||||||
pub format: wgpu::TextureFormat,
|
|
||||||
pub width: u32,
|
|
||||||
pub height: u32,
|
|
||||||
pub usage: wgpu::TextureUsages,
|
|
||||||
pub view_formats: Vec<wgpu::TextureFormat>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct RuntimeAllocationSlotV2 {
|
|
||||||
pub kind: AllocationKindV2,
|
|
||||||
pub descriptor: RuntimeTextureDescriptorV2,
|
|
||||||
pub occupants: Vec<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct RuntimeAllocationClassV2 {
|
|
||||||
pub key: TextureCompatibilityKeyV2,
|
|
||||||
pub slots: Vec<RuntimeAllocationSlotV2>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct MeshQueryRuntimeKeyV2 {
|
|
||||||
pub visible: TriStatePredicate,
|
|
||||||
pub frustum_culled: TriStatePredicate,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct RuntimeExecutionV2 {
|
|
||||||
pub execution: u32,
|
|
||||||
pub executor: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct RuntimeAllocationPlanV2 {
|
|
||||||
pub classes: Vec<RuntimeAllocationClassV2>,
|
|
||||||
pub resource_allocations: Vec<Option<AllocationRefV2>>,
|
|
||||||
pub surface_family: u32,
|
|
||||||
pub surface_resource: u32,
|
|
||||||
pub query: MeshQueryRuntimeKeyV2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct RuntimePlanV2 {
|
|
||||||
pub allocations: RuntimeAllocationPlanV2,
|
|
||||||
pub executions: Vec<RuntimeExecutionV2>,
|
|
||||||
pub surface: RuntimeSurfaceContractV2,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn error(code: &'static str, message: impl Into<String>, path: impl Into<String>) -> GraphError {
|
|
||||||
GraphError::at(code, message, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn texture_dimension_v2(value: TextureDimensionV2) -> wgpu::TextureDimension {
|
|
||||||
match value {
|
|
||||||
TextureDimensionV2::D1 => wgpu::TextureDimension::D1,
|
|
||||||
TextureDimensionV2::D2 => wgpu::TextureDimension::D2,
|
|
||||||
TextureDimensionV2::D3 => wgpu::TextureDimension::D3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn texture_format_v2(value: TextureFormatV2) -> wgpu::TextureFormat {
|
|
||||||
match value {
|
|
||||||
TextureFormatV2::Rgba8Unorm => wgpu::TextureFormat::Rgba8Unorm,
|
|
||||||
TextureFormatV2::Rgba8UnormSrgb => wgpu::TextureFormat::Rgba8UnormSrgb,
|
|
||||||
TextureFormatV2::Bgra8Unorm => wgpu::TextureFormat::Bgra8Unorm,
|
|
||||||
TextureFormatV2::Bgra8UnormSrgb => wgpu::TextureFormat::Bgra8UnormSrgb,
|
|
||||||
TextureFormatV2::Rgba16Float => wgpu::TextureFormat::Rgba16Float,
|
|
||||||
TextureFormatV2::R32Float => wgpu::TextureFormat::R32Float,
|
|
||||||
TextureFormatV2::Depth32Float => wgpu::TextureFormat::Depth32Float,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn texture_usage_v2(value: TextureUsageV2) -> wgpu::TextureUsages {
|
|
||||||
match value {
|
|
||||||
TextureUsageV2::Sampled => wgpu::TextureUsages::TEXTURE_BINDING,
|
|
||||||
TextureUsageV2::Storage => wgpu::TextureUsages::STORAGE_BINDING,
|
|
||||||
TextureUsageV2::CopySrc => wgpu::TextureUsages::COPY_SRC,
|
|
||||||
TextureUsageV2::CopyDst => wgpu::TextureUsages::COPY_DST,
|
|
||||||
TextureUsageV2::ColorAttachment | TextureUsageV2::DepthAttachment => {
|
|
||||||
wgpu::TextureUsages::RENDER_ATTACHMENT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn texture_usages_v2(values: &[TextureUsageV2]) -> wgpu::TextureUsages {
|
|
||||||
values
|
|
||||||
.iter()
|
|
||||||
.fold(wgpu::TextureUsages::empty(), |usage, value| {
|
|
||||||
usage | texture_usage_v2(*value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scaled(value: u32, ratio: RatioV2, path: &str) -> Result<u32, GraphError> {
|
|
||||||
if ratio.denominator == 0 {
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_RESOURCE_LIMIT",
|
|
||||||
"zero extent denominator",
|
|
||||||
path,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let product = u64::from(value)
|
|
||||||
.checked_mul(u64::from(ratio.numerator))
|
|
||||||
.ok_or_else(|| error("GRAPH_RESOURCE_LIMIT", "extent arithmetic overflow", path))?;
|
|
||||||
let result = product
|
|
||||||
.checked_add(u64::from(ratio.denominator) - 1)
|
|
||||||
.ok_or_else(|| error("GRAPH_RESOURCE_LIMIT", "extent arithmetic overflow", path))?
|
|
||||||
/ u64::from(ratio.denominator);
|
|
||||||
u32::try_from(result.max(1))
|
|
||||||
.map_err(|_| error("GRAPH_RESOURCE_LIMIT", "extent exceeds u32", path))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_extent_v2(
|
|
||||||
extent: &NormalizedTextureExtentV2,
|
|
||||||
surface: [u32; 2],
|
|
||||||
) -> Result<ResolvedExtentV2, GraphError> {
|
|
||||||
let resolved = match extent {
|
|
||||||
NormalizedTextureExtentV2::Absolute {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
depth_or_array_layers,
|
|
||||||
} => ResolvedExtentV2 {
|
|
||||||
width: *width,
|
|
||||||
height: *height,
|
|
||||||
depth_or_array_layers: *depth_or_array_layers,
|
|
||||||
},
|
|
||||||
NormalizedTextureExtentV2::SurfaceRelative {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
depth_or_array_layers,
|
|
||||||
} => ResolvedExtentV2 {
|
|
||||||
width: scaled(surface[0], *width, "extent.width")?,
|
|
||||||
height: scaled(surface[1], *height, "extent.height")?,
|
|
||||||
depth_or_array_layers: *depth_or_array_layers,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if resolved.width == 0 || resolved.height == 0 || resolved.depth_or_array_layers == 0 {
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_RESOURCE_LIMIT",
|
|
||||||
"texture extent is zero",
|
|
||||||
"extent",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(resolved)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolved_mip_level_count_v2(extent: ResolvedExtentV2) -> u32 {
|
|
||||||
32 - extent
|
|
||||||
.width
|
|
||||||
.max(extent.height)
|
|
||||||
.max(extent.depth_or_array_layers)
|
|
||||||
.leading_zeros()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_limits(
|
|
||||||
dimension: TextureDimensionV2,
|
|
||||||
extent: ResolvedExtentV2,
|
|
||||||
mip_count: u32,
|
|
||||||
limits: Option<&wgpu::Limits>,
|
|
||||||
path: &str,
|
|
||||||
) -> Result<(), GraphError> {
|
|
||||||
let max_mips = resolved_mip_level_count_v2(extent);
|
|
||||||
if mip_count == 0 || mip_count > max_mips {
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_RESOURCE_LIMIT",
|
|
||||||
"invalid mip level count",
|
|
||||||
path,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if let Some(l) = limits {
|
|
||||||
let valid = match dimension {
|
|
||||||
TextureDimensionV2::D1 => extent.width <= l.max_texture_dimension_1d,
|
|
||||||
TextureDimensionV2::D2 => {
|
|
||||||
extent.width <= l.max_texture_dimension_2d
|
|
||||||
&& extent.height <= l.max_texture_dimension_2d
|
|
||||||
&& extent.depth_or_array_layers <= l.max_texture_array_layers
|
|
||||||
}
|
|
||||||
TextureDimensionV2::D3 => {
|
|
||||||
extent.width <= l.max_texture_dimension_3d
|
|
||||||
&& extent.height <= l.max_texture_dimension_3d
|
|
||||||
&& extent.depth_or_array_layers <= l.max_texture_dimension_3d
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !valid {
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_RESOURCE_LIMIT",
|
|
||||||
"texture exceeds device limits",
|
|
||||||
path,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn runtime_texture_descriptor_v2(
|
|
||||||
key: &TextureCompatibilityKeyV2,
|
|
||||||
usage: &[TextureUsageV2],
|
|
||||||
surface: [u32; 2],
|
|
||||||
limits: Option<&wgpu::Limits>,
|
|
||||||
) -> Result<RuntimeTextureDescriptorV2, GraphError> {
|
|
||||||
let extent = resolve_extent_v2(&key.extent, surface)?;
|
|
||||||
validate_limits(
|
|
||||||
key.dimension,
|
|
||||||
extent,
|
|
||||||
key.mip_level_count,
|
|
||||||
limits,
|
|
||||||
"allocationClasses.key",
|
|
||||||
)?;
|
|
||||||
Ok(RuntimeTextureDescriptorV2 {
|
|
||||||
dimension: texture_dimension_v2(key.dimension),
|
|
||||||
format: texture_format_v2(key.format),
|
|
||||||
extent,
|
|
||||||
mip_level_count: key.mip_level_count,
|
|
||||||
sample_count: key.sample_count,
|
|
||||||
usage: texture_usages_v2(usage),
|
|
||||||
view_formats: key
|
|
||||||
.view_formats
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.map(texture_format_v2)
|
|
||||||
.collect(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invalid(message: impl Into<String>, path: impl Into<String>) -> GraphError {
|
|
||||||
error("GRAPH_RUNTIME_PLAN_INVALID", message, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prepare_runtime_plan_v2(
|
|
||||||
graph: &CompiledGraphV2,
|
|
||||||
surface: RuntimeSurfaceContractV2,
|
|
||||||
limits: Option<&wgpu::Limits>,
|
|
||||||
) -> Result<RuntimePlanV2, GraphError> {
|
|
||||||
if surface.width == 0 || surface.height == 0 {
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_SURFACE_INCOMPATIBLE",
|
|
||||||
"surface extent is zero",
|
|
||||||
"surface",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if !surface
|
|
||||||
.usage
|
|
||||||
.contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
|
|
||||||
{
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_SURFACE_INCOMPATIBLE",
|
|
||||||
"surface lacks render attachment usage",
|
|
||||||
"surface.usage",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut present_count = 0;
|
|
||||||
let mut query = None;
|
|
||||||
let mut executions = Vec::with_capacity(graph.executions.len());
|
|
||||||
for (i, execution) in graph.executions.iter().enumerate() {
|
|
||||||
let path = format!("executions[{i}]");
|
|
||||||
match execution.executor.key.as_str() {
|
|
||||||
"mesh_query" => {
|
|
||||||
let NormalizedParametersV2::MeshQuery { filters } = &execution.parameters else {
|
|
||||||
return Err(invalid("mesh query parameters mismatch", &path));
|
|
||||||
};
|
|
||||||
let key = MeshQueryRuntimeKeyV2 {
|
|
||||||
visible: filters[0].predicate,
|
|
||||||
frustum_culled: filters[1].predicate,
|
|
||||||
};
|
|
||||||
if query.replace(key).is_some() {
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
|
||||||
"multiple draw stream queries",
|
|
||||||
&path,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"legacy_forward" => {}
|
|
||||||
"fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur" | "bloom_composite"
|
|
||||||
| "luminance_edge" => {}
|
|
||||||
"frustum_cull" => {}
|
|
||||||
"present" => present_count += 1,
|
|
||||||
_ => {
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
|
||||||
"unsupported execution",
|
|
||||||
&path,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
executions.push(RuntimeExecutionV2 {
|
|
||||||
execution: u32::try_from(i).map_err(|_| invalid("execution index overflow", &path))?,
|
|
||||||
executor: execution.executor.key.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if present_count != 1 {
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
|
||||||
"exactly one present is required",
|
|
||||||
"executions",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let query = query.ok_or_else(|| {
|
|
||||||
error(
|
|
||||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
|
||||||
"one mesh query is required",
|
|
||||||
"executions",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut surface_pair = None;
|
|
||||||
let mut resource_allocations = vec![None; graph.resources.len()];
|
|
||||||
for (fi, family) in graph.texture_families.iter().enumerate() {
|
|
||||||
if family.id as usize != fi {
|
|
||||||
return Err(invalid(
|
|
||||||
"texture family id does not match index",
|
|
||||||
format!("textureFamilies[{fi}].id"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
match &family.source {
|
|
||||||
TextureFamilySourceV2::ImportedSurface { resource } => {
|
|
||||||
if family.allocation.is_some()
|
|
||||||
|| surface_pair.replace((family.id, *resource)).is_some()
|
|
||||||
{
|
|
||||||
return Err(invalid(
|
|
||||||
"invalid imported surface allocation",
|
|
||||||
format!("textureFamilies[{fi}]"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TextureFamilySourceV2::AuthoredTexture {
|
|
||||||
residency,
|
|
||||||
descriptor,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if !matches!(
|
|
||||||
residency,
|
|
||||||
TextureResidencyV2::Transient | TextureResidencyV2::Persistent
|
|
||||||
) || descriptor.dimension != TextureDimensionV2::D2
|
|
||||||
|| descriptor.mip_level_count != 1
|
|
||||||
|| descriptor.sample_count != 1
|
|
||||||
|| !matches!(
|
|
||||||
descriptor.extent,
|
|
||||||
NormalizedTextureExtentV2::Absolute {
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
..
|
|
||||||
} | NormalizedTextureExtentV2::SurfaceRelative {
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
)
|
|
||||||
{
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
|
||||||
"unsupported runtime texture descriptor",
|
|
||||||
format!("textureFamilies[{fi}]"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if family.allocation.is_none() {
|
|
||||||
return Err(invalid(
|
|
||||||
"authored family has no allocation",
|
|
||||||
format!("textureFamilies[{fi}].allocation"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (vi, version) in family.versions.iter().enumerate() {
|
|
||||||
if version.version as usize != vi {
|
|
||||||
return Err(invalid(
|
|
||||||
"texture version does not match index",
|
|
||||||
format!("textureFamilies[{fi}].versions[{vi}]"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let resource = graph
|
|
||||||
.resources
|
|
||||||
.get(version.resource as usize)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
invalid(
|
|
||||||
"version resource is out of bounds",
|
|
||||||
format!("textureFamilies[{fi}].versions[{vi}].resource"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let ResourcePlanV2::Texture {
|
|
||||||
family: rf,
|
|
||||||
version: rv,
|
|
||||||
allocation,
|
|
||||||
..
|
|
||||||
} = &resource.plan
|
|
||||||
else {
|
|
||||||
return Err(invalid(
|
|
||||||
"version resource is not a texture",
|
|
||||||
format!("resources[{}].plan", version.resource),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
if *rf != family.id || *rv != version.version || *allocation != family.allocation {
|
|
||||||
return Err(invalid(
|
|
||||||
"texture resource and family disagree",
|
|
||||||
format!("resources[{}].plan", version.resource),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
resource_allocations[version.resource as usize] = *allocation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let (surface_family, surface_resource) = surface_pair
|
|
||||||
.ok_or_else(|| invalid("missing imported surface family", "textureFamilies"))?;
|
|
||||||
|
|
||||||
// Validate the resource-to-family direction as well; compiled plans are public and may be
|
|
||||||
// cloned and modified by callers.
|
|
||||||
for (ri, resource) in graph.resources.iter().enumerate() {
|
|
||||||
if let ResourcePlanV2::Texture {
|
|
||||||
family,
|
|
||||||
version,
|
|
||||||
allocation,
|
|
||||||
..
|
|
||||||
} = resource.plan
|
|
||||||
{
|
|
||||||
let family_plan = graph.texture_families.get(family as usize).ok_or_else(|| {
|
|
||||||
invalid(
|
|
||||||
"texture resource family is out of bounds",
|
|
||||||
format!("resources[{ri}].plan.family"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let family_version = family_plan.versions.get(version as usize).ok_or_else(|| {
|
|
||||||
invalid(
|
|
||||||
"texture resource version is out of bounds",
|
|
||||||
format!("resources[{ri}].plan.version"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
if family_version.resource as usize != ri || allocation != family_plan.allocation {
|
|
||||||
return Err(invalid(
|
|
||||||
"texture resource is inconsistent with its family",
|
|
||||||
format!("resources[{ri}].plan"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut classes = Vec::with_capacity(graph.allocation_classes.len());
|
|
||||||
for (ci, class) in graph.allocation_classes.iter().enumerate() {
|
|
||||||
let mut slots = Vec::with_capacity(class.slots.len());
|
|
||||||
for (si, slot) in class.slots.iter().enumerate() {
|
|
||||||
let allocation = AllocationRefV2 {
|
|
||||||
class: ci as u32,
|
|
||||||
slot: si as u32,
|
|
||||||
};
|
|
||||||
for &family_id in &slot.occupants {
|
|
||||||
let family = graph
|
|
||||||
.texture_families
|
|
||||||
.get(family_id as usize)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
invalid(
|
|
||||||
"slot occupant is out of bounds",
|
|
||||||
format!("allocationClasses[{ci}].slots[{si}].occupants"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
if family.allocation != Some(allocation) {
|
|
||||||
return Err(invalid(
|
|
||||||
"slot occupant allocation disagrees",
|
|
||||||
format!("allocationClasses[{ci}].slots[{si}].occupants"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let TextureFamilySourceV2::AuthoredTexture { descriptor, .. } = &family.source
|
|
||||||
else {
|
|
||||||
return Err(invalid(
|
|
||||||
"imported family occupies a slot",
|
|
||||||
format!("allocationClasses[{ci}].slots[{si}]"),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
if descriptor.dimension != class.key.dimension
|
|
||||||
|| descriptor.format != class.key.format
|
|
||||||
|| descriptor.extent != class.key.extent
|
|
||||||
|| descriptor.mip_level_count != class.key.mip_level_count
|
|
||||||
|| descriptor.sample_count != class.key.sample_count
|
|
||||||
|| descriptor.view_formats != class.key.view_formats
|
|
||||||
{
|
|
||||||
return Err(invalid(
|
|
||||||
"occupant descriptor does not match class key",
|
|
||||||
format!("allocationClasses[{ci}].key"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
slots.push(RuntimeAllocationSlotV2 {
|
|
||||||
kind: slot.kind,
|
|
||||||
descriptor: runtime_texture_descriptor_v2(
|
|
||||||
&class.key,
|
|
||||||
&slot.usage,
|
|
||||||
[surface.width, surface.height],
|
|
||||||
limits,
|
|
||||||
)?,
|
|
||||||
occupants: slot.occupants.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
classes.push(RuntimeAllocationClassV2 {
|
|
||||||
key: class.key.clone(),
|
|
||||||
slots,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (fi, family) in graph.texture_families.iter().enumerate() {
|
|
||||||
if let Some(allocation) = family.allocation {
|
|
||||||
let slot = graph
|
|
||||||
.allocation_classes
|
|
||||||
.get(allocation.class as usize)
|
|
||||||
.and_then(|c| c.slots.get(allocation.slot as usize))
|
|
||||||
.ok_or_else(|| {
|
|
||||||
invalid(
|
|
||||||
"family allocation is out of bounds",
|
|
||||||
format!("textureFamilies[{fi}].allocation"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
if slot.occupants.iter().filter(|&&id| id == family.id).count() != 1 {
|
|
||||||
return Err(invalid(
|
|
||||||
"family is not exactly once in allocation occupants",
|
|
||||||
format!("textureFamilies[{fi}].allocation"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let imported = graph
|
|
||||||
.resources
|
|
||||||
.get(surface_resource as usize)
|
|
||||||
.ok_or_else(|| invalid("surface resource is out of bounds", "textureFamilies"))?;
|
|
||||||
if !matches!(imported.plan, ResourcePlanV2::SurfaceTarget { family } if family == surface_family)
|
|
||||||
{
|
|
||||||
return Err(invalid(
|
|
||||||
"surface source resource mismatch",
|
|
||||||
format!("resources[{surface_resource}]"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// The imported family may only flow through texture versions and the single present.
|
|
||||||
for (ri, resource) in graph.resources.iter().enumerate() {
|
|
||||||
if let ResourcePlanV2::Texture {
|
|
||||||
family, allocation, ..
|
|
||||||
} = resource.plan
|
|
||||||
{
|
|
||||||
if family == surface_family && allocation.is_some() {
|
|
||||||
return Err(invalid(
|
|
||||||
"surface texture has an allocation",
|
|
||||||
format!("resources[{ri}].plan"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let present = graph
|
|
||||||
.executions
|
|
||||||
.iter()
|
|
||||||
.find(|execution| execution.executor.key == "present")
|
|
||||||
.ok_or_else(|| invalid("present execution disappeared", "executions"))?;
|
|
||||||
let ExecutionKindV2::Present { surface: presented } = present.kind else {
|
|
||||||
return Err(invalid("present execution kind mismatch", "executions"));
|
|
||||||
};
|
|
||||||
let presented_resource = graph.resources.get(presented as usize).ok_or_else(|| {
|
|
||||||
invalid(
|
|
||||||
"present resource is out of bounds",
|
|
||||||
"executions.present.surface",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
if !matches!(presented_resource.plan, ResourcePlanV2::Texture { family, .. } if family == surface_family)
|
|
||||||
{
|
|
||||||
return Err(error(
|
|
||||||
"GRAPH_SURFACE_INCOMPATIBLE",
|
|
||||||
"present does not resolve to the imported surface",
|
|
||||||
"executions.present.surface",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RuntimePlanV2 {
|
|
||||||
allocations: RuntimeAllocationPlanV2 {
|
|
||||||
classes,
|
|
||||||
resource_allocations,
|
|
||||||
surface_family,
|
|
||||||
surface_resource,
|
|
||||||
query,
|
|
||||||
},
|
|
||||||
executions,
|
|
||||||
surface,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validate_activatable_v2(graph: &CompiledGraphV2) -> Result<(), GraphError> {
|
|
||||||
let surface = RuntimeSurfaceContractV2 {
|
|
||||||
format: wgpu::TextureFormat::Bgra8Unorm,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
||||||
view_formats: Vec::new(),
|
|
||||||
};
|
|
||||||
prepare_runtime_plan_v2(graph, surface, None).map(|_| ())
|
|
||||||
}
|
|
||||||
@@ -1,64 +1,58 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||||
pub struct GraphV1 {
|
pub struct Graph {
|
||||||
pub schema_version: u32,
|
pub schema_version: u32,
|
||||||
pub graph_id: String,
|
pub graph_id: String,
|
||||||
pub revision: u32,
|
pub revision: u32,
|
||||||
pub resources: Vec<Resource>,
|
pub nodes: Vec<Node>,
|
||||||
pub passes: Vec<Pass>,
|
|
||||||
pub outputs: Vec<Output>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
||||||
pub struct ResourceRef {
|
|
||||||
pub id: String,
|
|
||||||
pub version: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Resource {
|
pub struct Node {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub version: u32,
|
pub state: NodeState,
|
||||||
pub residency: Residency,
|
pub executor: ExecutorRef,
|
||||||
pub texture: TextureDescriptor,
|
pub parameters: serde_json::Value,
|
||||||
|
pub inputs: BTreeMap<String, NodeOutputRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum NodeState {
|
||||||
|
Enabled,
|
||||||
|
Muted,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub enum Residency {
|
pub struct ExecutorRef {
|
||||||
External { source: ExternalSource },
|
pub key: String,
|
||||||
Transient,
|
pub version: u32,
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum ExternalSource {
|
|
||||||
SurfaceColor,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct TextureDescriptor {
|
pub struct NodeOutputRef {
|
||||||
pub dimension: Dimension,
|
pub node: String,
|
||||||
pub format: Format,
|
pub socket: String,
|
||||||
pub extent: Extent,
|
|
||||||
pub mip_level_count: u32,
|
|
||||||
pub sample_count: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum Dimension {
|
pub enum TextureDimension {
|
||||||
D1,
|
D1,
|
||||||
D2,
|
D2,
|
||||||
D3,
|
D3,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum Format {
|
pub enum TextureFormat {
|
||||||
Surface,
|
|
||||||
Rgba8Unorm,
|
Rgba8Unorm,
|
||||||
Rgba8UnormSrgb,
|
Rgba8UnormSrgb,
|
||||||
Bgra8Unorm,
|
Bgra8Unorm,
|
||||||
@@ -67,15 +61,10 @@ pub enum Format {
|
|||||||
R32Float,
|
R32Float,
|
||||||
Depth32Float,
|
Depth32Float,
|
||||||
}
|
}
|
||||||
impl Format {
|
|
||||||
pub(crate) fn depth(self) -> bool {
|
|
||||||
self == Self::Depth32Float
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||||
pub enum Extent {
|
pub enum TextureExtent {
|
||||||
Absolute {
|
Absolute {
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
@@ -96,93 +85,75 @@ pub struct Ratio {
|
|||||||
pub denominator: u32,
|
pub denominator: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
||||||
pub struct Pass {
|
|
||||||
pub id: String,
|
|
||||||
pub state: PassState,
|
|
||||||
pub executor: ExecutorRef,
|
|
||||||
pub parameters: serde_json::Value,
|
|
||||||
pub reads: Vec<ReadBinding>,
|
|
||||||
pub writes: Vec<WriteBinding>,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum PassState {
|
|
||||||
Enabled,
|
|
||||||
Muted,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct ExecutorRef {
|
|
||||||
pub key: String,
|
|
||||||
pub version: u32,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct ReadBinding {
|
|
||||||
pub binding: String,
|
|
||||||
pub resource: ResourceRef,
|
|
||||||
pub access: ReadAccess,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ReadAccess {
|
pub enum TextureResidency {
|
||||||
Sampled,
|
Transient,
|
||||||
Storage,
|
Persistent,
|
||||||
CopySrc,
|
History,
|
||||||
}
|
Readback,
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct WriteBinding {
|
|
||||||
pub binding: String,
|
|
||||||
pub resource: ResourceRef,
|
|
||||||
pub access: WriteAccess,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
|
||||||
pub enum WriteAccess {
|
|
||||||
Storage,
|
|
||||||
CopyDst,
|
|
||||||
ColorAttachment {
|
|
||||||
location: u32,
|
|
||||||
load: ColorLoad,
|
|
||||||
store: StoreOp,
|
|
||||||
},
|
|
||||||
DepthAttachment {
|
|
||||||
load: DepthLoad,
|
|
||||||
store: StoreOp,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(tag = "op", rename_all = "snake_case", deny_unknown_fields)]
|
|
||||||
pub enum ColorLoad {
|
|
||||||
Clear { value: [f64; 4] },
|
|
||||||
Load,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(tag = "op", rename_all = "snake_case", deny_unknown_fields)]
|
|
||||||
pub enum DepthLoad {
|
|
||||||
Clear { value: f32 },
|
|
||||||
Load,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum StoreOp {
|
|
||||||
Store,
|
|
||||||
Discard,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct Output {
|
|
||||||
pub name: String,
|
|
||||||
pub resource: ResourceRef,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn identifier(s: &str) -> bool {
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
s.as_bytes()
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||||
.first()
|
pub struct TextureDescriptor {
|
||||||
.is_some_and(|c| c.is_ascii_alphabetic() || *c == b'_')
|
pub dimension: TextureDimension,
|
||||||
&& s.bytes()
|
pub format: TextureFormat,
|
||||||
.all(|c| c.is_ascii_alphanumeric() || matches!(c, b'.' | b'_' | b'/' | b'-'))
|
pub extent: TextureExtent,
|
||||||
|
pub mip_level_count: u32,
|
||||||
|
pub sample_count: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub view_formats: Vec<TextureFormat>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum TriStatePredicate {
|
||||||
|
Any,
|
||||||
|
RequiredTrue,
|
||||||
|
RequiredFalse,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct MeshFilter {
|
||||||
|
pub flag: MeshFlag,
|
||||||
|
pub predicate: TriStatePredicate,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum MeshFlag {
|
||||||
|
IsVisible,
|
||||||
|
IsFrustumCulled,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshFlag {
|
||||||
|
pub const ORDERED: [Self; 2] = [Self::IsVisible, Self::IsFrustumCulled];
|
||||||
|
|
||||||
|
pub const fn input_socket(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::IsVisible => "isVisible",
|
||||||
|
Self::IsFrustumCulled => "isFrustumCulled",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum CompareFunction {
|
||||||
|
Never,
|
||||||
|
Less,
|
||||||
|
Equal,
|
||||||
|
LessEqual,
|
||||||
|
Greater,
|
||||||
|
NotEqual,
|
||||||
|
GreaterEqual,
|
||||||
|
Always,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn identifier(value: &str) -> bool {
|
||||||
|
let mut chars = value.chars();
|
||||||
|
chars.next().is_some_and(|c| c.is_ascii_alphabetic())
|
||||||
|
&& chars.all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '.' | '-'))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
||||||
pub struct GraphV2 {
|
|
||||||
pub schema_version: u32,
|
|
||||||
pub graph_id: String,
|
|
||||||
pub revision: u32,
|
|
||||||
pub nodes: Vec<NodeV2>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct NodeV2 {
|
|
||||||
pub id: String,
|
|
||||||
pub state: NodeStateV2,
|
|
||||||
pub executor: ExecutorRefV2,
|
|
||||||
pub parameters: serde_json::Value,
|
|
||||||
pub inputs: BTreeMap<String, NodeOutputRef>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum NodeStateV2 {
|
|
||||||
Enabled,
|
|
||||||
Muted,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct ExecutorRefV2 {
|
|
||||||
pub key: String,
|
|
||||||
pub version: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct NodeOutputRef {
|
|
||||||
pub node: String,
|
|
||||||
pub socket: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum TextureDimensionV2 {
|
|
||||||
D1,
|
|
||||||
D2,
|
|
||||||
D3,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum TextureFormatV2 {
|
|
||||||
Rgba8Unorm,
|
|
||||||
Rgba8UnormSrgb,
|
|
||||||
Bgra8Unorm,
|
|
||||||
Bgra8UnormSrgb,
|
|
||||||
Rgba16Float,
|
|
||||||
R32Float,
|
|
||||||
Depth32Float,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
|
||||||
pub enum TextureExtentV2 {
|
|
||||||
Absolute {
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
#[serde(rename = "depthOrArrayLayers")]
|
|
||||||
depth_or_array_layers: u32,
|
|
||||||
},
|
|
||||||
SurfaceRelative {
|
|
||||||
width: RatioV2,
|
|
||||||
height: RatioV2,
|
|
||||||
#[serde(rename = "depthOrArrayLayers")]
|
|
||||||
depth_or_array_layers: u32,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct RatioV2 {
|
|
||||||
pub numerator: u32,
|
|
||||||
pub denominator: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum TextureResidencyV2 {
|
|
||||||
Transient,
|
|
||||||
Persistent,
|
|
||||||
History,
|
|
||||||
Readback,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
||||||
pub struct TextureDescriptorV2 {
|
|
||||||
pub dimension: TextureDimensionV2,
|
|
||||||
pub format: TextureFormatV2,
|
|
||||||
pub extent: TextureExtentV2,
|
|
||||||
pub mip_level_count: u32,
|
|
||||||
pub sample_count: u32,
|
|
||||||
#[serde(default)]
|
|
||||||
pub view_formats: Vec<TextureFormatV2>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum TriStatePredicate {
|
|
||||||
Any,
|
|
||||||
RequiredTrue,
|
|
||||||
RequiredFalse,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct MeshFilterV2 {
|
|
||||||
pub flag: MeshFlagV2,
|
|
||||||
pub predicate: TriStatePredicate,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum MeshFlagV2 {
|
|
||||||
IsVisible,
|
|
||||||
IsFrustumCulled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MeshFlagV2 {
|
|
||||||
pub const ORDERED: [Self; 2] = [Self::IsVisible, Self::IsFrustumCulled];
|
|
||||||
|
|
||||||
pub const fn input_socket(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::IsVisible => "isVisible",
|
|
||||||
Self::IsFrustumCulled => "isFrustumCulled",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum CompareFunctionV2 {
|
|
||||||
Never,
|
|
||||||
Less,
|
|
||||||
Equal,
|
|
||||||
LessEqual,
|
|
||||||
Greater,
|
|
||||||
NotEqual,
|
|
||||||
GreaterEqual,
|
|
||||||
Always,
|
|
||||||
}
|
|
||||||
+1616
-664
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
use crate::renderer::{
|
use crate::renderer::{
|
||||||
gpu_scene::GpuSceneCache, material::MaterialResources, ActiveCompiledV1, ActiveCompiledV2,
|
gpu_scene::GpuSceneCache, material::MaterialResources, ActiveCompiledGraph, PipelineLibrary,
|
||||||
PipelineLibrary, PreparedExecutionV2,
|
PreparedExecution,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::scene::Scene;
|
use super::super::scene::Scene;
|
||||||
@@ -46,10 +46,10 @@ fn encode_scene<'a, T: Scene>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn encode_compiled_v2<T: Scene>(
|
pub(crate) fn encode_compiled<T: Scene>(
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
surface: &wgpu::TextureView,
|
surface: &wgpu::TextureView,
|
||||||
active: &ActiveCompiledV2,
|
active: &ActiveCompiledGraph,
|
||||||
scene: &T,
|
scene: &T,
|
||||||
gpu: &GpuSceneCache,
|
gpu: &GpuSceneCache,
|
||||||
pipelines: &PipelineLibrary,
|
pipelines: &PipelineLibrary,
|
||||||
@@ -57,7 +57,7 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
mut profile: Option<&mut crate::renderer::profiler::ProfileFrame>,
|
mut profile: Option<&mut crate::renderer::profiler::ProfileFrame>,
|
||||||
) -> Result<(), &'static str> {
|
) -> Result<(), &'static str> {
|
||||||
use crate::render_graph::{
|
use crate::render_graph::{
|
||||||
ExecutionKindV2, NormalizedColorLoadV2, NormalizedDepthLoadV2, ResourcePlanV2, StoreOpV2,
|
ExecutionKind, NormalizedColorLoad, NormalizedDepthLoad, ResourcePlan, StoreOp,
|
||||||
};
|
};
|
||||||
let view = |resource: u32| -> Result<&wgpu::TextureView, &'static str> {
|
let view = |resource: u32| -> Result<&wgpu::TextureView, &'static str> {
|
||||||
let is_surface = active
|
let is_surface = active
|
||||||
@@ -67,8 +67,8 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
.is_some_and(|resource| {
|
.is_some_and(|resource| {
|
||||||
matches!(
|
matches!(
|
||||||
resource.plan,
|
resource.plan,
|
||||||
ResourcePlanV2::SurfaceTarget { family }
|
ResourcePlan::SurfaceTarget { family }
|
||||||
| ResourcePlanV2::Texture { family, .. }
|
| ResourcePlan::Texture { family, .. }
|
||||||
if family == active.runtime.allocations.surface_family
|
if family == active.runtime.allocations.surface_family
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -82,25 +82,25 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
.get(resource as usize)
|
.get(resource as usize)
|
||||||
.copied()
|
.copied()
|
||||||
.flatten()
|
.flatten()
|
||||||
.ok_or("V2 resource has no allocation")?;
|
.ok_or(" resource has no allocation")?;
|
||||||
active
|
active
|
||||||
.textures
|
.textures
|
||||||
.get(a.class as usize)
|
.get(a.class as usize)
|
||||||
.and_then(|c| c.get(a.slot as usize))
|
.and_then(|c| c.get(a.slot as usize))
|
||||||
.map(|s| &s.view)
|
.map(|s| &s.view)
|
||||||
.ok_or("V2 allocation out of bounds")
|
.ok_or(" allocation out of bounds")
|
||||||
};
|
};
|
||||||
for (execution_index, prepared) in active.executions.iter().enumerate() {
|
for (execution_index, prepared) in active.executions.iter().enumerate() {
|
||||||
let profile_id = &active.graph.executions[execution_index].id;
|
let profile_id = &active.graph.executions[execution_index].id;
|
||||||
match prepared {
|
match prepared {
|
||||||
PreparedExecutionV2::FrustumCull => {
|
PreparedExecution::FrustumCull => {
|
||||||
gpu.encode_frustum_cull(encoder, profile.as_deref_mut(), profile_id);
|
gpu.encode_frustum_cull(encoder, profile.as_deref_mut(), profile_id);
|
||||||
}
|
}
|
||||||
PreparedExecutionV2::MeshQuery => {
|
PreparedExecution::MeshQuery => {
|
||||||
gpu.encode_mesh_query(encoder, profile.as_deref_mut(), profile_id);
|
gpu.encode_mesh_query(encoder, profile.as_deref_mut(), profile_id);
|
||||||
}
|
}
|
||||||
PreparedExecutionV2::Present => {}
|
PreparedExecution::Present => {}
|
||||||
PreparedExecutionV2::Fullscreen {
|
PreparedExecution::Fullscreen {
|
||||||
execution,
|
execution,
|
||||||
bind_group,
|
bind_group,
|
||||||
pipeline,
|
pipeline,
|
||||||
@@ -110,8 +110,8 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
.graph
|
.graph
|
||||||
.executions
|
.executions
|
||||||
.get(*execution)
|
.get(*execution)
|
||||||
.ok_or("V2 execution out of bounds")?;
|
.ok_or(" execution out of bounds")?;
|
||||||
let ExecutionKindV2::Render {
|
let ExecutionKind::Render {
|
||||||
color_attachments, ..
|
color_attachments, ..
|
||||||
} = &execution.kind
|
} = &execution.kind
|
||||||
else {
|
else {
|
||||||
@@ -128,8 +128,8 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: match color.load {
|
load: match color.load {
|
||||||
NormalizedColorLoadV2::Load => wgpu::LoadOp::Load,
|
NormalizedColorLoad::Load => wgpu::LoadOp::Load,
|
||||||
NormalizedColorLoadV2::Clear { value } => {
|
NormalizedColorLoad::Clear { value } => {
|
||||||
wgpu::LoadOp::Clear(wgpu::Color {
|
wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
r: value[0],
|
r: value[0],
|
||||||
g: value[1],
|
g: value[1],
|
||||||
@@ -138,7 +138,7 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
store: if color.store == StoreOpV2::Store {
|
store: if color.store == StoreOp::Store {
|
||||||
wgpu::StoreOp::Store
|
wgpu::StoreOp::Store
|
||||||
} else {
|
} else {
|
||||||
wgpu::StoreOp::Discard
|
wgpu::StoreOp::Discard
|
||||||
@@ -155,7 +155,7 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
pass.set_bind_group(0, bind_group, &[]);
|
pass.set_bind_group(0, bind_group, &[]);
|
||||||
pass.draw(0..3, 0..1);
|
pass.draw(0..3, 0..1);
|
||||||
}
|
}
|
||||||
PreparedExecutionV2::LegacyForward {
|
PreparedExecution::LegacyForward {
|
||||||
execution,
|
execution,
|
||||||
variants,
|
variants,
|
||||||
} => {
|
} => {
|
||||||
@@ -163,8 +163,8 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
.graph
|
.graph
|
||||||
.executions
|
.executions
|
||||||
.get(*execution)
|
.get(*execution)
|
||||||
.ok_or("V2 execution out of bounds")?;
|
.ok_or(" execution out of bounds")?;
|
||||||
let ExecutionKindV2::Render {
|
let ExecutionKind::Render {
|
||||||
color_attachments,
|
color_attachments,
|
||||||
depth_stencil,
|
depth_stencil,
|
||||||
} = &execution.kind
|
} = &execution.kind
|
||||||
@@ -181,8 +181,8 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: match color.load {
|
load: match color.load {
|
||||||
NormalizedColorLoadV2::Load => wgpu::LoadOp::Load,
|
NormalizedColorLoad::Load => wgpu::LoadOp::Load,
|
||||||
NormalizedColorLoadV2::Clear { value } => {
|
NormalizedColorLoad::Clear { value } => {
|
||||||
wgpu::LoadOp::Clear(wgpu::Color {
|
wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
r: value[0],
|
r: value[0],
|
||||||
g: value[1],
|
g: value[1],
|
||||||
@@ -191,7 +191,7 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
store: if color.store == StoreOpV2::Store {
|
store: if color.store == StoreOp::Store {
|
||||||
wgpu::StoreOp::Store
|
wgpu::StoreOp::Store
|
||||||
} else {
|
} else {
|
||||||
wgpu::StoreOp::Discard
|
wgpu::StoreOp::Discard
|
||||||
@@ -202,12 +202,10 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
|||||||
view: view(depth.resource)?,
|
view: view(depth.resource)?,
|
||||||
depth_ops: Some(wgpu::Operations {
|
depth_ops: Some(wgpu::Operations {
|
||||||
load: match depth.load {
|
load: match depth.load {
|
||||||
NormalizedDepthLoadV2::Load => wgpu::LoadOp::Load,
|
NormalizedDepthLoad::Load => wgpu::LoadOp::Load,
|
||||||
NormalizedDepthLoadV2::Clear { value } => {
|
NormalizedDepthLoad::Clear { value } => wgpu::LoadOp::Clear(value),
|
||||||
wgpu::LoadOp::Clear(value)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
store: if depth.store == StoreOpV2::Store {
|
store: if depth.store == StoreOp::Store {
|
||||||
wgpu::StoreOp::Store
|
wgpu::StoreOp::Store
|
||||||
} else {
|
} else {
|
||||||
wgpu::StoreOp::Discard
|
wgpu::StoreOp::Discard
|
||||||
@@ -304,88 +302,3 @@ pub(crate) fn encode_immediate<T: Scene>(
|
|||||||
});
|
});
|
||||||
encode_scene(&mut pass, scene, gpu, pipelines, materials);
|
encode_scene(&mut pass, scene, gpu, pipelines, materials);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn encode_compiled_v1<T: Scene>(
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
color_view: &wgpu::TextureView,
|
|
||||||
active: &ActiveCompiledV1,
|
|
||||||
scene: &T,
|
|
||||||
gpu: &GpuSceneCache,
|
|
||||||
pipelines: &PipelineLibrary,
|
|
||||||
materials: &MaterialResources,
|
|
||||||
mut profile: Option<&mut crate::renderer::profiler::ProfileFrame>,
|
|
||||||
) {
|
|
||||||
for pass in &active.graph.passes {
|
|
||||||
let depth_resource = pass
|
|
||||||
.writes
|
|
||||||
.iter()
|
|
||||||
.find(|w| w.binding == "depth")
|
|
||||||
.unwrap()
|
|
||||||
.resource as usize;
|
|
||||||
let allocation = active.graph.resources[depth_resource].allocation.unwrap();
|
|
||||||
let depth_view =
|
|
||||||
&active.views[active.class_bases[allocation.class as usize] + allocation.slot as usize];
|
|
||||||
let color = pass.writes.iter().find(|w| w.binding == "color").unwrap();
|
|
||||||
let depth = pass.writes.iter().find(|w| w.binding == "depth").unwrap();
|
|
||||||
let (color_load, color_store) = match &color.access {
|
|
||||||
crate::render_graph::WriteAccess::ColorAttachment { load, store, .. } => (
|
|
||||||
match load {
|
|
||||||
crate::render_graph::ColorLoad::Clear { value } => {
|
|
||||||
wgpu::LoadOp::Clear(wgpu::Color {
|
|
||||||
r: value[0],
|
|
||||||
g: value[1],
|
|
||||||
b: value[2],
|
|
||||||
a: value[3],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
crate::render_graph::ColorLoad::Load => wgpu::LoadOp::Load,
|
|
||||||
},
|
|
||||||
if matches!(store, crate::render_graph::StoreOp::Store) {
|
|
||||||
wgpu::StoreOp::Store
|
|
||||||
} else {
|
|
||||||
wgpu::StoreOp::Discard
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let (depth_load, depth_store) = match &depth.access {
|
|
||||||
crate::render_graph::WriteAccess::DepthAttachment { load, store } => (
|
|
||||||
match load {
|
|
||||||
crate::render_graph::DepthLoad::Clear { value } => wgpu::LoadOp::Clear(*value),
|
|
||||||
crate::render_graph::DepthLoad::Load => wgpu::LoadOp::Load,
|
|
||||||
},
|
|
||||||
if matches!(store, crate::render_graph::StoreOp::Store) {
|
|
||||||
wgpu::StoreOp::Store
|
|
||||||
} else {
|
|
||||||
wgpu::StoreOp::Discard
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
||||||
label: Some(&pass.id),
|
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
||||||
depth_slice: None,
|
|
||||||
view: color_view,
|
|
||||||
resolve_target: None,
|
|
||||||
ops: wgpu::Operations {
|
|
||||||
load: color_load,
|
|
||||||
store: color_store,
|
|
||||||
},
|
|
||||||
})],
|
|
||||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
|
||||||
view: depth_view,
|
|
||||||
depth_ops: Some(wgpu::Operations {
|
|
||||||
load: depth_load,
|
|
||||||
store: depth_store,
|
|
||||||
}),
|
|
||||||
stencil_ops: None,
|
|
||||||
}),
|
|
||||||
occlusion_query_set: None,
|
|
||||||
timestamp_writes: profile
|
|
||||||
.as_deref_mut()
|
|
||||||
.and_then(|p| p.render_writes(&pass.id)),
|
|
||||||
});
|
|
||||||
encode_scene(&mut render_pass, scene, gpu, pipelines, materials);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
mod legacy_forward;
|
mod legacy_forward;
|
||||||
|
|
||||||
pub(super) use legacy_forward::{encode_compiled_v1, encode_compiled_v2, encode_immediate};
|
pub(super) use legacy_forward::{encode_compiled, encode_immediate};
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ impl GpuScenePlan {
|
|||||||
pub fn build(data: &SceneFramePlan) -> Result<Self, &'static str> {
|
pub fn build(data: &SceneFramePlan) -> Result<Self, &'static str> {
|
||||||
self::GpuScenePlan::build_with_query(
|
self::GpuScenePlan::build_with_query(
|
||||||
data,
|
data,
|
||||||
crate::render_graph::MeshQueryRuntimeKeyV2 {
|
crate::render_graph::MeshQueryRuntimeKey {
|
||||||
visible: crate::render_graph::TriStatePredicate::RequiredTrue,
|
visible: crate::render_graph::TriStatePredicate::RequiredTrue,
|
||||||
frustum_culled: crate::render_graph::TriStatePredicate::Any,
|
frustum_culled: crate::render_graph::TriStatePredicate::Any,
|
||||||
},
|
},
|
||||||
@@ -93,7 +93,7 @@ impl GpuScenePlan {
|
|||||||
|
|
||||||
pub fn build_with_query(
|
pub fn build_with_query(
|
||||||
data: &SceneFramePlan,
|
data: &SceneFramePlan,
|
||||||
query: crate::render_graph::MeshQueryRuntimeKeyV2,
|
query: crate::render_graph::MeshQueryRuntimeKey,
|
||||||
) -> Result<Self, &'static str> {
|
) -> Result<Self, &'static str> {
|
||||||
let _ = query; // Packing is canonical; predicates are evaluated by the GPU.
|
let _ = query; // Packing is canonical; predicates are evaluated by the GPU.
|
||||||
let mut plan = Self::default();
|
let mut plan = Self::default();
|
||||||
@@ -277,7 +277,7 @@ pub struct BufferSlot {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GpuSceneCache {
|
pub struct GpuSceneCache {
|
||||||
revision: Option<u64>,
|
revision: Option<u64>,
|
||||||
query: Option<crate::render_graph::MeshQueryRuntimeKeyV2>,
|
query: Option<crate::render_graph::MeshQueryRuntimeKey>,
|
||||||
pub positions: BufferSlot,
|
pub positions: BufferSlot,
|
||||||
pub normals: BufferSlot,
|
pub normals: BufferSlot,
|
||||||
pub uvs: BufferSlot,
|
pub uvs: BufferSlot,
|
||||||
@@ -329,7 +329,7 @@ impl GpuSceneCache {
|
|||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
data,
|
data,
|
||||||
crate::render_graph::MeshQueryRuntimeKeyV2 {
|
crate::render_graph::MeshQueryRuntimeKey {
|
||||||
visible: crate::render_graph::TriStatePredicate::RequiredTrue,
|
visible: crate::render_graph::TriStatePredicate::RequiredTrue,
|
||||||
frustum_culled: crate::render_graph::TriStatePredicate::Any,
|
frustum_culled: crate::render_graph::TriStatePredicate::Any,
|
||||||
},
|
},
|
||||||
@@ -341,7 +341,7 @@ impl GpuSceneCache {
|
|||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
data: &SceneFramePlan,
|
data: &SceneFramePlan,
|
||||||
query: crate::render_graph::MeshQueryRuntimeKeyV2,
|
query: crate::render_graph::MeshQueryRuntimeKey,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if self.revision == Some(data.revision) && self.query == Some(query) {
|
if self.revision == Some(data.revision) && self.query == Some(query) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -580,7 +580,7 @@ impl GpuSceneCache {
|
|||||||
&self,
|
&self,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
planes: Option<[[f32; 4]; 6]>,
|
planes: Option<[[f32; 4]; 6]>,
|
||||||
query: crate::render_graph::MeshQueryRuntimeKeyV2,
|
query: crate::render_graph::MeshQueryRuntimeKey,
|
||||||
) {
|
) {
|
||||||
if let Some(compute) = &self.compute {
|
if let Some(compute) = &self.compute {
|
||||||
if let Some(planes) = planes {
|
if let Some(planes) = planes {
|
||||||
|
|||||||
+140
-510
File diff suppressed because it is too large
Load Diff
@@ -419,12 +419,12 @@ impl PipelineLibrary {
|
|||||||
let spec =
|
let spec =
|
||||||
target_variant_spec(spec, color_format, depth_format, depth_compare, depth_write);
|
target_variant_spec(spec, color_format, depth_format, depth_compare, depth_write);
|
||||||
let vertex_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
let vertex_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
label: Some("V2 target variant"),
|
label: Some(" target variant"),
|
||||||
source: wgpu::ShaderSource::Wgsl(spec.vertex.shader_source.as_str().into()),
|
source: wgpu::ShaderSource::Wgsl(spec.vertex.shader_source.as_str().into()),
|
||||||
});
|
});
|
||||||
let fragment_shader = spec.fragment.as_ref().map(|stage| {
|
let fragment_shader = spec.fragment.as_ref().map(|stage| {
|
||||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
label: Some("V2 target variant"),
|
label: Some(" target variant"),
|
||||||
source: wgpu::ShaderSource::Wgsl(stage.shader_source.as_str().into()),
|
source: wgpu::ShaderSource::Wgsl(stage.shader_source.as_str().into()),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -469,7 +469,7 @@ impl PipelineLibrary {
|
|||||||
});
|
});
|
||||||
Ok(
|
Ok(
|
||||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
label: Some("V2 target variant"),
|
label: Some(" target variant"),
|
||||||
layout,
|
layout,
|
||||||
vertex: wgpu::VertexState {
|
vertex: wgpu::VertexState {
|
||||||
module: &vertex_shader,
|
module: &vertex_shader,
|
||||||
@@ -513,7 +513,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn v2_target_spec_disables_blending_without_mutating_base() {
|
fn target_spec_disables_blending_without_mutating_base() {
|
||||||
let mut base = spec();
|
let mut base = spec();
|
||||||
base.primitive.cull_mode = Some(wgpu::Face::Front);
|
base.primitive.cull_mode = Some(wgpu::Face::Front);
|
||||||
base.multisample.count = 4;
|
base.multisample.count = 4;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
pub const MAX_PROFILE_PASSES: usize = crate::render_graph::MAX_PASSES;
|
pub const MAX_PROFILE_PASSES: usize = crate::render_graph::MAX_EXECUTIONS;
|
||||||
const SLOT_COUNT: usize = 4;
|
const SLOT_COUNT: usize = 4;
|
||||||
const QUERY_COUNT: u32 = (MAX_PROFILE_PASSES * 2) as u32;
|
const QUERY_COUNT: u32 = (MAX_PROFILE_PASSES * 2) as u32;
|
||||||
const RESOLVE_SIZE: u64 = QUERY_COUNT as u64 * 8;
|
const RESOLVE_SIZE: u64 = QUERY_COUNT as u64 * 8;
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ function publish(telemetry) {
|
|||||||
activeCompiledGraph: telemetry.activeCompiledGraph,
|
activeCompiledGraph: telemetry.activeCompiledGraph,
|
||||||
activeCompiledRevision: telemetry.activeCompiledRevision,
|
activeCompiledRevision: telemetry.activeCompiledRevision,
|
||||||
activeCompiledSchemaVersion: telemetry.activeCompiledSchemaVersion,
|
activeCompiledSchemaVersion: telemetry.activeCompiledSchemaVersion,
|
||||||
graphPasses: telemetry.graphPasses,
|
|
||||||
graphExecutions: telemetry.graphExecutions,
|
graphExecutions: telemetry.graphExecutions,
|
||||||
graphTextureSlots: telemetry.graphTextureSlots,
|
graphTextureSlots: telemetry.graphTextureSlots,
|
||||||
draws: telemetry.draws,
|
draws: telemetry.draws,
|
||||||
|
|||||||
@@ -345,76 +345,3 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
|||||||
fail("AUTHORING_SHAPE");
|
fail("AUTHORING_SHAPE");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const adaptGraphSnapshot = adaptFxNodeSnapshot;
|
|
||||||
|
|
||||||
/** Compatibility helper retained for V1 presets. */
|
|
||||||
export function semanticProjectionToV1(p, revision = 1) {
|
|
||||||
const extent = {
|
|
||||||
kind: "surface_relative",
|
|
||||||
width: { numerator: 1, denominator: 1 },
|
|
||||||
height: { numerator: 1, denominator: 1 },
|
|
||||||
depthOrArrayLayers: 1,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
schemaVersion: 1,
|
|
||||||
graphId: p.graphId,
|
|
||||||
revision,
|
|
||||||
resources: [
|
|
||||||
{
|
|
||||||
id: "surface",
|
|
||||||
version: 0,
|
|
||||||
residency: { kind: "external", source: "surface_color" },
|
|
||||||
texture: {
|
|
||||||
dimension: "d2",
|
|
||||||
format: "surface",
|
|
||||||
extent,
|
|
||||||
mipLevelCount: 1,
|
|
||||||
sampleCount: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "depth",
|
|
||||||
version: 0,
|
|
||||||
residency: { kind: "transient" },
|
|
||||||
texture: {
|
|
||||||
dimension: "d2",
|
|
||||||
format: "depth32_float",
|
|
||||||
extent,
|
|
||||||
mipLevelCount: 1,
|
|
||||||
sampleCount: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
passes: [
|
|
||||||
{
|
|
||||||
id: "forward",
|
|
||||||
state: p.passState,
|
|
||||||
executor: { key: "scene_forward", version: 1 },
|
|
||||||
parameters: {},
|
|
||||||
reads: [],
|
|
||||||
writes: [
|
|
||||||
{
|
|
||||||
binding: "color",
|
|
||||||
resource: { id: "surface", version: 0 },
|
|
||||||
access: {
|
|
||||||
kind: "color_attachment",
|
|
||||||
location: 0,
|
|
||||||
load: { op: "clear", value: p.clearColor },
|
|
||||||
store: "store",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
binding: "depth",
|
|
||||||
resource: { id: "depth", version: 0 },
|
|
||||||
access: {
|
|
||||||
kind: "depth_attachment",
|
|
||||||
load: { op: "clear", value: p.clearDepth },
|
|
||||||
store: "store",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
outputs: [{ name: "present", resource: { id: "surface", version: 0 } }],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,13 +1,3 @@
|
|||||||
import { semanticProjectionToV1 } from "./adapter.js";
|
|
||||||
const make = (graphId, clearColor) =>
|
|
||||||
Object.freeze(
|
|
||||||
semanticProjectionToV1(
|
|
||||||
{ graphId, clearColor, clearDepth: 1, passState: "enabled" },
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
export const midnight = make("preset_midnight", [0.015, 0.06, 0.18, 1]);
|
|
||||||
export const ember = make("preset_ember", [0.18, 0.035, 0.012, 1]);
|
|
||||||
const input = (node, socket) => ({ node, socket });
|
const input = (node, socket) => ({ node, socket });
|
||||||
const node = (id, key, parameters = {}, inputs = {}) => ({
|
const node = (id, key, parameters = {}, inputs = {}) => ({
|
||||||
id,
|
id,
|
||||||
@@ -32,6 +22,38 @@ const texture = (format) => ({
|
|||||||
},
|
},
|
||||||
residency: "transient",
|
residency: "transient",
|
||||||
});
|
});
|
||||||
|
const direct = (graphId, clearColor) => Object.freeze({
|
||||||
|
schemaVersion: 2,
|
||||||
|
graphId,
|
||||||
|
revision: 1,
|
||||||
|
nodes: [
|
||||||
|
node("surface", "surface_target"),
|
||||||
|
node("depth", "texture_spec", texture("depth32_float")),
|
||||||
|
node("scene", "scene_table"),
|
||||||
|
node("visible", "visibility_flags", {}, { scene: input("scene", "scene") }),
|
||||||
|
node("query", "mesh_query", {
|
||||||
|
filters: [
|
||||||
|
{ flag: "isVisible", predicate: "required_true" },
|
||||||
|
{ flag: "isFrustumCulled", predicate: "any" },
|
||||||
|
],
|
||||||
|
}, { scene: input("scene", "scene"), isVisible: input("visible", "flags") }),
|
||||||
|
node("depth_config", "depth_stencil_config", {
|
||||||
|
depthCompare: "less_equal",
|
||||||
|
depthWriteEnabled: true,
|
||||||
|
clearDepth: 1,
|
||||||
|
}),
|
||||||
|
node("forward", "legacy_forward", { clearColor }, {
|
||||||
|
scene: input("scene", "scene"),
|
||||||
|
draws: input("query", "draws"),
|
||||||
|
colorTarget: input("surface", "surface"),
|
||||||
|
depthTarget: input("depth", "spec"),
|
||||||
|
depthStencil: input("depth_config", "config"),
|
||||||
|
}),
|
||||||
|
node("present", "present", {}, { surface: input("forward", "color") }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
export const midnight = direct("preset_midnight", [0.015, 0.06, 0.18, 1]);
|
||||||
|
export const ember = direct("preset_ember", [0.18, 0.035, 0.012, 1]);
|
||||||
export const hdr = Object.freeze({
|
export const hdr = Object.freeze({
|
||||||
schemaVersion: 2,
|
schemaVersion: 2,
|
||||||
graphId: "preset_hdr_fullscreen",
|
graphId: "preset_hdr_fullscreen",
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ function fixture() {
|
|||||||
version: 1,
|
version: 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
test("catalog exhaustively mirrors all current V2 contracts", () => {
|
test("catalog exhaustively mirrors all current contracts", () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
Object.keys(semanticCatalog).sort(),
|
Object.keys(semanticCatalog).sort(),
|
||||||
[
|
[
|
||||||
@@ -115,7 +115,7 @@ test("catalog exhaustively mirrors all current V2 contracts", () => {
|
|||||||
assert.ok(c.parameters);
|
assert.ok(c.parameters);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
test("adapter deterministically emits strict V2, permits repeated types, omits muted links and maps sources", () => {
|
test("adapter deterministically emits the canonical schema, permits repeated types, omits muted links and maps sources", () => {
|
||||||
const x = fixture(),
|
const x = fixture(),
|
||||||
a = adaptFxNodeSnapshot(x, 7);
|
a = adaptFxNodeSnapshot(x, 7);
|
||||||
x.nodes.reverse();
|
x.nodes.reverse();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
midnight,
|
midnight,
|
||||||
renderGraphPresets,
|
renderGraphPresets,
|
||||||
} from "../static/render-graph/presets.js";
|
} from "../static/render-graph/presets.js";
|
||||||
test("Phase 4 unit 4 presets preserve V1 graphs and add the V2 HDR fullscreen topology", () => {
|
test("presets use canonical node graphs", () => {
|
||||||
assert.deepEqual(Object.keys(renderGraphPresets), [
|
assert.deepEqual(Object.keys(renderGraphPresets), [
|
||||||
"midnight",
|
"midnight",
|
||||||
"ember",
|
"ember",
|
||||||
@@ -22,20 +22,13 @@ test("Phase 4 unit 4 presets preserve V1 graphs and add the V2 HDR fullscreen to
|
|||||||
[midnight.graphId, ember.graphId],
|
[midnight.graphId, ember.graphId],
|
||||||
["preset_midnight", "preset_ember"],
|
["preset_midnight", "preset_ember"],
|
||||||
);
|
);
|
||||||
assert.notDeepEqual(
|
assert.notDeepEqual(midnight.nodes[6].parameters.clearColor, ember.nodes[6].parameters.clearColor);
|
||||||
midnight.passes[0].writes[0].access.load.value,
|
|
||||||
ember.passes[0].writes[0].access.load.value,
|
|
||||||
);
|
|
||||||
for (const graph of [midnight, ember]) {
|
for (const graph of [midnight, ember]) {
|
||||||
assert.equal(graph.schemaVersion, 1);
|
assert.equal(graph.schemaVersion, 2);
|
||||||
assert.equal(graph.revision, 1);
|
assert.equal(graph.revision, 1);
|
||||||
assert.equal(graph.passes.length, 1);
|
assert.equal(graph.nodes[6].executor.key, "legacy_forward");
|
||||||
assert.equal(graph.passes[0].state, "enabled");
|
assert.deepEqual(graph.nodes[6].inputs.colorTarget, { node: "surface", socket: "surface" });
|
||||||
assert.deepEqual(graph.passes[0].executor, {
|
assert.equal(graph.nodes.at(-1).executor.key, "present");
|
||||||
key: "scene_forward",
|
|
||||||
version: 1,
|
|
||||||
});
|
|
||||||
assert.equal(graph.outputs[0].name, "present");
|
|
||||||
}
|
}
|
||||||
assert.equal(hdr.schemaVersion, 2);
|
assert.equal(hdr.schemaVersion, 2);
|
||||||
assert.equal(hdr.revision, 1);
|
assert.equal(hdr.revision, 1);
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ test("import rejects when disposed during asynchronous source loading", async ()
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("compile transfers payload and waits for ready before opcode 7", async()=>{
|
test("compile transfers payload and waits for ready before opcode 7", async()=>{
|
||||||
const f=fixture(), pending=f.client.compileGraph({schemaVersion:1});
|
const f=fixture(), pending=f.client.compileGraph({schemaVersion:2});
|
||||||
assert.equal(f.worker.transfers[0].length,1); assert.equal(Atomics.load(f.header,5),0);
|
assert.equal(f.worker.transfers[0].length,1); assert.equal(Atomics.load(f.header,5),0);
|
||||||
f.worker.reply({type:"payload-ready",id:1}); await Promise.resolve();
|
f.worker.reply({type:"payload-ready",id:1}); await Promise.resolve();
|
||||||
assert.equal(new Int32Array(f.memory.buffer,64,24)[1],7);
|
assert.equal(new Int32Array(f.memory.buffer,64,24)[1],7);
|
||||||
@@ -133,7 +133,7 @@ test("compile rejects oversized encoding", async()=>{const f=fixture();await ass
|
|||||||
test("drop graph emits opcode 8 and validates id", async()=>{const f=fixture(),p=f.client.dropCompiledGraph([9,4]);const slot=new Int32Array(f.memory.buffer,64,24);assert.deepEqual([...slot.slice(1,5)],[8,1,9,4]);f.worker.reply({type:"reply",request:1,ok:true});await p;assert.throws(()=>f.client.dropCompiledGraph([1]),TypeError);});
|
test("drop graph emits opcode 8 and validates id", async()=>{const f=fixture(),p=f.client.dropCompiledGraph([9,4]);const slot=new Int32Array(f.memory.buffer,64,24);assert.deepEqual([...slot.slice(1,5)],[8,1,9,4]);f.worker.reply({type:"reply",request:1,ok:true});await p;assert.throws(()=>f.client.dropCompiledGraph([1]),TypeError);});
|
||||||
test("ring-full graph compile releases staged payload", async()=>{const f=fixture();Atomics.store(f.header,5,1024);const p=f.client.compileGraph({});f.worker.reply({type:"payload-ready",id:1});await assert.rejects(p,e=>e.code==="RING_FULL");assert.equal(f.worker.messages.at(-1).type,"payload-release");});
|
test("ring-full graph compile releases staged payload", async()=>{const f=fixture();Atomics.store(f.header,5,1024);const p=f.client.compileGraph({});f.worker.reply({type:"payload-ready",id:1});await assert.rejects(p,e=>e.code==="RING_FULL");assert.equal(f.worker.messages.at(-1).type,"payload-release");});
|
||||||
test("disposal while graph payload is pending releases and rejects", async()=>{const f=fixture(),p=f.client.compileGraph({});f.client.dispose();await assert.rejects(p,e=>e.code==="DISPOSED");assert.equal(f.worker.messages.at(-1).type,"payload-release");});
|
test("disposal while graph payload is pending releases and rejects", async()=>{const f=fixture(),p=f.client.compileGraph({});f.client.dispose();await assert.rejects(p,e=>e.code==="DISPOSED");assert.equal(f.worker.messages.at(-1).type,"payload-release");});
|
||||||
test("payload transfer uses the exact encoded ArrayBuffer", async()=>{const f=fixture(),graph={schemaVersion:1};const expected=new TextEncoder().encode(JSON.stringify(graph));const p=f.client.compileGraph(graph);const transferred=f.worker.transfers[0][0];assert.strictEqual(f.worker.messages[0].buffer,transferred);assert.deepEqual(new Uint8Array(transferred),expected);f.client.dispose();await assert.rejects(p);});
|
test("payload transfer uses the exact encoded ArrayBuffer", async()=>{const f=fixture(),graph={schemaVersion:2};const expected=new TextEncoder().encode(JSON.stringify(graph));const p=f.client.compileGraph(graph);const transferred=f.worker.transfers[0][0];assert.strictEqual(f.worker.messages[0].buffer,transferred);assert.deepEqual(new Uint8Array(transferred),expected);f.client.dispose();await assert.rejects(p);});
|
||||||
test("cycle error details are preserved exactly", async()=>{const f=fixture(),p=f.client.compileGraph({});f.worker.reply({type:"payload-ready",id:1});await Promise.resolve();const details={message:"cycle",kind:"cycle",edges:[{from:"a",resource:{id:"r",version:0},to:"b"}]};f.worker.reply({type:"reply",request:1,ok:false,code:"GRAPH_CYCLE",details});await assert.rejects(p,e=>e.details===details&&e.details.edges[0].from==="a");});
|
test("cycle error details are preserved exactly", async()=>{const f=fixture(),p=f.client.compileGraph({});f.worker.reply({type:"payload-ready",id:1});await Promise.resolve();const details={message:"cycle",kind:"cycle",edges:[{from:"a",resource:{id:"r",version:0},to:"b"}]};f.worker.reply({type:"reply",request:1,ok:false,code:"GRAPH_CYCLE",details});await assert.rejects(p,e=>e.details===details&&e.details.edges[0].from==="a");});
|
||||||
test("error without details leaves details undefined", async()=>{const f=fixture(),p=f.client.dropCompiledGraph([1,1]);f.worker.reply({type:"reply",request:1,ok:false,code:"STALE_GRAPH_ID"});await assert.rejects(p,e=>e instanceof RendererError&&e.details===undefined&&e.message==="STALE_GRAPH_ID");});
|
test("error without details leaves details undefined", async()=>{const f=fixture(),p=f.client.dropCompiledGraph([1,1]);f.worker.reply({type:"reply",request:1,ok:false,code:"STALE_GRAPH_ID"});await assert.rejects(p,e=>e instanceof RendererError&&e.details===undefined&&e.message==="STALE_GRAPH_ID");});
|
||||||
test("switch methods emit exact opcode 9 words", async()=>{const f=fixture();const a=f.client.switchCompiledGraph([9,4]);assert.deepEqual([...new Int32Array(f.memory.buffer,64,24).slice(1,7)],[9,1,1,9,4,0]);f.worker.reply({type:"reply",request:1,ok:true});await a;await new Promise(queueMicrotask);const b=f.client.switchToImmediate();assert.deepEqual([...new Int32Array(f.memory.buffer,160,24).slice(1,7)],[9,2,0,0,0,0]);f.worker.reply({type:"reply",request:2,ok:true});await b;assert.throws(()=>f.client.switchCompiledGraph([0,0]),TypeError);});
|
test("switch methods emit exact opcode 9 words", async()=>{const f=fixture();const a=f.client.switchCompiledGraph([9,4]);assert.deepEqual([...new Int32Array(f.memory.buffer,64,24).slice(1,7)],[9,1,1,9,4,0]);f.worker.reply({type:"reply",request:1,ok:true});await a;await new Promise(queueMicrotask);const b=f.client.switchToImmediate();assert.deepEqual([...new Int32Array(f.memory.buffer,160,24).slice(1,7)],[9,2,0,0,0,0]);f.worker.reply({type:"reply",request:2,ok:true});await b;assert.throws(()=>f.client.switchCompiledGraph([0,0]),TypeError);});
|
||||||
|
|||||||
Reference in New Issue
Block a user