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