refactor: redesign render graph nodes
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:
@@ -15,15 +15,25 @@ struct TextureParameters {
|
||||
texture: TextureDescriptor,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
struct DepthParameters {
|
||||
depth_compare: CompareFunction,
|
||||
depth_write_enabled: bool,
|
||||
clear_depth: f32,
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct CullParameters {
|
||||
camera: ActiveCamera,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
struct ForwardParameters {
|
||||
struct QueryParameters {
|
||||
visible_predicate: TriStatePredicate,
|
||||
visible_default: bool,
|
||||
frustum_culled_predicate: TriStatePredicate,
|
||||
frustum_culled_default: bool,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
struct PipelineParameters {
|
||||
pipeline: String,
|
||||
depth_compare: CompareFunction,
|
||||
depth_write_enabled: bool,
|
||||
clear_depth: f32,
|
||||
clear_color: [f64; 4],
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
@@ -157,11 +167,12 @@ fn validate_name_grammar(s: &str, path: impl Into<String>) -> Result<(), GraphEr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mesh_predicate_matches(predicate: TriStatePredicate, flag: bool) -> bool {
|
||||
pub fn mesh_predicate_matches(predicate: RuntimePredicate, flag: bool) -> bool {
|
||||
match predicate {
|
||||
TriStatePredicate::Any => true,
|
||||
TriStatePredicate::RequiredTrue => flag,
|
||||
TriStatePredicate::RequiredFalse => !flag,
|
||||
RuntimePredicate::Any => true,
|
||||
RuntimePredicate::RequiredTrue => flag,
|
||||
RuntimePredicate::RequiredFalse => !flag,
|
||||
RuntimePredicate::Never => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,12 +360,12 @@ fn decode(node: &Node, i: usize) -> Result<NormalizedParameters, GraphError> {
|
||||
}};
|
||||
}
|
||||
Ok(match node.executor.key.as_str() {
|
||||
"surface_target" => empty!(NormalizedParameters::SurfaceTarget),
|
||||
"scene_table" => empty!(NormalizedParameters::SceneTable),
|
||||
"local_aabb_buffer" => empty!(NormalizedParameters::LocalAabbBuffer),
|
||||
"camera_frustum" => empty!(NormalizedParameters::CameraFrustum),
|
||||
"visibility_flags" => empty!(NormalizedParameters::VisibilityFlags),
|
||||
"frustum_cull" => empty!(NormalizedParameters::FrustumCull),
|
||||
"mesh" => empty!(NormalizedParameters::Mesh),
|
||||
"frustum_cull" => {
|
||||
let p: CullParameters =
|
||||
serde_json::from_value(node.parameters.clone()).map_err(invalid)?;
|
||||
NormalizedParameters::FrustumCull { camera: p.camera }
|
||||
}
|
||||
"fullscreen_copy" => empty!(NormalizedParameters::FullscreenCopy),
|
||||
"tone_map" => {
|
||||
let p: ToneMapParameters =
|
||||
@@ -402,8 +413,8 @@ fn decode(node: &Node, i: usize) -> Result<NormalizedParameters, GraphError> {
|
||||
strength: range(p.strength, 0.0, 16.0, format!("{base}.strength"))?,
|
||||
}
|
||||
}
|
||||
"present" => empty!(NormalizedParameters::Present),
|
||||
"texture_spec" => {
|
||||
"frame_out" => empty!(NormalizedParameters::FrameOut),
|
||||
"texture" => {
|
||||
let p: TextureParameters =
|
||||
serde_json::from_value(node.parameters.clone()).map_err(invalid)?;
|
||||
if matches!(
|
||||
@@ -416,100 +427,86 @@ fn decode(node: &Node, i: usize) -> Result<NormalizedParameters, GraphError> {
|
||||
format!("{base}.residency"),
|
||||
));
|
||||
}
|
||||
NormalizedParameters::TextureSpec {
|
||||
let unsupported = |suffix: &str| {
|
||||
error(
|
||||
"GRAPH_UNSUPPORTED_FEATURE",
|
||||
"texture feature is unsupported",
|
||||
format!("{base}.texture.{suffix}"),
|
||||
)
|
||||
};
|
||||
if p.texture.dimension != TextureDimension::D2 {
|
||||
return Err(unsupported("dimension"));
|
||||
}
|
||||
if p.texture.mip_level_count != 1 {
|
||||
return Err(unsupported("mipLevelCount"));
|
||||
}
|
||||
if p.texture.sample_count != 1 {
|
||||
return Err(unsupported("sampleCount"));
|
||||
}
|
||||
let depth_or_array_layers = match &p.texture.extent {
|
||||
TextureExtent::Absolute {
|
||||
depth_or_array_layers,
|
||||
..
|
||||
}
|
||||
| TextureExtent::SurfaceRelative {
|
||||
depth_or_array_layers,
|
||||
..
|
||||
} => *depth_or_array_layers,
|
||||
};
|
||||
if depth_or_array_layers != 1 {
|
||||
return Err(unsupported("extent.depthOrArrayLayers"));
|
||||
}
|
||||
NormalizedParameters::Texture {
|
||||
residency: p.residency,
|
||||
texture: normalize_texture(p.texture, &base)?,
|
||||
descriptor: normalize_texture(p.texture, &base)?,
|
||||
}
|
||||
}
|
||||
"mesh_query" => {
|
||||
let object = node.parameters.as_object().ok_or_else(|| {
|
||||
error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
"parameters must be an object",
|
||||
base.clone(),
|
||||
)
|
||||
})?;
|
||||
if object.len() != 1 || !object.contains_key("filters") {
|
||||
return Err(error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
"mesh query parameters must contain only filters",
|
||||
base.clone(),
|
||||
));
|
||||
}
|
||||
let filters = object["filters"].as_array().ok_or_else(|| {
|
||||
error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
"filters must be an array",
|
||||
format!("{base}.filters"),
|
||||
)
|
||||
})?;
|
||||
let mut found = [None, None];
|
||||
for (j, value) in filters.iter().enumerate() {
|
||||
let filter = value.as_object().ok_or_else(|| {
|
||||
error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
"filter must be an object",
|
||||
format!("{base}.filters[{j}]"),
|
||||
)
|
||||
})?;
|
||||
if filter.len() != 2
|
||||
|| !filter.contains_key("flag")
|
||||
|| !filter.contains_key("predicate")
|
||||
{
|
||||
return Err(error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
"filter must contain flag and predicate",
|
||||
format!("{base}.filters[{j}]"),
|
||||
));
|
||||
}
|
||||
let flag: MeshFlag =
|
||||
serde_json::from_value(filter["flag"].clone()).map_err(|e| {
|
||||
error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
&e.to_string(),
|
||||
format!("{base}.filters[{j}].flag"),
|
||||
)
|
||||
})?;
|
||||
let predicate: TriStatePredicate =
|
||||
serde_json::from_value(filter["predicate"].clone()).map_err(|e| {
|
||||
error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
&e.to_string(),
|
||||
format!("{base}.filters[{j}].predicate"),
|
||||
)
|
||||
})?;
|
||||
let index = if flag == MeshFlag::IsVisible { 0 } else { 1 };
|
||||
if found[index].replace(predicate).is_some() {
|
||||
return Err(error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
"duplicate mesh flag",
|
||||
format!("{base}.filters[{j}].flag"),
|
||||
));
|
||||
}
|
||||
}
|
||||
if found.iter().any(Option::is_none) {
|
||||
return Err(error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
"both mesh flags are required",
|
||||
format!("{base}.filters"),
|
||||
));
|
||||
let p: QueryParameters =
|
||||
serde_json::from_value(node.parameters.clone()).map_err(invalid)?;
|
||||
let fold = |predicate, default, linked| match (predicate, linked, default) {
|
||||
(TriStatePredicate::Any, _, _) => RuntimePredicate::Any,
|
||||
(TriStatePredicate::RequiredTrue, true, _) => RuntimePredicate::RequiredTrue,
|
||||
(TriStatePredicate::RequiredFalse, true, _) => RuntimePredicate::RequiredFalse,
|
||||
(TriStatePredicate::RequiredTrue, false, true)
|
||||
| (TriStatePredicate::RequiredFalse, false, false) => RuntimePredicate::Any,
|
||||
_ => RuntimePredicate::Never,
|
||||
};
|
||||
let mut visible = fold(
|
||||
p.visible_predicate,
|
||||
p.visible_default,
|
||||
node.inputs.contains_key("isVisible"),
|
||||
);
|
||||
let mut culled = fold(
|
||||
p.frustum_culled_predicate,
|
||||
p.frustum_culled_default,
|
||||
node.inputs.contains_key("isFrustumCulled"),
|
||||
);
|
||||
if visible == RuntimePredicate::Never || culled == RuntimePredicate::Never {
|
||||
visible = RuntimePredicate::Never;
|
||||
culled = RuntimePredicate::Never;
|
||||
}
|
||||
NormalizedParameters::MeshQuery {
|
||||
filters: [
|
||||
NormalizedMeshFilter {
|
||||
flag: MeshFlag::IsVisible,
|
||||
predicate: found[0].unwrap(),
|
||||
},
|
||||
NormalizedMeshFilter {
|
||||
flag: MeshFlag::IsFrustumCulled,
|
||||
predicate: found[1].unwrap(),
|
||||
},
|
||||
],
|
||||
visible_predicate: visible,
|
||||
frustum_culled_predicate: culled,
|
||||
}
|
||||
}
|
||||
"depth_stencil_config" => {
|
||||
let p: DepthParameters =
|
||||
"pipeline_registry" => empty!(NormalizedParameters::PipelineRegistry),
|
||||
"pipeline" => {
|
||||
let p: PipelineParameters =
|
||||
serde_json::from_value(node.parameters.clone()).map_err(invalid)?;
|
||||
let valid_name = !p.pipeline.is_empty()
|
||||
&& p.pipeline.len() <= 64
|
||||
&& p.pipeline.bytes().enumerate().all(|(i, c)| {
|
||||
c == b'_' || c.is_ascii_alphanumeric() && (i > 0 || c.is_ascii_alphabetic())
|
||||
});
|
||||
if !valid_name {
|
||||
return Err(error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
"pipeline must be a 1-64 byte identifier",
|
||||
format!("{base}.pipeline"),
|
||||
));
|
||||
}
|
||||
if !p.clear_depth.is_finite() || !(0.0..=1.0).contains(&p.clear_depth) {
|
||||
return Err(error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
@@ -517,17 +514,6 @@ fn decode(node: &Node, i: usize) -> Result<NormalizedParameters, GraphError> {
|
||||
format!("{base}.clearDepth"),
|
||||
));
|
||||
}
|
||||
NormalizedParameters::DepthStencilConfig {
|
||||
config: NormalizedDepthStencil {
|
||||
depth_compare: p.depth_compare,
|
||||
depth_write_enabled: p.depth_write_enabled,
|
||||
clear_depth: p.clear_depth,
|
||||
},
|
||||
}
|
||||
}
|
||||
"legacy_forward" => {
|
||||
let p: ForwardParameters =
|
||||
serde_json::from_value(node.parameters.clone()).map_err(invalid)?;
|
||||
if p.clear_color.iter().any(|x| !x.is_finite()) {
|
||||
return Err(error(
|
||||
"GRAPH_PARAMETERS_INVALID",
|
||||
@@ -535,7 +521,11 @@ fn decode(node: &Node, i: usize) -> Result<NormalizedParameters, GraphError> {
|
||||
format!("{base}.clearColor"),
|
||||
));
|
||||
}
|
||||
NormalizedParameters::LegacyForward {
|
||||
NormalizedParameters::Pipeline {
|
||||
pipeline: p.pipeline,
|
||||
depth_compare: p.depth_compare,
|
||||
depth_write_enabled: p.depth_write_enabled,
|
||||
clear_depth: p.clear_depth,
|
||||
clear_color: p.clear_color,
|
||||
}
|
||||
}
|
||||
@@ -569,19 +559,6 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
));
|
||||
}
|
||||
}
|
||||
if graph
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|n| n.executor.key == "present")
|
||||
.count()
|
||||
> 64
|
||||
{
|
||||
return Err(error(
|
||||
"GRAPH_LIMIT_EXCEEDED",
|
||||
"present count exceeds 64",
|
||||
"nodes",
|
||||
));
|
||||
}
|
||||
if graph.schema_version != 2 {
|
||||
return Err(GraphError::new(
|
||||
"GRAPH_SCHEMA_UNSUPPORTED",
|
||||
@@ -673,8 +650,21 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
.enumerate()
|
||||
.map(|(i, n)| decode(n, i))
|
||||
.collect::<Result<_, _>>()?;
|
||||
if graph
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|node| node.executor.key == "frame_out" && node.state == NodeState::Enabled)
|
||||
.count()
|
||||
!= 1
|
||||
{
|
||||
return Err(error(
|
||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
||||
"exactly one frame_out is required",
|
||||
"nodes",
|
||||
));
|
||||
}
|
||||
for (i, n) in graph.nodes.iter().enumerate() {
|
||||
if n.state != NodeState::Enabled {
|
||||
if n.state != NodeState::Enabled && n.executor.key != "frame_out" {
|
||||
return Err(error(
|
||||
"GRAPH_NODE_STATE_INVALID",
|
||||
"muted nodes are unsupported",
|
||||
@@ -710,12 +700,12 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
}
|
||||
for (i, n) in graph.nodes.iter().enumerate() {
|
||||
for input in contracts[i].inputs {
|
||||
let inactive = matches!(¶ms[i], NormalizedParameters::MeshQuery { filters } if filters.iter().any(|f| f.flag.input_socket() == input.name && f.predicate == TriStatePredicate::Any));
|
||||
let inactive = matches!(¶ms[i], NormalizedParameters::MeshQuery { visible_predicate, frustum_culled_predicate } if (input.name == "isVisible" && matches!(visible_predicate, RuntimePredicate::Any | RuntimePredicate::Never)) || (input.name == "isFrustumCulled" && matches!(frustum_culled_predicate, RuntimePredicate::Any | RuntimePredicate::Never)));
|
||||
if !n.inputs.contains_key(input.name) {
|
||||
if input.cardinality == InputCardinality::RequiredOne
|
||||
|| (!inactive
|
||||
&& matches!(params[i], NormalizedParameters::MeshQuery { .. })
|
||||
&& input.name != "scene")
|
||||
&& input.name != "mesh")
|
||||
{
|
||||
return Err(error(
|
||||
"GRAPH_SOCKET_CARDINALITY",
|
||||
@@ -730,7 +720,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
let mut bound: Vec<BTreeMap<&str, BoundInput>> = vec![BTreeMap::new(); graph.nodes.len()];
|
||||
for (i, n) in graph.nodes.iter().enumerate() {
|
||||
for input in contracts[i].inputs {
|
||||
let inactive = matches!(¶ms[i], NormalizedParameters::MeshQuery { filters } if filters.iter().any(|f| f.flag.input_socket() == input.name && f.predicate == TriStatePredicate::Any));
|
||||
let inactive = matches!(¶ms[i], NormalizedParameters::MeshQuery { visible_predicate, frustum_culled_predicate } if (input.name == "isVisible" && matches!(visible_predicate, RuntimePredicate::Any | RuntimePredicate::Never)) || (input.name == "isFrustumCulled" && matches!(frustum_culled_predicate, RuntimePredicate::Any | RuntimePredicate::Never)));
|
||||
let Some(r) = n.inputs.get(input.name) else {
|
||||
continue;
|
||||
};
|
||||
@@ -741,10 +731,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
.enumerate()
|
||||
.find(|(_, o)| o.name == r.socket)
|
||||
.expect("producer sockets were globally validated");
|
||||
let attachment_shape_checked_later = contracts[i].key == "legacy_forward"
|
||||
&& input.name == "depthTarget"
|
||||
&& out.semantic_type == SemanticType::SurfaceTarget;
|
||||
if !accepts(input.accepted, out.semantic_type) && !attachment_shape_checked_later {
|
||||
if !accepts(input.accepted, out.semantic_type) {
|
||||
return Err(error(
|
||||
"GRAPH_SOCKET_TYPE_MISMATCH",
|
||||
"socket type mismatch",
|
||||
@@ -782,15 +769,26 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
if !seen.insert(k.0) {
|
||||
return None;
|
||||
}
|
||||
if contracts[k.0].outputs[k.1 as usize].semantic_type == SemanticType::SceneTable {
|
||||
if contracts[k.0].key == "mesh" {
|
||||
let ordinal = contracts[k.0]
|
||||
.outputs
|
||||
.iter()
|
||||
.position(|output| output.semantic_type == SemanticType::MeshData)?;
|
||||
return Some(OutputKey(k.0, ordinal as u16));
|
||||
}
|
||||
if contracts[k.0].outputs[k.1 as usize].semantic_type == SemanticType::MeshData {
|
||||
return Some(k);
|
||||
}
|
||||
k = bound[k.0].get("scene")?.producer;
|
||||
k = bound[k.0]
|
||||
.get("mesh")
|
||||
.or_else(|| bound[k.0].get("pipelineIndices"))
|
||||
.or_else(|| bound[k.0].get("activation"))?
|
||||
.producer;
|
||||
}
|
||||
};
|
||||
for (i, c) in contracts.iter().enumerate() {
|
||||
if c.key == "frustum_cull"
|
||||
&& root(bound[i]["scene"].producer, &bound, &contracts)
|
||||
&& root(bound[i]["mesh"].producer, &bound, &contracts)
|
||||
!= root(bound[i]["localAabbs"].producer, &bound, &contracts)
|
||||
{
|
||||
return Err(error(
|
||||
@@ -799,11 +797,23 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
format!("nodes[{i}].inputs.localAabbs"),
|
||||
));
|
||||
}
|
||||
if matches!(c.key, "mesh_query" | "legacy_forward") {
|
||||
let scene = root(bound[i]["scene"].producer, &bound, &contracts);
|
||||
if matches!(c.key, "mesh_query" | "pipeline_registry" | "pipeline") {
|
||||
let scene_socket = if c.key == "pipeline_registry" {
|
||||
"pipelineIndices"
|
||||
} else {
|
||||
"mesh"
|
||||
};
|
||||
let scene = root(bound[i][scene_socket].producer, &bound, &contracts);
|
||||
for (s, b) in &bound[i] {
|
||||
if b.active
|
||||
&& matches!(*s, "isVisible" | "isFrustumCulled" | "draws")
|
||||
&& matches!(
|
||||
*s,
|
||||
"isVisible"
|
||||
| "isFrustumCulled"
|
||||
| "draws"
|
||||
| "pipelineIndices"
|
||||
| "activation"
|
||||
)
|
||||
&& root(b.producer, &bound, &contracts) != scene
|
||||
{
|
||||
return Err(error(
|
||||
@@ -853,7 +863,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
let mut stack: Vec<_> = contracts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, c)| c.inherently_observable)
|
||||
.filter(|(i, c)| c.inherently_observable && graph.nodes[*i].state == NodeState::Enabled)
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
while let Some(i) = stack.pop() {
|
||||
@@ -862,13 +872,26 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
}
|
||||
}
|
||||
// IDs are independent of scheduling: original node order, then contract output order.
|
||||
// Source nodes expose only outputs that survived active-edge/liveness analysis;
|
||||
// executable nodes retain their complete output shape for runtime lowering.
|
||||
let referenced_outputs: HashSet<_> = edges
|
||||
.iter()
|
||||
.filter(|edge| live.contains(&edge.to_node))
|
||||
.map(|edge| OutputKey(edge.from_node, edge.producer_output_ordinal))
|
||||
.collect();
|
||||
let mut output_ids = BTreeMap::new();
|
||||
let mut resource_meta = Vec::new();
|
||||
for i in 0..graph.nodes.len() {
|
||||
if live.contains(&i) {
|
||||
for (o, out) in contracts[i].outputs.iter().enumerate() {
|
||||
let key = OutputKey(i, o as u16);
|
||||
if contracts[i].execution == ExecutionClass::Source
|
||||
&& !referenced_outputs.contains(&key)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let id = resource_meta.len() as u32;
|
||||
output_ids.insert(OutputKey(i, o as u16), id);
|
||||
output_ids.insert(key, id);
|
||||
resource_meta.push((i, o as u16, *out));
|
||||
}
|
||||
}
|
||||
@@ -884,28 +907,10 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
}
|
||||
let source = output_ids.get(&OutputKey(i, 0)).copied();
|
||||
match ¶ms[i] {
|
||||
NormalizedParameters::SurfaceTarget => {
|
||||
let id = families.len() as u32;
|
||||
let r = source.unwrap();
|
||||
source_family.insert(OutputKey(i, 0), id);
|
||||
families.push(TextureFamily {
|
||||
id,
|
||||
key: TextureFamilyKey {
|
||||
source_node: i as u32,
|
||||
source_socket: 0,
|
||||
},
|
||||
source: TextureFamilySource::ImportedSurface { resource: r },
|
||||
lifetime: Lifetime {
|
||||
first_use: 0,
|
||||
last_use: 0,
|
||||
},
|
||||
versions: vec![],
|
||||
usage: vec![],
|
||||
allocation: None,
|
||||
aliasable: false,
|
||||
});
|
||||
}
|
||||
NormalizedParameters::TextureSpec { residency, texture } => {
|
||||
NormalizedParameters::Texture {
|
||||
residency,
|
||||
descriptor,
|
||||
} => {
|
||||
let id = families.len() as u32;
|
||||
let r = source.unwrap();
|
||||
source_family.insert(OutputKey(i, 0), id);
|
||||
@@ -918,7 +923,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
source: TextureFamilySource::AuthoredTexture {
|
||||
resource: r,
|
||||
residency: *residency,
|
||||
descriptor: texture.clone(),
|
||||
descriptor: descriptor.clone(),
|
||||
},
|
||||
lifetime: Lifetime {
|
||||
first_use: 0,
|
||||
@@ -940,7 +945,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
continue;
|
||||
}
|
||||
let transition_sockets: &[(&str, u16)] = match contracts[i].key {
|
||||
"legacy_forward" => &[("colorTarget", 0), ("depthTarget", 1)],
|
||||
"pipeline" => &[("colorTarget", 0), ("depthTarget", 1)],
|
||||
"fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur" | "bloom_composite"
|
||||
| "luminance_edge" => &[("colorTarget", 0)],
|
||||
_ => continue,
|
||||
@@ -1066,7 +1071,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
for input in contract
|
||||
.inputs
|
||||
.iter()
|
||||
.filter(|input| matches!(input.role, InputRole::Present | InputRole::SampledTexture))
|
||||
.filter(|input| matches!(input.role, InputRole::SampledTexture))
|
||||
{
|
||||
let key = bound[i][input.name].producer;
|
||||
if !version_of.contains_key(&key) {
|
||||
@@ -1103,7 +1108,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
if !live.contains(&i) {
|
||||
continue;
|
||||
}
|
||||
if contracts[i].key == "legacy_forward" {
|
||||
if contracts[i].key == "pipeline" {
|
||||
if bound[i]["colorTarget"].producer == bound[i]["depthTarget"].producer
|
||||
|| matches!((version_of.get(&OutputKey(i, 0)), version_of.get(&OutputKey(i, 1))), (Some((cf, _, _)), Some((df, _, _))) if cf == df)
|
||||
{
|
||||
@@ -1113,10 +1118,11 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
format!("nodes[{i}].inputs"),
|
||||
));
|
||||
}
|
||||
} else if contracts[i]
|
||||
.inputs
|
||||
.iter()
|
||||
.any(|input| matches!(input.role, InputRole::SampledTexture))
|
||||
} else if contracts[i].key != "frame_out"
|
||||
&& contracts[i]
|
||||
.inputs
|
||||
.iter()
|
||||
.any(|input| matches!(input.role, InputRole::SampledTexture))
|
||||
{
|
||||
let hazard = contracts[i].inputs.iter().filter(|input| matches!(input.role, InputRole::SampledTexture)).any(|input| matches!((version_of.get(&bound[i][input.name].producer), version_of.get(&OutputKey(i, 0))), (Some((sf, _, _)), Some((tf, _, _))) if sf == tf));
|
||||
if hazard {
|
||||
@@ -1176,7 +1182,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
|
||||
// Validate every independently resolved attachment before graph cycle reporting.
|
||||
for i in 0..graph.nodes.len() {
|
||||
if !live.contains(&i) || contracts[i].key != "legacy_forward" {
|
||||
if !live.contains(&i) || contracts[i].key != "pipeline" {
|
||||
continue;
|
||||
}
|
||||
let (Some(&(cf, _, _)), Some(&(df, _, _))) = (
|
||||
@@ -1185,33 +1191,19 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let cd = match &families[cf as usize].source {
|
||||
TextureFamilySource::AuthoredTexture { descriptor, .. } => Some(descriptor),
|
||||
_ => None,
|
||||
};
|
||||
let dd = match &families[df as usize].source {
|
||||
TextureFamilySource::AuthoredTexture { descriptor, .. } => descriptor,
|
||||
_ => {
|
||||
return Err(error(
|
||||
"GRAPH_ILLEGAL_ACCESS",
|
||||
"depth target must be authored",
|
||||
format!("nodes[{i}].inputs.depthTarget"),
|
||||
))
|
||||
}
|
||||
};
|
||||
let TextureFamilySource::AuthoredTexture { descriptor: cd, .. } =
|
||||
&families[cf as usize].source;
|
||||
let TextureFamilySource::AuthoredTexture { descriptor: dd, .. } =
|
||||
&families[df as usize].source;
|
||||
let ok_depth = dd.dimension == TextureDimension::D2
|
||||
&& dd.format == TextureFormat::Depth32Float
|
||||
&& dd.sample_count == 1
|
||||
&& extent_layers(&dd.extent) == 1;
|
||||
let ok_color = cd.is_none_or(|d| {
|
||||
d.format != TextureFormat::Depth32Float
|
||||
&& d.dimension == dd.dimension
|
||||
&& d.extent == dd.extent
|
||||
&& d.sample_count == 1
|
||||
});
|
||||
let surface_ok = cd.is_some()
|
||||
|| matches!(&dd.extent,NormalizedTextureExtent::SurfaceRelative{width,height,..} if *width==Ratio{numerator:1,denominator:1}&&*height==Ratio{numerator:1,denominator:1});
|
||||
if !ok_depth || !ok_color || !surface_ok {
|
||||
let ok_color = cd.format != TextureFormat::Depth32Float
|
||||
&& cd.dimension == dd.dimension
|
||||
&& cd.extent == dd.extent
|
||||
&& cd.sample_count == 1;
|
||||
if !ok_depth || !ok_color {
|
||||
return Err(error(
|
||||
"GRAPH_ILLEGAL_ACCESS",
|
||||
"attachments are incompatible",
|
||||
@@ -1222,6 +1214,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
|
||||
for i in 0..graph.nodes.len() {
|
||||
if !live.contains(&i)
|
||||
|| contracts[i].key == "frame_out"
|
||||
|| !contracts[i]
|
||||
.inputs
|
||||
.iter()
|
||||
@@ -1242,19 +1235,11 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
};
|
||||
let source_descriptor = match &families[source_family_id as usize].source {
|
||||
TextureFamilySource::AuthoredTexture { descriptor, .. } => descriptor,
|
||||
TextureFamilySource::ImportedSurface { .. } => {
|
||||
return Err(error(
|
||||
"GRAPH_ILLEGAL_ACCESS",
|
||||
"copy source must be an authored texture",
|
||||
format!("nodes[{i}].inputs.source"),
|
||||
))
|
||||
}
|
||||
};
|
||||
let source_ok = source_descriptor.format == TextureFormat::Rgba16Float
|
||||
&& is_single_view_d2(source_descriptor);
|
||||
let target_descriptor = match &families[target_family_id as usize].source {
|
||||
TextureFamilySource::AuthoredTexture { descriptor, .. } => Some(descriptor),
|
||||
TextureFamilySource::ImportedSurface { .. } => None,
|
||||
};
|
||||
let authored_target_ok = target_descriptor.is_some_and(|descriptor| {
|
||||
descriptor.format == TextureFormat::Rgba16Float && is_single_view_d2(descriptor)
|
||||
@@ -1272,13 +1257,6 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
TextureFamilySource::AuthoredTexture { descriptor, .. } => {
|
||||
descriptor.format == TextureFormat::Rgba16Float && is_single_view_d2(descriptor)
|
||||
}
|
||||
TextureFamilySource::ImportedSurface { .. } => {
|
||||
return Err(error(
|
||||
"GRAPH_ILLEGAL_ACCESS",
|
||||
"bloom source must be an authored texture",
|
||||
format!("nodes[{i}].inputs.bloom"),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
true
|
||||
@@ -1295,7 +1273,12 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
&& descriptor.extent == source_descriptor.extent
|
||||
})
|
||||
}
|
||||
"tone_map" => target_descriptor.is_none() && source_is_full_surface,
|
||||
"tone_map" => target_descriptor.is_some_and(|descriptor| {
|
||||
descriptor.format != TextureFormat::Depth32Float
|
||||
&& descriptor.format != TextureFormat::R32Float
|
||||
&& is_single_view_d2(descriptor)
|
||||
&& descriptor.extent == source_descriptor.extent
|
||||
}),
|
||||
"bloom_extract" => authored_target_ok,
|
||||
"bloom_blur" | "luminance_edge" => authored_target_ok && target_matches_source,
|
||||
"bloom_composite" => authored_target_ok && target_matches_source && bloom_input_ok,
|
||||
@@ -1310,30 +1293,29 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialization and presentation legality are later than attachment compatibility.
|
||||
// Frame output must consume an initialized, produced, filterable color texture.
|
||||
for (i, contract) in contracts.iter().enumerate() {
|
||||
if !live.contains(&i) || contract.key != "present" {
|
||||
if !live.contains(&i) || contract.key != "frame_out" {
|
||||
continue;
|
||||
}
|
||||
let key = bound[i]["surface"].producer;
|
||||
let key = bound[i]["color"].producer;
|
||||
let Some(&(family, _, _)) = version_of.get(&key) else {
|
||||
if !matches!(resolved.get(&key), Some(ResolvedTransition::Cyclic)) {
|
||||
return Err(error(
|
||||
"GRAPH_UNINITIALIZED_RESOURCE",
|
||||
"present source is not produced",
|
||||
format!("nodes[{i}].inputs.surface"),
|
||||
"frame output source is not produced",
|
||||
format!("nodes[{i}].inputs.color"),
|
||||
));
|
||||
}
|
||||
continue;
|
||||
};
|
||||
if !matches!(
|
||||
families[family as usize].source,
|
||||
TextureFamilySource::ImportedSurface { .. }
|
||||
) {
|
||||
let TextureFamilySource::AuthoredTexture { descriptor, .. } =
|
||||
&families[family as usize].source;
|
||||
if !is_filterable_frame_color(descriptor) {
|
||||
return Err(error(
|
||||
"GRAPH_ILLEGAL_ACCESS",
|
||||
"offscreen textures cannot be presented",
|
||||
format!("nodes[{i}].inputs.surface"),
|
||||
"frame output requires a filterable single-view d2 color texture",
|
||||
format!("nodes[{i}].inputs.color"),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1463,17 +1445,18 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
for (i, o, out) in resource_meta {
|
||||
let key = OutputKey(i, o);
|
||||
let id = output_ids[&key];
|
||||
let scene = || output_ids[&root(bound[i]["scene"].producer, &bound, &contracts).unwrap()];
|
||||
let mesh = || output_ids[&root(key, &bound, &contracts).unwrap()];
|
||||
let plan = match out.semantic_type {
|
||||
SemanticType::SurfaceTarget => ResourcePlan::SurfaceTarget {
|
||||
family: source_family[&key],
|
||||
},
|
||||
SemanticType::TextureSpec => {
|
||||
if let NormalizedParameters::TextureSpec { residency, texture } = ¶ms[i] {
|
||||
ResourcePlan::TextureSpec {
|
||||
SemanticType::Texture if matches!(params[i], NormalizedParameters::Texture { .. }) => {
|
||||
if let NormalizedParameters::Texture {
|
||||
residency,
|
||||
descriptor,
|
||||
} = ¶ms[i]
|
||||
{
|
||||
ResourcePlan::TextureSource {
|
||||
family: source_family[&key],
|
||||
residency: *residency,
|
||||
descriptor: texture.clone(),
|
||||
descriptor: descriptor.clone(),
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
@@ -1490,27 +1473,20 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
allocation: None,
|
||||
}
|
||||
}
|
||||
SemanticType::SceneTable => ResourcePlan::SceneTable,
|
||||
SemanticType::LocalAabbBuffer => ResourcePlan::LocalAabbBuffer { scene: scene() },
|
||||
SemanticType::CameraFrustum => ResourcePlan::CameraFrustum,
|
||||
SemanticType::MeshData => ResourcePlan::MeshData,
|
||||
SemanticType::LocalAabbBuffer => ResourcePlan::LocalAabbBuffer { mesh: mesh() },
|
||||
SemanticType::BooleanFlagBuffer => {
|
||||
if let OutputMetadata::BooleanFlag { flag } = out.metadata {
|
||||
ResourcePlan::BooleanFlagBuffer {
|
||||
scene: scene(),
|
||||
flag,
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
SemanticType::DrawStream => ResourcePlan::DrawStream { scene: scene() },
|
||||
SemanticType::DepthStencilConfig => {
|
||||
if let NormalizedParameters::DepthStencilConfig { config } = ¶ms[i] {
|
||||
ResourcePlan::DepthStencilConfig { config: *config }
|
||||
ResourcePlan::BooleanFlagBuffer { mesh: mesh(), flag }
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
SemanticType::PipelineIndexStream => ResourcePlan::PipelineIndexStream { mesh: mesh() },
|
||||
SemanticType::PipelineActivation => ResourcePlan::PipelineActivation {
|
||||
pipeline_indices: output_ids[&bound[i]["pipelineIndices"].producer],
|
||||
},
|
||||
SemanticType::DrawStream => ResourcePlan::DrawStream { mesh: mesh() },
|
||||
};
|
||||
resources.push(CompiledResource {
|
||||
original_node_index: i as u32,
|
||||
@@ -1557,9 +1533,8 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
let kind = match contracts[i].key {
|
||||
"frustum_cull" => {
|
||||
for (s, m) in [
|
||||
("scene", AccessMode::StorageRead),
|
||||
("mesh", AccessMode::StorageRead),
|
||||
("localAabbs", AccessMode::StorageRead),
|
||||
("frustum", AccessMode::UniformRead),
|
||||
] {
|
||||
accesses.push(CompiledAccess {
|
||||
socket: s.into(),
|
||||
@@ -1569,7 +1544,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
}
|
||||
let r = output_ids[&OutputKey(i, 0)];
|
||||
accesses.push(CompiledAccess {
|
||||
socket: "flags".into(),
|
||||
socket: "isFrustumCulled".into(),
|
||||
resource: r,
|
||||
mode: AccessMode::StorageWrite {
|
||||
full_overwrite: true,
|
||||
@@ -1580,7 +1555,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
}
|
||||
}
|
||||
"mesh_query" => {
|
||||
for s in ["scene", "isVisible", "isFrustumCulled"] {
|
||||
for s in ["mesh", "isVisible", "isFrustumCulled"] {
|
||||
if let Some(b) = bound[i].get(s).filter(|b| b.active) {
|
||||
accesses.push(CompiledAccess {
|
||||
socket: s.into(),
|
||||
@@ -1600,23 +1575,38 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
work: ComputeWork::MeshQuery,
|
||||
}
|
||||
}
|
||||
"legacy_forward" => {
|
||||
"pipeline_registry" => {
|
||||
accesses.push(CompiledAccess {
|
||||
socket: "pipelineIndices".into(),
|
||||
resource: input_resource("pipelineIndices"),
|
||||
mode: AccessMode::SemanticRead,
|
||||
});
|
||||
ExecutionKind::CpuPreparation
|
||||
}
|
||||
"pipeline" => {
|
||||
let color = output_ids[&OutputKey(i, 0)];
|
||||
let depth = output_ids[&OutputKey(i, 1)];
|
||||
let clear = match params[i] {
|
||||
NormalizedParameters::LegacyForward { clear_color } => clear_color,
|
||||
NormalizedParameters::Pipeline { clear_color, .. } => clear_color,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let config_node = bound[i]["depthStencil"].producer.0;
|
||||
let dc = match params[config_node] {
|
||||
NormalizedParameters::DepthStencilConfig { config } => config,
|
||||
let clear_depth = match ¶ms[i] {
|
||||
NormalizedParameters::Pipeline { clear_depth, .. } => *clear_depth,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let cl = NormalizedColorLoad::Clear { value: clear };
|
||||
let dl = NormalizedDepthLoad::Clear {
|
||||
value: dc.clear_depth,
|
||||
let first_color = version_of[&OutputKey(i, 0)].1 == 0;
|
||||
let first_depth = version_of[&OutputKey(i, 1)].1 == 0;
|
||||
let cl = if first_color {
|
||||
NormalizedColorLoad::Clear { value: clear }
|
||||
} else {
|
||||
NormalizedColorLoad::Load
|
||||
};
|
||||
for s in ["scene", "draws"] {
|
||||
let dl = if first_depth {
|
||||
NormalizedDepthLoad::Clear { value: clear_depth }
|
||||
} else {
|
||||
NormalizedDepthLoad::Load
|
||||
};
|
||||
for s in ["mesh", "draws", "activation"] {
|
||||
accesses.push(CompiledAccess {
|
||||
socket: s.into(),
|
||||
resource: input_resource(s),
|
||||
@@ -1634,7 +1624,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
location: 0,
|
||||
load: cl,
|
||||
store: StoreOp::Store,
|
||||
full_overwrite: true,
|
||||
full_overwrite: first_color,
|
||||
},
|
||||
});
|
||||
accesses.push(CompiledAccess {
|
||||
@@ -1643,7 +1633,7 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
mode: AccessMode::DepthAttachment {
|
||||
load: dl,
|
||||
store: StoreOp::Store,
|
||||
full_overwrite: true,
|
||||
full_overwrite: first_depth,
|
||||
},
|
||||
});
|
||||
ExecutionKind::Render {
|
||||
@@ -1698,14 +1688,14 @@ pub fn compile(graph: Graph) -> Result<CompiledGraph, GraphError> {
|
||||
depth_stencil: None,
|
||||
}
|
||||
}
|
||||
"present" => {
|
||||
let r = input_resource("surface");
|
||||
"frame_out" => {
|
||||
let r = input_resource("color");
|
||||
accesses.push(CompiledAccess {
|
||||
socket: "surface".into(),
|
||||
socket: "color".into(),
|
||||
resource: r,
|
||||
mode: AccessMode::Present,
|
||||
mode: AccessMode::SampledTexture,
|
||||
});
|
||||
ExecutionKind::Present { surface: r }
|
||||
ExecutionKind::FrameOut { color: r }
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
@@ -1803,13 +1793,27 @@ fn extent_layers(e: &NormalizedTextureExtent) -> u32 {
|
||||
} => *depth_or_array_layers,
|
||||
}
|
||||
}
|
||||
fn is_single_view_d2(descriptor: &NormalizedTextureDescriptor) -> bool {
|
||||
pub(super) fn is_single_view_d2(descriptor: &NormalizedTextureDescriptor) -> bool {
|
||||
descriptor.dimension == TextureDimension::D2
|
||||
&& descriptor.sample_count == 1
|
||||
&& descriptor.mip_level_count == 1
|
||||
&& extent_layers(&descriptor.extent) == 1
|
||||
}
|
||||
fn texture_usage(f: &TextureFamily, executions: &[CompiledExecution]) -> Vec<TextureUsage> {
|
||||
pub(super) fn is_filterable_frame_color(descriptor: &NormalizedTextureDescriptor) -> bool {
|
||||
is_single_view_d2(descriptor)
|
||||
&& matches!(
|
||||
descriptor.format,
|
||||
TextureFormat::Rgba8Unorm
|
||||
| TextureFormat::Rgba8UnormSrgb
|
||||
| TextureFormat::Bgra8Unorm
|
||||
| TextureFormat::Bgra8UnormSrgb
|
||||
| TextureFormat::Rgba16Float
|
||||
)
|
||||
}
|
||||
pub(super) fn texture_usage(
|
||||
f: &TextureFamily,
|
||||
executions: &[CompiledExecution],
|
||||
) -> Vec<TextureUsage> {
|
||||
let rs: HashSet<_> = f.versions.iter().map(|v| v.resource).collect();
|
||||
let mut u = BTreeSet::new();
|
||||
for e in executions {
|
||||
@@ -1842,19 +1846,18 @@ fn allocate(
|
||||
) -> (Vec<AllocationClass>, u32) {
|
||||
let mut grouped: BTreeMap<TextureCompatibilityKey, Vec<usize>> = BTreeMap::new();
|
||||
for (i, f) in families.iter().enumerate() {
|
||||
if let TextureFamilySource::AuthoredTexture { descriptor, .. } = &f.source {
|
||||
grouped
|
||||
.entry(TextureCompatibilityKey {
|
||||
dimension: descriptor.dimension,
|
||||
format: descriptor.format,
|
||||
extent: descriptor.extent.clone(),
|
||||
mip_level_count: descriptor.mip_level_count,
|
||||
sample_count: descriptor.sample_count,
|
||||
view_formats: descriptor.view_formats.clone(),
|
||||
})
|
||||
.or_default()
|
||||
.push(i);
|
||||
}
|
||||
let TextureFamilySource::AuthoredTexture { descriptor, .. } = &f.source;
|
||||
grouped
|
||||
.entry(TextureCompatibilityKey {
|
||||
dimension: descriptor.dimension,
|
||||
format: descriptor.format,
|
||||
extent: descriptor.extent.clone(),
|
||||
mip_level_count: descriptor.mip_level_count,
|
||||
sample_count: descriptor.sample_count,
|
||||
view_formats: descriptor.view_formats.clone(),
|
||||
})
|
||||
.or_default()
|
||||
.push(i);
|
||||
}
|
||||
let mut classes = Vec::new();
|
||||
let mut transient = 0;
|
||||
|
||||
@@ -3,15 +3,13 @@ use super::MeshFlag;
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SemanticType {
|
||||
SurfaceTarget,
|
||||
TextureSpec,
|
||||
MeshData,
|
||||
Texture,
|
||||
SceneTable,
|
||||
LocalAabbBuffer,
|
||||
CameraFrustum,
|
||||
BooleanFlagBuffer,
|
||||
PipelineIndexStream,
|
||||
PipelineActivation,
|
||||
DrawStream,
|
||||
DepthStencilConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||
@@ -21,7 +19,7 @@ pub enum ExecutionClass {
|
||||
CpuPreparation,
|
||||
Compute,
|
||||
Render,
|
||||
Present,
|
||||
Frame,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||
@@ -48,8 +46,6 @@ pub enum InputRole {
|
||||
SampledTexture,
|
||||
ColorTarget { location: u32 },
|
||||
DepthTarget,
|
||||
Present,
|
||||
Configuration,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
|
||||
@@ -119,48 +115,42 @@ 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 TEXTURE_OUT: &[OutputSocketContract] = &[output("texture", Texture, OutputMetadata::None)];
|
||||
const MESH_OUT: &[OutputSocketContract] = &[
|
||||
output("mesh", MeshData, OutputMetadata::None),
|
||||
output("localAabbs", LocalAabbBuffer, OutputMetadata::None),
|
||||
output(
|
||||
"isVisible",
|
||||
BooleanFlagBuffer,
|
||||
OutputMetadata::BooleanFlag {
|
||||
flag: MeshFlag::IsVisible,
|
||||
},
|
||||
),
|
||||
output("pipelineIndices", PipelineIndexStream, OutputMetadata::None),
|
||||
];
|
||||
const CULLED_OUT: &[OutputSocketContract] = &[output(
|
||||
"flags",
|
||||
"isFrustumCulled",
|
||||
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] = &[
|
||||
const ACTIVATION_OUT: &[OutputSocketContract] = &[output(
|
||||
"activation",
|
||||
PipelineActivation,
|
||||
OutputMetadata::None,
|
||||
)];
|
||||
const PIPELINE_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),
|
||||
"mesh",
|
||||
TypeConstraint::Exact(MeshData),
|
||||
REQUIRED,
|
||||
InputRole::StorageRead,
|
||||
),
|
||||
@@ -170,17 +160,11 @@ const CULL_IN: &[InputSocketContract] = &[
|
||||
REQUIRED,
|
||||
InputRole::StorageRead,
|
||||
),
|
||||
input(
|
||||
"frustum",
|
||||
TypeConstraint::Exact(CameraFrustum),
|
||||
REQUIRED,
|
||||
InputRole::UniformRead,
|
||||
),
|
||||
];
|
||||
const QUERY_IN: &[InputSocketContract] = &[
|
||||
input(
|
||||
"scene",
|
||||
TypeConstraint::Exact(SceneTable),
|
||||
"mesh",
|
||||
TypeConstraint::Exact(MeshData),
|
||||
REQUIRED,
|
||||
InputRole::StorageRead,
|
||||
),
|
||||
@@ -197,10 +181,16 @@ const QUERY_IN: &[InputSocketContract] = &[
|
||||
InputRole::StorageRead,
|
||||
),
|
||||
];
|
||||
const FORWARD_IN: &[InputSocketContract] = &[
|
||||
const REGISTRY_IN: &[InputSocketContract] = &[input(
|
||||
"pipelineIndices",
|
||||
TypeConstraint::Exact(PipelineIndexStream),
|
||||
REQUIRED,
|
||||
InputRole::SemanticRead,
|
||||
)];
|
||||
const PIPELINE_IN: &[InputSocketContract] = &[
|
||||
input(
|
||||
"scene",
|
||||
TypeConstraint::Exact(SceneTable),
|
||||
"mesh",
|
||||
TypeConstraint::Exact(MeshData),
|
||||
REQUIRED,
|
||||
InputRole::SemanticRead,
|
||||
),
|
||||
@@ -210,24 +200,24 @@ const FORWARD_IN: &[InputSocketContract] = &[
|
||||
REQUIRED,
|
||||
InputRole::IndirectRead,
|
||||
),
|
||||
input(
|
||||
"activation",
|
||||
TypeConstraint::Exact(PipelineActivation),
|
||||
REQUIRED,
|
||||
InputRole::SemanticRead,
|
||||
),
|
||||
input(
|
||||
"colorTarget",
|
||||
TypeConstraint::OneOf(&[SurfaceTarget, TextureSpec, Texture]),
|
||||
TypeConstraint::Exact(Texture),
|
||||
REQUIRED,
|
||||
InputRole::ColorTarget { location: 0 },
|
||||
),
|
||||
input(
|
||||
"depthTarget",
|
||||
TypeConstraint::OneOf(&[TextureSpec, Texture]),
|
||||
TypeConstraint::Exact(Texture),
|
||||
REQUIRED,
|
||||
InputRole::DepthTarget,
|
||||
),
|
||||
input(
|
||||
"depthStencil",
|
||||
TypeConstraint::Exact(DepthStencilConfig),
|
||||
REQUIRED,
|
||||
InputRole::Configuration,
|
||||
),
|
||||
];
|
||||
const FULLSCREEN_COPY_IN: &[InputSocketContract] = &[
|
||||
input(
|
||||
@@ -238,7 +228,7 @@ const FULLSCREEN_COPY_IN: &[InputSocketContract] = &[
|
||||
),
|
||||
input(
|
||||
"colorTarget",
|
||||
TypeConstraint::OneOf(&[SurfaceTarget, TextureSpec, Texture]),
|
||||
TypeConstraint::Exact(Texture),
|
||||
REQUIRED,
|
||||
InputRole::ColorTarget { location: 0 },
|
||||
),
|
||||
@@ -258,65 +248,33 @@ const BLOOM_COMPOSITE_IN: &[InputSocketContract] = &[
|
||||
),
|
||||
input(
|
||||
"colorTarget",
|
||||
TypeConstraint::OneOf(&[SurfaceTarget, TextureSpec, Texture]),
|
||||
TypeConstraint::Exact(Texture),
|
||||
REQUIRED,
|
||||
InputRole::ColorTarget { location: 0 },
|
||||
),
|
||||
];
|
||||
const PRESENT_IN: &[InputSocketContract] = &[input(
|
||||
"surface",
|
||||
const FRAME_OUT_IN: &[InputSocketContract] = &[input(
|
||||
"color",
|
||||
TypeConstraint::Exact(Texture),
|
||||
REQUIRED,
|
||||
InputRole::Present,
|
||||
InputRole::SampledTexture,
|
||||
)];
|
||||
|
||||
pub static CONTRACTS: &[Contract] = &[
|
||||
Contract {
|
||||
key: "surface_target",
|
||||
key: "mesh",
|
||||
version: 1,
|
||||
execution: ExecutionClass::Source,
|
||||
inputs: NONE_IN,
|
||||
outputs: SURFACE_OUT,
|
||||
outputs: MESH_OUT,
|
||||
inherently_observable: false,
|
||||
},
|
||||
Contract {
|
||||
key: "texture_spec",
|
||||
key: "texture",
|
||||
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,
|
||||
outputs: TEXTURE_OUT,
|
||||
inherently_observable: false,
|
||||
},
|
||||
Contract {
|
||||
@@ -336,19 +294,19 @@ pub static CONTRACTS: &[Contract] = &[
|
||||
inherently_observable: false,
|
||||
},
|
||||
Contract {
|
||||
key: "depth_stencil_config",
|
||||
key: "pipeline_registry",
|
||||
version: 1,
|
||||
execution: ExecutionClass::Source,
|
||||
inputs: NONE_IN,
|
||||
outputs: CONFIG_OUT,
|
||||
execution: ExecutionClass::CpuPreparation,
|
||||
inputs: REGISTRY_IN,
|
||||
outputs: ACTIVATION_OUT,
|
||||
inherently_observable: false,
|
||||
},
|
||||
Contract {
|
||||
key: "legacy_forward",
|
||||
key: "pipeline",
|
||||
version: 1,
|
||||
execution: ExecutionClass::Render,
|
||||
inputs: FORWARD_IN,
|
||||
outputs: FORWARD_OUT,
|
||||
inputs: PIPELINE_IN,
|
||||
outputs: PIPELINE_OUT,
|
||||
inherently_observable: false,
|
||||
},
|
||||
Contract {
|
||||
@@ -400,10 +358,10 @@ pub static CONTRACTS: &[Contract] = &[
|
||||
inherently_observable: false,
|
||||
},
|
||||
Contract {
|
||||
key: "present",
|
||||
key: "frame_out",
|
||||
version: 1,
|
||||
execution: ExecutionClass::Present,
|
||||
inputs: PRESENT_IN,
|
||||
execution: ExecutionClass::Frame,
|
||||
inputs: FRAME_OUT_IN,
|
||||
outputs: NONE_OUT,
|
||||
inherently_observable: true,
|
||||
},
|
||||
|
||||
@@ -49,4 +49,4 @@ impl GraphError {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub(crate) mod tests;
|
||||
|
||||
@@ -33,10 +33,7 @@ pub struct CompiledResource {
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum ResourcePlan {
|
||||
SurfaceTarget {
|
||||
family: u32,
|
||||
},
|
||||
TextureSpec {
|
||||
TextureSource {
|
||||
family: u32,
|
||||
residency: TextureResidency,
|
||||
descriptor: NormalizedTextureDescriptor,
|
||||
@@ -49,20 +46,22 @@ pub enum ResourcePlan {
|
||||
stored: bool,
|
||||
allocation: Option<AllocationRef>,
|
||||
},
|
||||
SceneTable,
|
||||
MeshData,
|
||||
LocalAabbBuffer {
|
||||
scene: u32,
|
||||
mesh: u32,
|
||||
},
|
||||
CameraFrustum,
|
||||
BooleanFlagBuffer {
|
||||
scene: u32,
|
||||
mesh: u32,
|
||||
flag: MeshFlag,
|
||||
},
|
||||
DrawStream {
|
||||
scene: u32,
|
||||
PipelineIndexStream {
|
||||
mesh: u32,
|
||||
},
|
||||
DepthStencilConfig {
|
||||
config: NormalizedDepthStencil,
|
||||
PipelineActivation {
|
||||
pipeline_indices: u32,
|
||||
},
|
||||
DrawStream {
|
||||
mesh: u32,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -104,12 +103,12 @@ pub enum ExecutionKind {
|
||||
color_attachments: Vec<ColorAttachmentPlan>,
|
||||
depth_stencil: Option<DepthStencilAttachmentPlan>,
|
||||
},
|
||||
Present {
|
||||
surface: u32,
|
||||
FrameOut {
|
||||
color: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ComputeWork {
|
||||
FrustumCull,
|
||||
@@ -184,29 +183,29 @@ pub enum AccessMode {
|
||||
store: StoreOp,
|
||||
full_overwrite: bool,
|
||||
},
|
||||
Present,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum NormalizedParameters {
|
||||
SurfaceTarget,
|
||||
TextureSpec {
|
||||
Texture {
|
||||
residency: TextureResidency,
|
||||
texture: NormalizedTextureDescriptor,
|
||||
descriptor: NormalizedTextureDescriptor,
|
||||
},
|
||||
Mesh,
|
||||
FrustumCull {
|
||||
camera: ActiveCamera,
|
||||
},
|
||||
SceneTable,
|
||||
LocalAabbBuffer,
|
||||
CameraFrustum,
|
||||
VisibilityFlags,
|
||||
FrustumCull,
|
||||
MeshQuery {
|
||||
filters: [NormalizedMeshFilter; 2],
|
||||
visible_predicate: RuntimePredicate,
|
||||
frustum_culled_predicate: RuntimePredicate,
|
||||
},
|
||||
DepthStencilConfig {
|
||||
config: NormalizedDepthStencil,
|
||||
},
|
||||
LegacyForward {
|
||||
PipelineRegistry,
|
||||
Pipeline {
|
||||
pipeline: String,
|
||||
depth_compare: CompareFunction,
|
||||
depth_write_enabled: bool,
|
||||
clear_depth: f32,
|
||||
clear_color: [f64; 4],
|
||||
},
|
||||
FullscreenCopy,
|
||||
@@ -227,22 +226,13 @@ pub enum NormalizedParameters {
|
||||
LuminanceEdge {
|
||||
strength: f32,
|
||||
},
|
||||
Present,
|
||||
FrameOut,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NormalizedMeshFilter {
|
||||
pub flag: MeshFlag,
|
||||
pub predicate: TriStatePredicate,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NormalizedDepthStencil {
|
||||
pub depth_compare: CompareFunction,
|
||||
pub depth_write_enabled: bool,
|
||||
pub clear_depth: f32,
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ActiveCamera {
|
||||
Active,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
|
||||
@@ -288,9 +278,6 @@ pub struct TextureFamilyKey {
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum TextureFamilySource {
|
||||
ImportedSurface {
|
||||
resource: u32,
|
||||
},
|
||||
AuthoredTexture {
|
||||
resource: u32,
|
||||
residency: TextureResidency,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -114,11 +114,13 @@ pub enum TriStatePredicate {
|
||||
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 = "snake_case")]
|
||||
pub enum RuntimePredicate {
|
||||
Any,
|
||||
RequiredTrue,
|
||||
RequiredFalse,
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
||||
+1967
-466
File diff suppressed because it is too large
Load Diff
@@ -42,10 +42,14 @@ fn mesh_query(@builtin(global_invocation_id) id: vec3<u32>) {
|
||||
if (i >= params.count) { return; }
|
||||
let draw_meta = metadata[i];
|
||||
var selected = true;
|
||||
if (params.visible_predicate != 0u) {
|
||||
if (params.visible_predicate == 3u) {
|
||||
selected = false;
|
||||
} else if (params.visible_predicate != 0u) {
|
||||
selected = matches(authored_visible[i], params.visible_predicate);
|
||||
}
|
||||
if (params.frustum_predicate != 0u) {
|
||||
if (selected && params.frustum_predicate == 3u) {
|
||||
selected = false;
|
||||
} else if (selected && params.frustum_predicate != 0u) {
|
||||
selected = selected && matches(frustum_flags[i], params.frustum_predicate);
|
||||
}
|
||||
commands[i] = Command(draw_meta.index_count, select(0u, 1u, selected), draw_meta.first_index, draw_meta.base_vertex, 0u);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
mod legacy_forward;
|
||||
mod pipeline;
|
||||
|
||||
pub(super) use legacy_forward::{encode_compiled, encode_immediate};
|
||||
pub(super) use pipeline::{encode_compiled, encode_immediate};
|
||||
|
||||
+48
-48
@@ -56,25 +56,8 @@ pub(crate) fn encode_compiled<T: Scene>(
|
||||
materials: &MaterialResources,
|
||||
mut profile: Option<&mut crate::renderer::profiler::ProfileFrame>,
|
||||
) -> Result<(), &'static str> {
|
||||
use crate::render_graph::{
|
||||
ExecutionKind, NormalizedColorLoad, NormalizedDepthLoad, ResourcePlan, StoreOp,
|
||||
};
|
||||
use crate::render_graph::{ExecutionKind, NormalizedColorLoad, NormalizedDepthLoad, StoreOp};
|
||||
let view = |resource: u32| -> Result<&wgpu::TextureView, &'static str> {
|
||||
let is_surface = active
|
||||
.graph
|
||||
.resources
|
||||
.get(resource as usize)
|
||||
.is_some_and(|resource| {
|
||||
matches!(
|
||||
resource.plan,
|
||||
ResourcePlan::SurfaceTarget { family }
|
||||
| ResourcePlan::Texture { family, .. }
|
||||
if family == active.runtime.allocations.surface_family
|
||||
)
|
||||
});
|
||||
if is_surface {
|
||||
return Ok(surface);
|
||||
}
|
||||
let a = active
|
||||
.runtime
|
||||
.allocations
|
||||
@@ -93,15 +76,16 @@ pub(crate) fn encode_compiled<T: Scene>(
|
||||
for (execution_index, prepared) in active.executions.iter().enumerate() {
|
||||
let profile_id = &active.graph.executions[execution_index].id;
|
||||
match prepared {
|
||||
PreparedExecution::PipelineRegistry => {}
|
||||
PreparedExecution::FrustumCull => {
|
||||
gpu.encode_frustum_cull(encoder, profile.as_deref_mut(), profile_id);
|
||||
}
|
||||
PreparedExecution::MeshQuery => {
|
||||
gpu.encode_mesh_query(encoder, profile.as_deref_mut(), profile_id);
|
||||
}
|
||||
PreparedExecution::Present => {}
|
||||
PreparedExecution::Fullscreen {
|
||||
execution,
|
||||
frame_out,
|
||||
bind_group,
|
||||
pipeline,
|
||||
..
|
||||
@@ -111,22 +95,30 @@ pub(crate) fn encode_compiled<T: Scene>(
|
||||
.executions
|
||||
.get(*execution)
|
||||
.ok_or(" execution out of bounds")?;
|
||||
let ExecutionKind::Render {
|
||||
color_attachments, ..
|
||||
} = &execution.kind
|
||||
else {
|
||||
return Err("fullscreen is not render");
|
||||
};
|
||||
let color = color_attachments
|
||||
.first()
|
||||
.ok_or("fullscreen target missing")?;
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some(&execution.id),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: view(color.resource)?,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
let (target, operations) = if *frame_out {
|
||||
let ExecutionKind::FrameOut { .. } = execution.kind else {
|
||||
return Err("frame_out kind mismatch");
|
||||
};
|
||||
(
|
||||
surface,
|
||||
wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
let ExecutionKind::Render {
|
||||
color_attachments, ..
|
||||
} = &execution.kind
|
||||
else {
|
||||
return Err("fullscreen is not render");
|
||||
};
|
||||
let color = color_attachments
|
||||
.first()
|
||||
.ok_or("fullscreen target missing")?;
|
||||
(
|
||||
view(color.resource)?,
|
||||
wgpu::Operations {
|
||||
load: match color.load {
|
||||
NormalizedColorLoad::Load => wgpu::LoadOp::Load,
|
||||
NormalizedColorLoad::Clear { value } => {
|
||||
@@ -144,6 +136,15 @@ pub(crate) fn encode_compiled<T: Scene>(
|
||||
wgpu::StoreOp::Discard
|
||||
},
|
||||
},
|
||||
)
|
||||
};
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some(&execution.id),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: operations,
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
@@ -155,9 +156,10 @@ pub(crate) fn encode_compiled<T: Scene>(
|
||||
pass.set_bind_group(0, bind_group, &[]);
|
||||
pass.draw(0..3, 0..1);
|
||||
}
|
||||
PreparedExecution::LegacyForward {
|
||||
PreparedExecution::Pipeline {
|
||||
execution,
|
||||
variants,
|
||||
base,
|
||||
variant,
|
||||
} => {
|
||||
let execution = active
|
||||
.graph
|
||||
@@ -169,10 +171,10 @@ pub(crate) fn encode_compiled<T: Scene>(
|
||||
depth_stencil,
|
||||
} = &execution.kind
|
||||
else {
|
||||
return Err("legacy forward is not render");
|
||||
return Err("pipeline is not render");
|
||||
};
|
||||
let color = color_attachments.first().ok_or("legacy color missing")?;
|
||||
let depth = depth_stencil.as_ref().ok_or("legacy depth missing")?;
|
||||
let color = color_attachments.first().ok_or("pipeline color missing")?;
|
||||
let depth = depth_stencil.as_ref().ok_or("pipeline depth missing")?;
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some(&execution.id),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
@@ -235,16 +237,14 @@ pub(crate) fn encode_compiled<T: Scene>(
|
||||
pass.set_vertex_buffer(4, t.slice(..));
|
||||
pass.set_index_buffer(ix.slice(..), wgpu::IndexFormat::Uint32);
|
||||
for draw in &gpu.draws {
|
||||
if draw.pipeline != *base {
|
||||
continue;
|
||||
}
|
||||
let slot = draw.instances.start as u64;
|
||||
let start = slot
|
||||
* std::mem::size_of::<crate::renderer::gpu_scene::GpuInstance>() as u64;
|
||||
pass.set_vertex_buffer(3, inst.slice(start..start + 112));
|
||||
let key = variants
|
||||
.iter()
|
||||
.find(|(base, _)| *base == draw.pipeline)
|
||||
.map(|x| &x.1)
|
||||
.ok_or("pipeline variant missing")?;
|
||||
pass.set_pipeline(key);
|
||||
pass.set_pipeline(variant);
|
||||
if pipelines.requires_material(draw.pipeline) {
|
||||
pass.set_bind_group(2, materials.group(draw.material), &[]);
|
||||
}
|
||||
@@ -274,7 +274,7 @@ pub(crate) fn encode_immediate<T: Scene>(
|
||||
profile: Option<&mut crate::renderer::profiler::ProfileFrame>,
|
||||
) {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render pass"),
|
||||
label: Some("Immediate pipeline pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
depth_slice: None,
|
||||
view: color,
|
||||
@@ -298,7 +298,7 @@ pub(crate) fn encode_immediate<T: Scene>(
|
||||
stencil_ops: None,
|
||||
}),
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: profile.and_then(|p| p.render_writes("immediate.forward")),
|
||||
timestamp_writes: profile.and_then(|p| p.render_writes("immediate.pipeline")),
|
||||
});
|
||||
encode_scene(&mut pass, scene, gpu, pipelines, materials);
|
||||
}
|
||||
@@ -85,8 +85,8 @@ impl GpuScenePlan {
|
||||
self::GpuScenePlan::build_with_query(
|
||||
data,
|
||||
crate::render_graph::MeshQueryRuntimeKey {
|
||||
visible: crate::render_graph::TriStatePredicate::RequiredTrue,
|
||||
frustum_culled: crate::render_graph::TriStatePredicate::Any,
|
||||
visible: crate::render_graph::RuntimePredicate::RequiredTrue,
|
||||
frustum_culled: crate::render_graph::RuntimePredicate::Any,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -277,7 +277,6 @@ pub struct BufferSlot {
|
||||
#[derive(Default)]
|
||||
pub struct GpuSceneCache {
|
||||
revision: Option<u64>,
|
||||
query: Option<crate::render_graph::MeshQueryRuntimeKey>,
|
||||
pub positions: BufferSlot,
|
||||
pub normals: BufferSlot,
|
||||
pub uvs: BufferSlot,
|
||||
@@ -310,11 +309,12 @@ struct CullingParams {
|
||||
_pad: u32,
|
||||
}
|
||||
|
||||
fn predicate_code(value: crate::render_graph::TriStatePredicate) -> u32 {
|
||||
fn predicate_code(value: crate::render_graph::RuntimePredicate) -> u32 {
|
||||
match value {
|
||||
crate::render_graph::TriStatePredicate::Any => 0,
|
||||
crate::render_graph::TriStatePredicate::RequiredTrue => 1,
|
||||
crate::render_graph::TriStatePredicate::RequiredFalse => 2,
|
||||
crate::render_graph::RuntimePredicate::Any => 0,
|
||||
crate::render_graph::RuntimePredicate::RequiredTrue => 1,
|
||||
crate::render_graph::RuntimePredicate::RequiredFalse => 2,
|
||||
crate::render_graph::RuntimePredicate::Never => 3,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,8 +330,8 @@ impl GpuSceneCache {
|
||||
queue,
|
||||
data,
|
||||
crate::render_graph::MeshQueryRuntimeKey {
|
||||
visible: crate::render_graph::TriStatePredicate::RequiredTrue,
|
||||
frustum_culled: crate::render_graph::TriStatePredicate::Any,
|
||||
visible: crate::render_graph::RuntimePredicate::RequiredTrue,
|
||||
frustum_culled: crate::render_graph::RuntimePredicate::Any,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -343,14 +343,13 @@ impl GpuSceneCache {
|
||||
data: &SceneFramePlan,
|
||||
query: crate::render_graph::MeshQueryRuntimeKey,
|
||||
) -> Result<(), String> {
|
||||
if self.revision == Some(data.revision) && self.query == Some(query) {
|
||||
if self.revision == Some(data.revision) {
|
||||
return Ok(());
|
||||
}
|
||||
let plan = GpuScenePlan::build_with_query(data, query).map_err(str::to_owned)?;
|
||||
if plan.draws.is_empty() {
|
||||
self.draws.clear();
|
||||
self.revision = Some(data.revision);
|
||||
self.query = Some(query);
|
||||
return Ok(());
|
||||
}
|
||||
let maximum = device.limits().max_buffer_size;
|
||||
@@ -485,7 +484,6 @@ impl GpuSceneCache {
|
||||
self.draws = plan.draws;
|
||||
self.rebuild_compute(device)?;
|
||||
self.revision = Some(data.revision);
|
||||
self.query = Some(query);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -642,11 +640,28 @@ mod tests {
|
||||
#[test]
|
||||
fn mesh_query_source_guards_optional_flag_buffer_reads() {
|
||||
let source = include_str!("culling.wgsl");
|
||||
let visible_guard = source.find("if (params.visible_predicate != 0u)").unwrap();
|
||||
let visible_never = source.find("if (params.visible_predicate == 3u)").unwrap();
|
||||
let visible_guard = source
|
||||
.find("else if (params.visible_predicate != 0u)")
|
||||
.unwrap();
|
||||
let visible_load = source.find("matches(authored_visible[i]").unwrap();
|
||||
let frustum_guard = source.find("if (params.frustum_predicate != 0u)").unwrap();
|
||||
let frustum_never = source
|
||||
.find("if (selected && params.frustum_predicate == 3u)")
|
||||
.unwrap();
|
||||
let frustum_guard = source
|
||||
.find("else if (selected && params.frustum_predicate != 0u)")
|
||||
.unwrap();
|
||||
let frustum_load = source.find("matches(frustum_flags[i]").unwrap();
|
||||
assert!(visible_guard < visible_load && frustum_guard < frustum_load);
|
||||
assert!(visible_never < visible_guard && visible_guard < visible_load);
|
||||
assert!(frustum_never < frustum_guard && frustum_guard < frustum_load);
|
||||
assert!(
|
||||
visible_load < frustum_load,
|
||||
"visible rejection must precede the frustum load"
|
||||
);
|
||||
assert!(
|
||||
source.contains("predicate == 0u ||"),
|
||||
"predicate zero is handled without a load by the guards"
|
||||
);
|
||||
for binding in 0..=6 {
|
||||
assert!(source.contains(&format!("@binding({binding})")));
|
||||
}
|
||||
|
||||
+155
-103
@@ -35,17 +35,19 @@ struct GpuTextureSlot {
|
||||
enum PreparedExecution {
|
||||
FrustumCull,
|
||||
MeshQuery,
|
||||
LegacyForward {
|
||||
PipelineRegistry,
|
||||
Pipeline {
|
||||
execution: usize,
|
||||
variants: Vec<(crate::render_data::PipelineKey, wgpu::RenderPipeline)>,
|
||||
base: crate::render_data::PipelineKey,
|
||||
variant: wgpu::RenderPipeline,
|
||||
},
|
||||
Fullscreen {
|
||||
execution: usize,
|
||||
frame_out: bool,
|
||||
bind_group: wgpu::BindGroup,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
_uniform: wgpu::Buffer,
|
||||
},
|
||||
Present,
|
||||
}
|
||||
|
||||
struct ActiveCompiledGraph {
|
||||
@@ -81,7 +83,10 @@ fn resolve_culling_frustum(
|
||||
query: crate::render_graph::MeshQueryRuntimeKey,
|
||||
read: impl FnOnce() -> Option<Result<[[f32; 4]; 6], crate::camera::FrustumError>>,
|
||||
) -> Result<Option<[[f32; 4]; 6]>, crate::render_graph::GraphError> {
|
||||
if query.frustum_culled == crate::render_graph::TriStatePredicate::Any {
|
||||
if matches!(
|
||||
query.frustum_culled,
|
||||
crate::render_graph::RuntimePredicate::Any | crate::render_graph::RuntimePredicate::Never
|
||||
) {
|
||||
return Ok(None);
|
||||
}
|
||||
match read() {
|
||||
@@ -177,18 +182,25 @@ fn resolve_switch_request(
|
||||
|
||||
#[cfg(test)]
|
||||
mod switch_request_tests {
|
||||
fn valid_compile_graph(graph_id: &str, revision: u64) -> Vec<u8> {
|
||||
let mut graph = crate::render_graph::tests::full_cull_graph();
|
||||
graph["graphId"] = serde_json::json!(graph_id);
|
||||
graph["revision"] = serde_json::json!(revision);
|
||||
serde_json::to_vec(&graph).unwrap()
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
fn query(visible: crate::render_graph::TriStatePredicate) -> UploadGraph {
|
||||
fn query(visible: crate::render_graph::RuntimePredicate) -> UploadGraph {
|
||||
UploadGraph::Compiled(crate::render_graph::MeshQueryRuntimeKey {
|
||||
visible,
|
||||
frustum_culled: crate::render_graph::TriStatePredicate::Any,
|
||||
frustum_culled: crate::render_graph::RuntimePredicate::Any,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upload_selection_follows_the_graph_rendered_for_the_commit_frame() {
|
||||
use crate::render_graph::TriStatePredicate::{Any, RequiredFalse, RequiredTrue};
|
||||
use crate::render_graph::RuntimePredicate::{Any, RequiredFalse, RequiredTrue};
|
||||
let selected =
|
||||
|pending, active| upload_query_for_render(pending, active).map(|query| query.visible);
|
||||
assert_eq!(
|
||||
@@ -214,7 +226,7 @@ mod switch_request_tests {
|
||||
|
||||
#[test]
|
||||
fn frustum_preflight_skips_any_and_distinguishes_missing_from_invalid() {
|
||||
use crate::render_graph::TriStatePredicate::{Any, RequiredFalse, RequiredTrue};
|
||||
use crate::render_graph::RuntimePredicate::{Any, RequiredFalse, RequiredTrue};
|
||||
let query = |frustum_culled| crate::render_graph::MeshQueryRuntimeKey {
|
||||
visible: RequiredTrue,
|
||||
frustum_culled,
|
||||
@@ -245,8 +257,10 @@ mod switch_request_tests {
|
||||
#[test]
|
||||
fn resolves_at_command_boundary_before_gpu_work() {
|
||||
let mut registry = crate::render_graph::Registry::default();
|
||||
let bytes = br#"{"schemaVersion":2,"graphId":"switch","revision":1,"nodes":[]}"#;
|
||||
let (id, _) = registry.compile(bytes).unwrap();
|
||||
let mut graph = crate::render_graph::tests::full_cull_graph();
|
||||
graph["graphId"] = serde_json::json!("switch");
|
||||
let bytes = serde_json::to_vec(&graph).unwrap();
|
||||
let (id, _) = registry.compile(&bytes).unwrap();
|
||||
let active = "existing_graph";
|
||||
let pending: Option<&str> = None;
|
||||
assert_eq!(
|
||||
@@ -279,9 +293,7 @@ mod switch_request_tests {
|
||||
#[test]
|
||||
fn resize_restart_snapshot_remains_bound_to_its_immutable_registry_revision() {
|
||||
let mut registry = crate::render_graph::Registry::default();
|
||||
let (id, _) = registry
|
||||
.compile(br#"{"schemaVersion":2,"graphId":"resize","revision":1,"nodes":[]}"#)
|
||||
.unwrap();
|
||||
let (id, _) = registry.compile(&valid_compile_graph("resize", 1)).unwrap();
|
||||
let revision_one = registry.get(id).unwrap().clone();
|
||||
let in_flight = InFlightPreparation {
|
||||
token: 1,
|
||||
@@ -289,9 +301,7 @@ mod switch_request_tests {
|
||||
purpose: PreparationPurpose::Resize,
|
||||
graph: revision_one,
|
||||
};
|
||||
let (revision_two_id, _) = registry
|
||||
.compile(br#"{"schemaVersion":2,"graphId":"resize","revision":2,"nodes":[]}"#)
|
||||
.unwrap();
|
||||
let (revision_two_id, _) = registry.compile(&valid_compile_graph("resize", 2)).unwrap();
|
||||
let original = registry.get(id).unwrap();
|
||||
let revision_two = registry.get(revision_two_id).unwrap();
|
||||
assert_eq!(in_flight.graph.revision, 1);
|
||||
@@ -728,6 +738,26 @@ impl<T: Scene + 'static> Renderer<T> {
|
||||
) -> Result<ActiveCompiledGraph, crate::render_graph::GraphError> {
|
||||
use crate::render_graph::*;
|
||||
let fail = |message| GraphError::new("GRAPH_RUNTIME_PLAN_INVALID", message);
|
||||
let resolved_pipelines = graph
|
||||
.executions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, execution)| {
|
||||
let NormalizedParameters::Pipeline { pipeline, .. } = &execution.parameters else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.resources
|
||||
.find_pipeline(pipeline)
|
||||
.map(Some)
|
||||
.ok_or_else(|| {
|
||||
GraphError::at(
|
||||
"GRAPH_EXECUTION_UNSUPPORTED",
|
||||
format!("pipeline '{pipeline}' is not registered"),
|
||||
format!("executions[{index}].parameters.pipeline"),
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let mut textures = Vec::with_capacity(runtime.allocations.classes.len());
|
||||
for class in &runtime.allocations.classes {
|
||||
let mut gpu_class = Vec::with_capacity(class.slots.len());
|
||||
@@ -845,37 +875,53 @@ impl<T: Scene + 'static> Renderer<T> {
|
||||
match execution.executor.key.as_str() {
|
||||
"frustum_cull" => executions.push(PreparedExecution::FrustumCull),
|
||||
"mesh_query" => executions.push(PreparedExecution::MeshQuery),
|
||||
"present" => executions.push(PreparedExecution::Present),
|
||||
"fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur"
|
||||
"frame_out" | "fullscreen_copy" | "tone_map" | "bloom_extract" | "bloom_blur"
|
||||
| "bloom_composite" | "luminance_edge" => {
|
||||
let sampled: Vec<_> = execution
|
||||
.accesses
|
||||
.iter()
|
||||
.filter(|a| matches!(a.mode, AccessMode::SampledTexture))
|
||||
.map(|a| a.resource)
|
||||
.collect();
|
||||
let source = *sampled
|
||||
.first()
|
||||
.ok_or_else(|| fail("fullscreen source missing"))?;
|
||||
let second = *sampled.get(1).unwrap_or(&source);
|
||||
let values: [f32; 8] = match execution.parameters {
|
||||
NormalizedParameters::ToneMap { exposure } => {
|
||||
[exposure, 0., 0., 0., 0., 0., 0., 0.]
|
||||
let frame_out = execution.executor.key == "frame_out";
|
||||
let (source, second) = if frame_out {
|
||||
let ExecutionKind::FrameOut { color } = execution.kind else {
|
||||
return Err(fail("frame_out kind mismatch"));
|
||||
};
|
||||
(color, color)
|
||||
} else {
|
||||
match execution.inputs.as_slice() {
|
||||
[source, _color_target] => (source.resource, source.resource),
|
||||
[source, bloom, _color_target]
|
||||
if execution.executor.key == "bloom_composite" =>
|
||||
{
|
||||
(source.resource, bloom.resource)
|
||||
}
|
||||
_ => return Err(fail("fullscreen inputs mismatch")),
|
||||
}
|
||||
NormalizedParameters::BloomExtract { threshold, knee } => {
|
||||
[threshold, knee, 0., 0., 0., 0., 0., 0.]
|
||||
}
|
||||
NormalizedParameters::BloomBlur { direction, radius } => {
|
||||
[direction[0], direction[1], radius, 0., 0., 0., 0., 0.]
|
||||
}
|
||||
NormalizedParameters::BloomComposite { intensity } => {
|
||||
[intensity, 0., 0., 0., 0., 0., 0., 0.]
|
||||
}
|
||||
NormalizedParameters::LuminanceEdge { strength } => {
|
||||
[strength, 0., 0., 0., 0., 0., 0., 0.]
|
||||
}
|
||||
_ => [0.; 8],
|
||||
};
|
||||
let values: [f32; 8] =
|
||||
match (execution.executor.key.as_str(), &execution.parameters) {
|
||||
(
|
||||
"fullscreen_copy" | "frame_out",
|
||||
NormalizedParameters::FullscreenCopy
|
||||
| NormalizedParameters::FrameOut,
|
||||
) => [0.; 8],
|
||||
("tone_map", NormalizedParameters::ToneMap { exposure }) => {
|
||||
[*exposure, 0., 0., 0., 0., 0., 0., 0.]
|
||||
}
|
||||
(
|
||||
"bloom_extract",
|
||||
NormalizedParameters::BloomExtract { threshold, knee },
|
||||
) => [*threshold, *knee, 0., 0., 0., 0., 0., 0.],
|
||||
(
|
||||
"bloom_blur",
|
||||
NormalizedParameters::BloomBlur { direction, radius },
|
||||
) => [direction[0], direction[1], *radius, 0., 0., 0., 0., 0.],
|
||||
(
|
||||
"bloom_composite",
|
||||
NormalizedParameters::BloomComposite { intensity },
|
||||
) => [*intensity, 0., 0., 0., 0., 0., 0., 0.],
|
||||
(
|
||||
"luminance_edge",
|
||||
NormalizedParameters::LuminanceEdge { strength },
|
||||
) => [*strength, 0., 0., 0., 0., 0., 0., 0.],
|
||||
_ => return Err(fail("executor parameters mismatch")),
|
||||
};
|
||||
use wgpu::util::DeviceExt;
|
||||
let uniform =
|
||||
self.context
|
||||
@@ -885,17 +931,35 @@ impl<T: Scene + 'static> Renderer<T> {
|
||||
contents: bytemuck::cast_slice(&values),
|
||||
usage: wgpu::BufferUsages::UNIFORM,
|
||||
});
|
||||
let ExecutionKind::Render {
|
||||
color_attachments, ..
|
||||
} = &execution.kind
|
||||
else {
|
||||
return Err(fail("fullscreen execution is not render"));
|
||||
let target_format = if frame_out {
|
||||
runtime.surface.format
|
||||
} else {
|
||||
let ExecutionKind::Render {
|
||||
color_attachments, ..
|
||||
} = &execution.kind
|
||||
else {
|
||||
return Err(fail("fullscreen execution is not render"));
|
||||
};
|
||||
let target = color_attachments
|
||||
.first()
|
||||
.ok_or_else(|| fail("fullscreen target missing"))?
|
||||
.resource;
|
||||
let a = runtime
|
||||
.allocations
|
||||
.resource_allocations
|
||||
.get(target as usize)
|
||||
.copied()
|
||||
.flatten()
|
||||
.ok_or_else(|| fail("fullscreen target allocation missing"))?;
|
||||
runtime
|
||||
.allocations
|
||||
.classes
|
||||
.get(a.class as usize)
|
||||
.and_then(|class| class.slots.get(a.slot as usize))
|
||||
.ok_or_else(|| fail("fullscreen target allocation is invalid"))?
|
||||
.descriptor
|
||||
.format
|
||||
};
|
||||
let target = color_attachments
|
||||
.first()
|
||||
.ok_or_else(|| fail("fullscreen target missing"))?
|
||||
.resource;
|
||||
let target_format = if graph.resources.get(target as usize).is_some_and(|r| matches!(r.plan, ResourcePlan::Texture { family, .. } if family == runtime.allocations.surface_family)) { runtime.surface.format } else { let a=runtime.allocations.resource_allocations[target as usize].ok_or_else(|| fail("fullscreen target allocation missing"))?; runtime.allocations.classes[a.class as usize].slots[a.slot as usize].descriptor.format };
|
||||
let entry = match execution.executor.key.as_str() {
|
||||
"fullscreen_copy" => "fs_copy",
|
||||
"tone_map" => "fs_tone_map",
|
||||
@@ -903,7 +967,8 @@ impl<T: Scene + 'static> Renderer<T> {
|
||||
"bloom_blur" => "fs_bloom_blur",
|
||||
"bloom_composite" => "fs_bloom_composite",
|
||||
"luminance_edge" => "fs_luminance_edge",
|
||||
_ => unreachable!(),
|
||||
"frame_out" => "fs_copy",
|
||||
_ => return Err(fail("fullscreen executor mismatch")),
|
||||
};
|
||||
let pipeline = self.context.device.create_render_pipeline(
|
||||
&wgpu::RenderPipelineDescriptor {
|
||||
@@ -963,36 +1028,25 @@ impl<T: Scene + 'static> Renderer<T> {
|
||||
});
|
||||
executions.push(PreparedExecution::Fullscreen {
|
||||
execution: index,
|
||||
frame_out,
|
||||
bind_group,
|
||||
pipeline,
|
||||
_uniform: uniform,
|
||||
});
|
||||
}
|
||||
"legacy_forward" => {
|
||||
"pipeline_registry" => executions.push(PreparedExecution::PipelineRegistry),
|
||||
"pipeline" => {
|
||||
let ExecutionKind::Render {
|
||||
color_attachments,
|
||||
depth_stencil,
|
||||
} = &execution.kind
|
||||
else {
|
||||
return Err(fail("legacy forward is not render"));
|
||||
return Err(fail("pipeline is not render"));
|
||||
};
|
||||
let color = color_attachments
|
||||
.first()
|
||||
.ok_or_else(|| fail("legacy color missing"))?;
|
||||
let color_is_surface = graph
|
||||
.resources
|
||||
.get(color.resource as usize)
|
||||
.is_some_and(|resource| {
|
||||
matches!(
|
||||
resource.plan,
|
||||
ResourcePlan::SurfaceTarget { family }
|
||||
| ResourcePlan::Texture { family, .. }
|
||||
if family == runtime.allocations.surface_family
|
||||
)
|
||||
});
|
||||
let color_format = if color_is_surface {
|
||||
runtime.surface.format
|
||||
} else {
|
||||
.ok_or_else(|| fail("pipeline color missing"))?;
|
||||
let color_format = {
|
||||
let a = runtime
|
||||
.allocations
|
||||
.resource_allocations
|
||||
@@ -1027,19 +1081,21 @@ impl<T: Scene + 'static> Renderer<T> {
|
||||
.ok_or_else(|| fail("depth allocation invalid"))
|
||||
})
|
||||
.transpose()?;
|
||||
let config = execution
|
||||
.inputs
|
||||
.iter()
|
||||
.filter_map(|i| graph.resources.get(i.resource as usize))
|
||||
.find_map(|r| {
|
||||
if let ResourcePlan::DepthStencilConfig { config } = r.plan {
|
||||
Some(config)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| fail("depth config missing"))?;
|
||||
let compare = match config.depth_compare {
|
||||
let NormalizedParameters::Pipeline {
|
||||
pipeline: _,
|
||||
depth_compare,
|
||||
depth_write_enabled,
|
||||
..
|
||||
} = &execution.parameters
|
||||
else {
|
||||
return Err(fail("pipeline parameters mismatch"));
|
||||
};
|
||||
let base = resolved_pipelines
|
||||
.get(index)
|
||||
.copied()
|
||||
.flatten()
|
||||
.ok_or_else(|| fail("resolved pipeline missing"))?;
|
||||
let compare = match depth_compare {
|
||||
CompareFunction::Never => wgpu::CompareFunction::Never,
|
||||
CompareFunction::Less => wgpu::CompareFunction::Less,
|
||||
CompareFunction::LessEqual => wgpu::CompareFunction::LessEqual,
|
||||
@@ -1049,25 +1105,21 @@ impl<T: Scene + 'static> Renderer<T> {
|
||||
CompareFunction::NotEqual => wgpu::CompareFunction::NotEqual,
|
||||
CompareFunction::Always => wgpu::CompareFunction::Always,
|
||||
};
|
||||
let mut variants = Vec::new();
|
||||
let bases: Vec<_> = self.resources.pipeline_keys().collect();
|
||||
for base in bases {
|
||||
let variant = self
|
||||
.resources
|
||||
.create_target_variant(
|
||||
&self.context.device,
|
||||
base,
|
||||
color_format,
|
||||
depth_format,
|
||||
compare,
|
||||
config.depth_write_enabled,
|
||||
)
|
||||
.map_err(|e| GraphError::new("GRAPH_RUNTIME_PLAN_INVALID", e))?;
|
||||
variants.push((base, variant));
|
||||
}
|
||||
executions.push(PreparedExecution::LegacyForward {
|
||||
let variant = self
|
||||
.resources
|
||||
.create_target_variant(
|
||||
&self.context.device,
|
||||
base,
|
||||
color_format,
|
||||
depth_format,
|
||||
compare,
|
||||
*depth_write_enabled,
|
||||
)
|
||||
.map_err(|e| GraphError::new("GRAPH_RUNTIME_PLAN_INVALID", e))?;
|
||||
executions.push(PreparedExecution::Pipeline {
|
||||
execution: index,
|
||||
variants,
|
||||
base,
|
||||
variant,
|
||||
});
|
||||
}
|
||||
_ => return Err(fail("unsupported prepared execution")),
|
||||
|
||||
@@ -372,36 +372,6 @@ impl PipelineLibrary {
|
||||
&& self.specs[key.get() as usize].layout == self.material_layout
|
||||
}
|
||||
|
||||
pub fn pipeline_keys(&self) -> impl Iterator<Item = PipelineKey> + '_ {
|
||||
let mut keys = self
|
||||
.pipeline_registry
|
||||
.values()
|
||||
.map(|entry| entry.0)
|
||||
.collect::<Vec<_>>();
|
||||
keys.sort_by_key(|key| key.get());
|
||||
keys.dedup();
|
||||
keys.into_iter()
|
||||
}
|
||||
|
||||
pub fn get_or_create_target_variant(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
base: PipelineKey,
|
||||
color_format: wgpu::TextureFormat,
|
||||
depth_format: Option<wgpu::TextureFormat>,
|
||||
depth_compare: wgpu::CompareFunction,
|
||||
depth_write: bool,
|
||||
) -> Result<PipelineKey, String> {
|
||||
let spec = self
|
||||
.specs
|
||||
.get(base.get() as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| "unknown base pipeline".to_owned())?;
|
||||
let spec =
|
||||
target_variant_spec(spec, color_format, depth_format, depth_compare, depth_write);
|
||||
Ok(self.get_or_create_from_spec(device, &spec, Some("target variant")))
|
||||
}
|
||||
|
||||
pub fn create_target_variant(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
|
||||
Reference in New Issue
Block a user