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:
+1856
-1004
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_v2;
|
||||
mod contracts_v2;
|
||||
mod plan_v2;
|
||||
mod contracts;
|
||||
mod plan;
|
||||
mod registry;
|
||||
mod runtime;
|
||||
mod runtime_v2;
|
||||
mod schema;
|
||||
mod schema_v2;
|
||||
|
||||
pub use compiler::{
|
||||
compile, compile_with, parse_and_compile, AllocationClass, CompiledGraph, CompiledOutput,
|
||||
CompiledPass, CompiledRead, CompiledResource, CompiledWrite, ExecutorContract,
|
||||
ExecutorRegistry, ExecutorResolution, Lifetime, NormalizedParameters, SceneForwardExecutors,
|
||||
TextureAllocationKey, TextureUsage, TransientAllocation,
|
||||
};
|
||||
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 compiler::{compile, mesh_predicate_matches, parse_and_compile};
|
||||
pub use contracts::*;
|
||||
pub use plan::*;
|
||||
pub use registry::{CompiledGraphId, Registry};
|
||||
pub use runtime::*;
|
||||
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_RESOURCES: usize = 1024;
|
||||
pub const MAX_PASSES: usize = 1024;
|
||||
pub const MAX_USES: usize = 8192;
|
||||
pub const MAX_OUTPUTS: usize = 64;
|
||||
pub const MAX_EXECUTIONS: usize = 1024;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
|
||||
pub struct GraphError {
|
||||
@@ -71,6 +33,7 @@ impl GraphError {
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn at(
|
||||
code: &'static str,
|
||||
message: impl Into<String>,
|
||||
@@ -87,5 +50,3 @@ impl GraphError {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
#[cfg(test)]
|
||||
mod tests_v2;
|
||||
|
||||
@@ -4,15 +4,15 @@ use super::*;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompiledGraphV2 {
|
||||
pub struct CompiledGraph {
|
||||
pub schema_version: u32,
|
||||
pub graph_id: String,
|
||||
pub revision: u32,
|
||||
pub node_count: u32,
|
||||
pub resources: Vec<CompiledResourceV2>,
|
||||
pub executions: Vec<CompiledExecutionV2>,
|
||||
pub texture_families: Vec<TextureFamilyV2>,
|
||||
pub allocation_classes: Vec<AllocationClassV2>,
|
||||
pub resources: Vec<CompiledResource>,
|
||||
pub executions: Vec<CompiledExecution>,
|
||||
pub texture_families: Vec<TextureFamily>,
|
||||
pub allocation_classes: Vec<AllocationClass>,
|
||||
pub culled_node_count: u32,
|
||||
pub culled_resource_count: u32,
|
||||
pub transient_slot_count: u32,
|
||||
@@ -20,26 +20,26 @@ pub struct CompiledGraphV2 {
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompiledResourceV2 {
|
||||
pub struct CompiledResource {
|
||||
pub original_node_index: u32,
|
||||
pub output_ordinal: u16,
|
||||
pub origin: NodeOutputRef,
|
||||
pub semantic_type: SemanticTypeV2,
|
||||
pub semantic_type: SemanticType,
|
||||
pub producer_execution: Option<u32>,
|
||||
pub lifetime: Option<LifetimeV2>,
|
||||
pub plan: ResourcePlanV2,
|
||||
pub lifetime: Option<Lifetime>,
|
||||
pub plan: ResourcePlan,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum ResourcePlanV2 {
|
||||
pub enum ResourcePlan {
|
||||
SurfaceTarget {
|
||||
family: u32,
|
||||
},
|
||||
TextureSpec {
|
||||
family: u32,
|
||||
residency: TextureResidencyV2,
|
||||
descriptor: NormalizedTextureDescriptorV2,
|
||||
residency: TextureResidency,
|
||||
descriptor: NormalizedTextureDescriptor,
|
||||
},
|
||||
Texture {
|
||||
family: u32,
|
||||
@@ -47,7 +47,7 @@ pub enum ResourcePlanV2 {
|
||||
target: u32,
|
||||
initialized: bool,
|
||||
stored: bool,
|
||||
allocation: Option<AllocationRefV2>,
|
||||
allocation: Option<AllocationRef>,
|
||||
},
|
||||
SceneTable,
|
||||
LocalAabbBuffer {
|
||||
@@ -56,53 +56,53 @@ pub enum ResourcePlanV2 {
|
||||
CameraFrustum,
|
||||
BooleanFlagBuffer {
|
||||
scene: u32,
|
||||
flag: MeshFlagV2,
|
||||
flag: MeshFlag,
|
||||
},
|
||||
DrawStream {
|
||||
scene: u32,
|
||||
},
|
||||
DepthStencilConfig {
|
||||
config: NormalizedDepthStencilV2,
|
||||
config: NormalizedDepthStencil,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompiledExecutionV2 {
|
||||
pub struct CompiledExecution {
|
||||
pub id: String,
|
||||
pub original_node_index: u32,
|
||||
pub executor: ExecutorRefV2,
|
||||
pub parameters: NormalizedParametersV2,
|
||||
pub kind: ExecutionKindV2,
|
||||
pub inputs: Vec<CompiledSocketInputV2>,
|
||||
pub outputs: Vec<CompiledSocketOutputV2>,
|
||||
pub accesses: Vec<CompiledAccessV2>,
|
||||
pub executor: ExecutorRef,
|
||||
pub parameters: NormalizedParameters,
|
||||
pub kind: ExecutionKind,
|
||||
pub inputs: Vec<CompiledSocketInput>,
|
||||
pub outputs: Vec<CompiledSocketOutput>,
|
||||
pub accesses: Vec<CompiledAccess>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompiledSocketInputV2 {
|
||||
pub struct CompiledSocketInput {
|
||||
pub socket: String,
|
||||
pub resource: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompiledSocketOutputV2 {
|
||||
pub struct CompiledSocketOutput {
|
||||
pub socket: String,
|
||||
pub resource: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum ExecutionKindV2 {
|
||||
pub enum ExecutionKind {
|
||||
CpuPreparation,
|
||||
Compute {
|
||||
work: ComputeWorkV2,
|
||||
work: ComputeWork,
|
||||
},
|
||||
Render {
|
||||
color_attachments: Vec<ColorAttachmentPlanV2>,
|
||||
depth_stencil: Option<DepthStencilAttachmentPlanV2>,
|
||||
color_attachments: Vec<ColorAttachmentPlan>,
|
||||
depth_stencil: Option<DepthStencilAttachmentPlan>,
|
||||
},
|
||||
Present {
|
||||
surface: u32,
|
||||
@@ -111,60 +111,60 @@ pub enum ExecutionKindV2 {
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ComputeWorkV2 {
|
||||
pub enum ComputeWork {
|
||||
FrustumCull,
|
||||
MeshQuery,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ColorAttachmentPlanV2 {
|
||||
pub struct ColorAttachmentPlan {
|
||||
pub resource: u32,
|
||||
pub location: u32,
|
||||
pub load: NormalizedColorLoadV2,
|
||||
pub store: StoreOpV2,
|
||||
pub load: NormalizedColorLoad,
|
||||
pub store: StoreOp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DepthStencilAttachmentPlanV2 {
|
||||
pub struct DepthStencilAttachmentPlan {
|
||||
pub resource: u32,
|
||||
pub load: NormalizedDepthLoadV2,
|
||||
pub store: StoreOpV2,
|
||||
pub load: NormalizedDepthLoad,
|
||||
pub store: StoreOp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum NormalizedColorLoadV2 {
|
||||
pub enum NormalizedColorLoad {
|
||||
Load,
|
||||
Clear { value: [f64; 4] },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum NormalizedDepthLoadV2 {
|
||||
pub enum NormalizedDepthLoad {
|
||||
Load,
|
||||
Clear { value: f32 },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum StoreOpV2 {
|
||||
pub enum StoreOp {
|
||||
Store,
|
||||
Discard,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompiledAccessV2 {
|
||||
pub struct CompiledAccess {
|
||||
pub socket: String,
|
||||
pub resource: u32,
|
||||
pub mode: AccessModeV2,
|
||||
pub mode: AccessMode,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum AccessModeV2 {
|
||||
pub enum AccessMode {
|
||||
SemanticRead,
|
||||
UniformRead,
|
||||
StorageRead,
|
||||
@@ -175,13 +175,13 @@ pub enum AccessModeV2 {
|
||||
SampledTexture,
|
||||
ColorAttachment {
|
||||
location: u32,
|
||||
load: NormalizedColorLoadV2,
|
||||
store: StoreOpV2,
|
||||
load: NormalizedColorLoad,
|
||||
store: StoreOp,
|
||||
full_overwrite: bool,
|
||||
},
|
||||
DepthAttachment {
|
||||
load: NormalizedDepthLoadV2,
|
||||
store: StoreOpV2,
|
||||
load: NormalizedDepthLoad,
|
||||
store: StoreOp,
|
||||
full_overwrite: bool,
|
||||
},
|
||||
Present,
|
||||
@@ -189,11 +189,11 @@ pub enum AccessModeV2 {
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum NormalizedParametersV2 {
|
||||
pub enum NormalizedParameters {
|
||||
SurfaceTarget,
|
||||
TextureSpec {
|
||||
residency: TextureResidencyV2,
|
||||
texture: NormalizedTextureDescriptorV2,
|
||||
residency: TextureResidency,
|
||||
texture: NormalizedTextureDescriptor,
|
||||
},
|
||||
SceneTable,
|
||||
LocalAabbBuffer,
|
||||
@@ -201,10 +201,10 @@ pub enum NormalizedParametersV2 {
|
||||
VisibilityFlags,
|
||||
FrustumCull,
|
||||
MeshQuery {
|
||||
filters: [NormalizedMeshFilterV2; 2],
|
||||
filters: [NormalizedMeshFilter; 2],
|
||||
},
|
||||
DepthStencilConfig {
|
||||
config: NormalizedDepthStencilV2,
|
||||
config: NormalizedDepthStencil,
|
||||
},
|
||||
LegacyForward {
|
||||
clear_color: [f64; 4],
|
||||
@@ -232,117 +232,117 @@ pub enum NormalizedParametersV2 {
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NormalizedMeshFilterV2 {
|
||||
pub flag: MeshFlagV2,
|
||||
pub struct NormalizedMeshFilter {
|
||||
pub flag: MeshFlag,
|
||||
pub predicate: TriStatePredicate,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NormalizedDepthStencilV2 {
|
||||
pub depth_compare: CompareFunctionV2,
|
||||
pub struct NormalizedDepthStencil {
|
||||
pub depth_compare: CompareFunction,
|
||||
pub depth_write_enabled: bool,
|
||||
pub clear_depth: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NormalizedTextureDescriptorV2 {
|
||||
pub dimension: TextureDimensionV2,
|
||||
pub format: TextureFormatV2,
|
||||
pub extent: NormalizedTextureExtentV2,
|
||||
pub struct NormalizedTextureDescriptor {
|
||||
pub dimension: TextureDimension,
|
||||
pub format: TextureFormat,
|
||||
pub extent: NormalizedTextureExtent,
|
||||
pub mip_level_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)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum NormalizedTextureExtentV2 {
|
||||
pub enum NormalizedTextureExtent {
|
||||
Absolute {
|
||||
width: u32,
|
||||
height: u32,
|
||||
depth_or_array_layers: u32,
|
||||
},
|
||||
SurfaceRelative {
|
||||
width: RatioV2,
|
||||
height: RatioV2,
|
||||
width: Ratio,
|
||||
height: Ratio,
|
||||
depth_or_array_layers: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LifetimeV2 {
|
||||
pub struct Lifetime {
|
||||
pub first_use: u32,
|
||||
pub last_use: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TextureFamilyKeyV2 {
|
||||
pub struct TextureFamilyKey {
|
||||
pub source_node: u32,
|
||||
pub source_socket: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum TextureFamilySourceV2 {
|
||||
pub enum TextureFamilySource {
|
||||
ImportedSurface {
|
||||
resource: u32,
|
||||
},
|
||||
AuthoredTexture {
|
||||
resource: u32,
|
||||
residency: TextureResidencyV2,
|
||||
descriptor: NormalizedTextureDescriptorV2,
|
||||
residency: TextureResidency,
|
||||
descriptor: NormalizedTextureDescriptor,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TextureFamilyV2 {
|
||||
pub struct TextureFamily {
|
||||
pub id: u32,
|
||||
pub key: TextureFamilyKeyV2,
|
||||
pub source: TextureFamilySourceV2,
|
||||
pub lifetime: LifetimeV2,
|
||||
pub versions: Vec<TextureVersionV2>,
|
||||
pub usage: Vec<TextureUsageV2>,
|
||||
pub allocation: Option<AllocationRefV2>,
|
||||
pub key: TextureFamilyKey,
|
||||
pub source: TextureFamilySource,
|
||||
pub lifetime: Lifetime,
|
||||
pub versions: Vec<TextureVersion>,
|
||||
pub usage: Vec<TextureUsage>,
|
||||
pub allocation: Option<AllocationRef>,
|
||||
pub aliasable: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TextureVersionV2 {
|
||||
pub struct TextureVersion {
|
||||
pub version: u32,
|
||||
pub resource: u32,
|
||||
pub target: u32,
|
||||
pub initialized: bool,
|
||||
pub stored: bool,
|
||||
pub lifetime: LifetimeV2,
|
||||
pub lifetime: Lifetime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TextureCompatibilityKeyV2 {
|
||||
pub dimension: TextureDimensionV2,
|
||||
pub format: TextureFormatV2,
|
||||
pub extent: NormalizedTextureExtentV2,
|
||||
pub struct TextureCompatibilityKey {
|
||||
pub dimension: TextureDimension,
|
||||
pub format: TextureFormat,
|
||||
pub extent: NormalizedTextureExtent,
|
||||
pub mip_level_count: u32,
|
||||
pub sample_count: u32,
|
||||
pub view_formats: Vec<TextureFormatV2>,
|
||||
pub view_formats: Vec<TextureFormat>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AllocationClassV2 {
|
||||
pub key: TextureCompatibilityKeyV2,
|
||||
pub slots: Vec<AllocationSlotV2>,
|
||||
pub struct AllocationClass {
|
||||
pub key: TextureCompatibilityKey,
|
||||
pub slots: Vec<AllocationSlot>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AllocationKindV2 {
|
||||
pub enum AllocationKind {
|
||||
AliasedTransient,
|
||||
DedicatedTransient,
|
||||
Persistent,
|
||||
@@ -350,22 +350,22 @@ pub enum AllocationKindV2 {
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AllocationSlotV2 {
|
||||
pub kind: AllocationKindV2,
|
||||
pub usage: Vec<TextureUsageV2>,
|
||||
pub struct AllocationSlot {
|
||||
pub kind: AllocationKind,
|
||||
pub usage: Vec<TextureUsage>,
|
||||
pub occupants: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AllocationRefV2 {
|
||||
pub struct AllocationRef {
|
||||
pub class: u32,
|
||||
pub slot: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TextureUsageV2 {
|
||||
pub enum TextureUsage {
|
||||
Sampled,
|
||||
Storage,
|
||||
CopySrc,
|
||||
@@ -374,7 +374,7 @@ pub enum TextureUsageV2 {
|
||||
DepthAttachment,
|
||||
}
|
||||
|
||||
impl CompiledGraphV2 {
|
||||
impl CompiledGraph {
|
||||
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})
|
||||
}
|
||||
@@ -1,51 +1,39 @@
|
||||
use super::{parse_and_compile_any, CompiledGraph, CompiledGraphV2, GraphError};
|
||||
use std::collections::HashMap;
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RegisteredGraph {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use super::{parse_and_compile, CompiledGraph, GraphError};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct CompiledGraphId {
|
||||
pub slot: u32,
|
||||
pub generation: u32,
|
||||
}
|
||||
|
||||
impl From<CompiledGraphId> for [u32; 2] {
|
||||
fn from(x: CompiledGraphId) -> Self {
|
||||
[x.slot, x.generation]
|
||||
fn from(id: CompiledGraphId) -> Self {
|
||||
[id.slot, id.generation]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Slot {
|
||||
generation: u32,
|
||||
value: Option<RegisteredGraph>,
|
||||
value: Option<CompiledGraph>,
|
||||
retired: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Registry {
|
||||
slots: Vec<Slot>,
|
||||
capacity: u32,
|
||||
latest_revisions: HashMap<String, u32>,
|
||||
}
|
||||
|
||||
impl Default for Registry {
|
||||
fn default() -> Self {
|
||||
Self::new(16)
|
||||
}
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
pub fn new(capacity: u32) -> Self {
|
||||
Self {
|
||||
@@ -54,28 +42,28 @@ impl Registry {
|
||||
latest_revisions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(
|
||||
&mut self,
|
||||
bytes: &[u8],
|
||||
) -> Result<(CompiledGraphId, serde_json::Value), GraphError> {
|
||||
let graph = parse_and_compile_any(bytes)?;
|
||||
let (graph_id, revision) = graph.identity();
|
||||
let graph = parse_and_compile(bytes)?;
|
||||
if self
|
||||
.latest_revisions
|
||||
.get(graph_id)
|
||||
.is_some_and(|latest| revision <= *latest)
|
||||
.get(&graph.graph_id)
|
||||
.is_some_and(|latest| graph.revision <= *latest)
|
||||
{
|
||||
return Err(GraphError::new(
|
||||
"GRAPH_REVISION_CONFLICT",
|
||||
"revision must increase",
|
||||
));
|
||||
}
|
||||
let i = if let Some(i) = self
|
||||
let index = if let Some(index) = self
|
||||
.slots
|
||||
.iter()
|
||||
.position(|s| s.value.is_none() && !s.retired)
|
||||
.position(|slot| slot.value.is_none() && !slot.retired)
|
||||
{
|
||||
i
|
||||
index
|
||||
} else {
|
||||
if u32::try_from(self.slots.len()).map_or(true, |len| len >= self.capacity) {
|
||||
return Err(GraphError::new(
|
||||
@@ -91,51 +79,40 @@ impl Registry {
|
||||
self.slots.len() - 1
|
||||
};
|
||||
let id = CompiledGraphId {
|
||||
slot: u32::try_from(i)
|
||||
slot: u32::try_from(index)
|
||||
.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());
|
||||
self.latest_revisions.insert(graph_id.to_owned(), revision);
|
||||
self.slots[i].value = Some(graph);
|
||||
self.latest_revisions
|
||||
.insert(graph.graph_id.clone(), graph.revision);
|
||||
self.slots[index].value = Some(graph);
|
||||
Ok((id, summary))
|
||||
}
|
||||
|
||||
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
|
||||
.get(id.slot as usize)
|
||||
.filter(|s| s.generation == id.generation)
|
||||
.and_then(|s| s.value.as_ref())
|
||||
.filter(|slot| slot.generation == id.generation)
|
||||
.and_then(|slot| slot.value.as_ref())
|
||||
.ok_or_else(|| GraphError::new("STALE_GRAPH_ID", "stale compiled graph id"))
|
||||
}
|
||||
|
||||
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> {
|
||||
let s = self
|
||||
let slot = self
|
||||
.slots
|
||||
.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"))?;
|
||||
s.value = None;
|
||||
if s.generation == u32::MAX {
|
||||
s.retired = true
|
||||
slot.value = None;
|
||||
if slot.generation == u32::MAX {
|
||||
slot.retired = true
|
||||
} else {
|
||||
s.generation += 1
|
||||
slot.generation += 1
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,215 +1,604 @@
|
||||
use std::collections::BTreeMap;
|
||||
use super::*;
|
||||
|
||||
use super::{
|
||||
CompiledGraph, Dimension, Extent, ExternalSource, Format, GraphError, Residency,
|
||||
TextureAllocationKey, TextureUsage,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct ResolvedExtent {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub depth_or_array_layers: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct RuntimeTextureKey {
|
||||
pub dimension: Dimension,
|
||||
pub format: Format,
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RuntimeTextureDescriptor {
|
||||
pub dimension: wgpu::TextureDimension,
|
||||
pub format: wgpu::TextureFormat,
|
||||
pub extent: ResolvedExtent,
|
||||
pub mip_level_count: u32,
|
||||
pub sample_count: u32,
|
||||
pub usage: Vec<TextureUsage>,
|
||||
pub view_formats: Vec<Format>,
|
||||
pub usage: wgpu::TextureUsages,
|
||||
pub view_formats: Vec<wgpu::TextureFormat>,
|
||||
}
|
||||
|
||||
fn scaled(value: u32, numerator: u32, denominator: u32) -> Result<u32, GraphError> {
|
||||
if denominator == 0 {
|
||||
return Err(GraphError::new(
|
||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RuntimeSurfaceContract {
|
||||
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 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",
|
||||
path,
|
||||
));
|
||||
}
|
||||
let product = u64::from(value)
|
||||
.checked_mul(u64::from(numerator))
|
||||
.ok_or_else(|| GraphError::new("GRAPH_EXECUTION_UNSUPPORTED", "extent overflow"))?;
|
||||
.checked_mul(u64::from(ratio.numerator))
|
||||
.ok_or_else(|| error("GRAPH_RESOURCE_LIMIT", "extent arithmetic overflow", path))?;
|
||||
let result = product
|
||||
.checked_add(u64::from(denominator) - 1)
|
||||
.ok_or_else(|| GraphError::new("GRAPH_EXECUTION_UNSUPPORTED", "extent overflow"))?
|
||||
/ u64::from(denominator);
|
||||
.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(|_| 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> {
|
||||
let (width, height, depth_or_array_layers) = match extent {
|
||||
Extent::Absolute {
|
||||
pub fn resolve_extent(
|
||||
extent: &NormalizedTextureExtent,
|
||||
surface: [u32; 2],
|
||||
) -> Result<ResolvedExtent, GraphError> {
|
||||
let resolved = match extent {
|
||||
NormalizedTextureExtent::Absolute {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers,
|
||||
} => (*width, *height, *depth_or_array_layers),
|
||||
Extent::SurfaceRelative {
|
||||
} => ResolvedExtent {
|
||||
width: *width,
|
||||
height: *height,
|
||||
depth_or_array_layers: *depth_or_array_layers,
|
||||
},
|
||||
NormalizedTextureExtent::SurfaceRelative {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers,
|
||||
} => (
|
||||
scaled(surface[0], width.numerator, width.denominator)?,
|
||||
scaled(surface[1], height.numerator, height.denominator)?,
|
||||
*depth_or_array_layers,
|
||||
),
|
||||
} => ResolvedExtent {
|
||||
width: scaled(surface[0], *width, "extent.width")?,
|
||||
height: scaled(surface[1], *height, "extent.height")?,
|
||||
depth_or_array_layers: *depth_or_array_layers,
|
||||
},
|
||||
};
|
||||
if width == 0 || height == 0 || depth_or_array_layers == 0 {
|
||||
return Err(GraphError::new(
|
||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
||||
"texture extent must be nonzero",
|
||||
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(ResolvedExtent {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers,
|
||||
})
|
||||
Ok(resolved)
|
||||
}
|
||||
|
||||
pub fn runtime_texture_key(
|
||||
key: &TextureAllocationKey,
|
||||
surface: [u32; 2],
|
||||
) -> Result<RuntimeTextureKey, GraphError> {
|
||||
Ok(RuntimeTextureKey {
|
||||
dimension: key.descriptor.dimension,
|
||||
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(),
|
||||
})
|
||||
pub fn resolved_mip_level_count(extent: ResolvedExtent) -> u32 {
|
||||
32 - extent
|
||||
.width
|
||||
.max(extent.height)
|
||||
.max(extent.depth_or_array_layers)
|
||||
.leading_zeros()
|
||||
}
|
||||
|
||||
/// Assigns disjoint physical ranges after merging symbolic allocation classes that
|
||||
/// resolve to the same concrete descriptor key.
|
||||
pub fn class_offsets(
|
||||
classes: &[(TextureAllocationKey, u32)],
|
||||
surface: [u32; 2],
|
||||
) -> Result<Vec<u32>, GraphError> {
|
||||
let mut next = BTreeMap::new();
|
||||
let mut offsets = Vec::with_capacity(classes.len());
|
||||
for (key, count) in classes {
|
||||
let concrete = runtime_texture_key(key, surface)?;
|
||||
let offset = next.entry(concrete).or_insert(0u32);
|
||||
offsets.push(*offset);
|
||||
*offset = offset.checked_add(*count).ok_or_else(|| {
|
||||
GraphError::new("GRAPH_EXECUTION_UNSUPPORTED", "transient slot overflow")
|
||||
})?;
|
||||
fn validate_limits(
|
||||
dimension: TextureDimension,
|
||||
extent: ResolvedExtent,
|
||||
mip_count: u32,
|
||||
limits: Option<&wgpu::Limits>,
|
||||
path: &str,
|
||||
) -> Result<(), GraphError> {
|
||||
let max_mips = resolved_mip_level_count(extent);
|
||||
if mip_count == 0 || mip_count > max_mips {
|
||||
return Err(error(
|
||||
"GRAPH_RESOURCE_LIMIT",
|
||||
"invalid mip level count",
|
||||
path,
|
||||
));
|
||||
}
|
||||
Ok(offsets)
|
||||
}
|
||||
|
||||
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() {
|
||||
return Err(unsupported());
|
||||
}
|
||||
let surface_outputs = graph
|
||||
.outputs
|
||||
.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
|
||||
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
|
||||
}
|
||||
) || !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());
|
||||
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
|
||||
}
|
||||
};
|
||||
if !valid {
|
||||
return Err(error(
|
||||
"GRAPH_RESOURCE_LIMIT",
|
||||
"texture exceeds device limits",
|
||||
path,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::render_graph::{Dimension, Ratio, TextureDescriptor, TextureUsage};
|
||||
fn key(n: u32, d: u32) -> TextureAllocationKey {
|
||||
TextureAllocationKey {
|
||||
descriptor: TextureDescriptor {
|
||||
dimension: Dimension::D2,
|
||||
format: Format::Depth32Float,
|
||||
extent: Extent::SurfaceRelative {
|
||||
width: Ratio {
|
||||
numerator: n,
|
||||
denominator: d,
|
||||
},
|
||||
height: Ratio {
|
||||
numerator: n,
|
||||
denominator: d,
|
||||
},
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
},
|
||||
usage: vec![TextureUsage::DepthAttachment],
|
||||
view_formats: vec![],
|
||||
pub fn runtime_texture_descriptor(
|
||||
key: &TextureCompatibilityKey,
|
||||
usage: &[TextureUsage],
|
||||
surface: [u32; 2],
|
||||
limits: Option<&wgpu::Limits>,
|
||||
) -> Result<RuntimeTextureDescriptor, GraphError> {
|
||||
let extent = resolve_extent(&key.extent, surface)?;
|
||||
validate_limits(
|
||||
key.dimension,
|
||||
extent,
|
||||
key.mip_level_count,
|
||||
limits,
|
||||
"allocationClasses.key",
|
||||
)?;
|
||||
Ok(RuntimeTextureDescriptor {
|
||||
dimension: texture_dimension(key.dimension),
|
||||
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,
|
||||
..
|
||||
} | NormalizedTextureExtent::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 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;
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn extent_uses_checked_ceil_and_minimum_one() {
|
||||
assert_eq!(
|
||||
resolve_extent(&key(1, 2).descriptor.extent, [3, 1]).unwrap(),
|
||||
ResolvedExtent {
|
||||
width: 2,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1
|
||||
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"),
|
||||
));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
#[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]
|
||||
);
|
||||
|
||||
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,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: Vec::new(),
|
||||
};
|
||||
prepare_runtime_plan(graph, surface, None).map(|_| ())
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct GraphV1 {
|
||||
pub struct Graph {
|
||||
pub schema_version: u32,
|
||||
pub graph_id: String,
|
||||
pub revision: u32,
|
||||
pub resources: Vec<Resource>,
|
||||
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,
|
||||
pub nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct Resource {
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Node {
|
||||
pub id: String,
|
||||
pub version: u32,
|
||||
pub residency: Residency,
|
||||
pub texture: TextureDescriptor,
|
||||
pub state: NodeState,
|
||||
pub executor: ExecutorRef,
|
||||
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)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum Residency {
|
||||
External { source: ExternalSource },
|
||||
Transient,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExternalSource {
|
||||
SurfaceColor,
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ExecutorRef {
|
||||
pub key: String,
|
||||
pub version: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct TextureDescriptor {
|
||||
pub dimension: Dimension,
|
||||
pub format: Format,
|
||||
pub extent: Extent,
|
||||
pub mip_level_count: u32,
|
||||
pub sample_count: 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 Dimension {
|
||||
pub enum TextureDimension {
|
||||
D1,
|
||||
D2,
|
||||
D3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Format {
|
||||
Surface,
|
||||
pub enum TextureFormat {
|
||||
Rgba8Unorm,
|
||||
Rgba8UnormSrgb,
|
||||
Bgra8Unorm,
|
||||
@@ -67,15 +61,10 @@ pub enum Format {
|
||||
R32Float,
|
||||
Depth32Float,
|
||||
}
|
||||
impl Format {
|
||||
pub(crate) fn depth(self) -> bool {
|
||||
self == Self::Depth32Float
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum Extent {
|
||||
pub enum TextureExtent {
|
||||
Absolute {
|
||||
width: u32,
|
||||
height: u32,
|
||||
@@ -96,93 +85,75 @@ pub struct Ratio {
|
||||
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)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ReadAccess {
|
||||
Sampled,
|
||||
Storage,
|
||||
CopySrc,
|
||||
}
|
||||
#[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 enum TextureResidency {
|
||||
Transient,
|
||||
Persistent,
|
||||
History,
|
||||
Readback,
|
||||
}
|
||||
|
||||
pub(crate) fn identifier(s: &str) -> bool {
|
||||
s.as_bytes()
|
||||
.first()
|
||||
.is_some_and(|c| c.is_ascii_alphabetic() || *c == b'_')
|
||||
&& s.bytes()
|
||||
.all(|c| c.is_ascii_alphanumeric() || matches!(c, b'.' | b'_' | b'/' | b'-'))
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct TextureDescriptor {
|
||||
pub dimension: TextureDimension,
|
||||
pub format: TextureFormat,
|
||||
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,
|
||||
}
|
||||
+1609
-657
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::{
|
||||
gpu_scene::GpuSceneCache, material::MaterialResources, ActiveCompiledV1, ActiveCompiledV2,
|
||||
PipelineLibrary, PreparedExecutionV2,
|
||||
gpu_scene::GpuSceneCache, material::MaterialResources, ActiveCompiledGraph, PipelineLibrary,
|
||||
PreparedExecution,
|
||||
};
|
||||
|
||||
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,
|
||||
surface: &wgpu::TextureView,
|
||||
active: &ActiveCompiledV2,
|
||||
active: &ActiveCompiledGraph,
|
||||
scene: &T,
|
||||
gpu: &GpuSceneCache,
|
||||
pipelines: &PipelineLibrary,
|
||||
@@ -57,7 +57,7 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
||||
mut profile: Option<&mut crate::renderer::profiler::ProfileFrame>,
|
||||
) -> Result<(), &'static str> {
|
||||
use crate::render_graph::{
|
||||
ExecutionKindV2, NormalizedColorLoadV2, NormalizedDepthLoadV2, ResourcePlanV2, StoreOpV2,
|
||||
ExecutionKind, NormalizedColorLoad, NormalizedDepthLoad, ResourcePlan, StoreOp,
|
||||
};
|
||||
let view = |resource: u32| -> Result<&wgpu::TextureView, &'static str> {
|
||||
let is_surface = active
|
||||
@@ -67,8 +67,8 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
||||
.is_some_and(|resource| {
|
||||
matches!(
|
||||
resource.plan,
|
||||
ResourcePlanV2::SurfaceTarget { family }
|
||||
| ResourcePlanV2::Texture { family, .. }
|
||||
ResourcePlan::SurfaceTarget { family }
|
||||
| ResourcePlan::Texture { family, .. }
|
||||
if family == active.runtime.allocations.surface_family
|
||||
)
|
||||
});
|
||||
@@ -82,25 +82,25 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
||||
.get(resource as usize)
|
||||
.copied()
|
||||
.flatten()
|
||||
.ok_or("V2 resource has no allocation")?;
|
||||
.ok_or(" resource has no allocation")?;
|
||||
active
|
||||
.textures
|
||||
.get(a.class as usize)
|
||||
.and_then(|c| c.get(a.slot as usize))
|
||||
.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() {
|
||||
let profile_id = &active.graph.executions[execution_index].id;
|
||||
match prepared {
|
||||
PreparedExecutionV2::FrustumCull => {
|
||||
PreparedExecution::FrustumCull => {
|
||||
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);
|
||||
}
|
||||
PreparedExecutionV2::Present => {}
|
||||
PreparedExecutionV2::Fullscreen {
|
||||
PreparedExecution::Present => {}
|
||||
PreparedExecution::Fullscreen {
|
||||
execution,
|
||||
bind_group,
|
||||
pipeline,
|
||||
@@ -110,8 +110,8 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
||||
.graph
|
||||
.executions
|
||||
.get(*execution)
|
||||
.ok_or("V2 execution out of bounds")?;
|
||||
let ExecutionKindV2::Render {
|
||||
.ok_or(" execution out of bounds")?;
|
||||
let ExecutionKind::Render {
|
||||
color_attachments, ..
|
||||
} = &execution.kind
|
||||
else {
|
||||
@@ -128,8 +128,8 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: match color.load {
|
||||
NormalizedColorLoadV2::Load => wgpu::LoadOp::Load,
|
||||
NormalizedColorLoadV2::Clear { value } => {
|
||||
NormalizedColorLoad::Load => wgpu::LoadOp::Load,
|
||||
NormalizedColorLoad::Clear { value } => {
|
||||
wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: value[0],
|
||||
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
|
||||
} else {
|
||||
wgpu::StoreOp::Discard
|
||||
@@ -155,7 +155,7 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
||||
pass.set_bind_group(0, bind_group, &[]);
|
||||
pass.draw(0..3, 0..1);
|
||||
}
|
||||
PreparedExecutionV2::LegacyForward {
|
||||
PreparedExecution::LegacyForward {
|
||||
execution,
|
||||
variants,
|
||||
} => {
|
||||
@@ -163,8 +163,8 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
||||
.graph
|
||||
.executions
|
||||
.get(*execution)
|
||||
.ok_or("V2 execution out of bounds")?;
|
||||
let ExecutionKindV2::Render {
|
||||
.ok_or(" execution out of bounds")?;
|
||||
let ExecutionKind::Render {
|
||||
color_attachments,
|
||||
depth_stencil,
|
||||
} = &execution.kind
|
||||
@@ -181,8 +181,8 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: match color.load {
|
||||
NormalizedColorLoadV2::Load => wgpu::LoadOp::Load,
|
||||
NormalizedColorLoadV2::Clear { value } => {
|
||||
NormalizedColorLoad::Load => wgpu::LoadOp::Load,
|
||||
NormalizedColorLoad::Clear { value } => {
|
||||
wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: value[0],
|
||||
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
|
||||
} else {
|
||||
wgpu::StoreOp::Discard
|
||||
@@ -202,12 +202,10 @@ pub(crate) fn encode_compiled_v2<T: Scene>(
|
||||
view: view(depth.resource)?,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: match depth.load {
|
||||
NormalizedDepthLoadV2::Load => wgpu::LoadOp::Load,
|
||||
NormalizedDepthLoadV2::Clear { value } => {
|
||||
wgpu::LoadOp::Clear(value)
|
||||
}
|
||||
NormalizedDepthLoad::Load => wgpu::LoadOp::Load,
|
||||
NormalizedDepthLoad::Clear { value } => wgpu::LoadOp::Clear(value),
|
||||
},
|
||||
store: if depth.store == StoreOpV2::Store {
|
||||
store: if depth.store == StoreOp::Store {
|
||||
wgpu::StoreOp::Store
|
||||
} else {
|
||||
wgpu::StoreOp::Discard
|
||||
@@ -304,88 +302,3 @@ pub(crate) fn encode_immediate<T: Scene>(
|
||||
});
|
||||
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;
|
||||
|
||||
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> {
|
||||
self::GpuScenePlan::build_with_query(
|
||||
data,
|
||||
crate::render_graph::MeshQueryRuntimeKeyV2 {
|
||||
crate::render_graph::MeshQueryRuntimeKey {
|
||||
visible: crate::render_graph::TriStatePredicate::RequiredTrue,
|
||||
frustum_culled: crate::render_graph::TriStatePredicate::Any,
|
||||
},
|
||||
@@ -93,7 +93,7 @@ impl GpuScenePlan {
|
||||
|
||||
pub fn build_with_query(
|
||||
data: &SceneFramePlan,
|
||||
query: crate::render_graph::MeshQueryRuntimeKeyV2,
|
||||
query: crate::render_graph::MeshQueryRuntimeKey,
|
||||
) -> Result<Self, &'static str> {
|
||||
let _ = query; // Packing is canonical; predicates are evaluated by the GPU.
|
||||
let mut plan = Self::default();
|
||||
@@ -277,7 +277,7 @@ pub struct BufferSlot {
|
||||
#[derive(Default)]
|
||||
pub struct GpuSceneCache {
|
||||
revision: Option<u64>,
|
||||
query: Option<crate::render_graph::MeshQueryRuntimeKeyV2>,
|
||||
query: Option<crate::render_graph::MeshQueryRuntimeKey>,
|
||||
pub positions: BufferSlot,
|
||||
pub normals: BufferSlot,
|
||||
pub uvs: BufferSlot,
|
||||
@@ -329,7 +329,7 @@ impl GpuSceneCache {
|
||||
device,
|
||||
queue,
|
||||
data,
|
||||
crate::render_graph::MeshQueryRuntimeKeyV2 {
|
||||
crate::render_graph::MeshQueryRuntimeKey {
|
||||
visible: crate::render_graph::TriStatePredicate::RequiredTrue,
|
||||
frustum_culled: crate::render_graph::TriStatePredicate::Any,
|
||||
},
|
||||
@@ -341,7 +341,7 @@ impl GpuSceneCache {
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
data: &SceneFramePlan,
|
||||
query: crate::render_graph::MeshQueryRuntimeKeyV2,
|
||||
query: crate::render_graph::MeshQueryRuntimeKey,
|
||||
) -> Result<(), String> {
|
||||
if self.revision == Some(data.revision) && self.query == Some(query) {
|
||||
return Ok(());
|
||||
@@ -580,7 +580,7 @@ impl GpuSceneCache {
|
||||
&self,
|
||||
queue: &wgpu::Queue,
|
||||
planes: Option<[[f32; 4]; 6]>,
|
||||
query: crate::render_graph::MeshQueryRuntimeKeyV2,
|
||||
query: crate::render_graph::MeshQueryRuntimeKey,
|
||||
) {
|
||||
if let Some(compute) = &self.compute {
|
||||
if let Some(planes) = planes {
|
||||
|
||||
+155
-525
File diff suppressed because it is too large
Load Diff
@@ -419,12 +419,12 @@ impl PipelineLibrary {
|
||||
let spec =
|
||||
target_variant_spec(spec, color_format, depth_format, depth_compare, depth_write);
|
||||
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()),
|
||||
});
|
||||
let fragment_shader = spec.fragment.as_ref().map(|stage| {
|
||||
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()),
|
||||
})
|
||||
});
|
||||
@@ -469,7 +469,7 @@ impl PipelineLibrary {
|
||||
});
|
||||
Ok(
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("V2 target variant"),
|
||||
label: Some(" target variant"),
|
||||
layout,
|
||||
vertex: wgpu::VertexState {
|
||||
module: &vertex_shader,
|
||||
@@ -513,7 +513,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v2_target_spec_disables_blending_without_mutating_base() {
|
||||
fn target_spec_disables_blending_without_mutating_base() {
|
||||
let mut base = spec();
|
||||
base.primitive.cull_mode = Some(wgpu::Face::Front);
|
||||
base.multisample.count = 4;
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{
|
||||
};
|
||||
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 QUERY_COUNT: u32 = (MAX_PROFILE_PASSES * 2) as u32;
|
||||
const RESOLVE_SIZE: u64 = QUERY_COUNT as u64 * 8;
|
||||
|
||||
Reference in New Issue
Block a user