use crate::assets::{Assets, TextureHandle};
use crate::diagnostics::PrepareError;
use crate::scene::{Scene, Vec3};
pub(super) use self::diagnostics::{
collect_asset_camera_visibility_diagnostics, collect_camera_projection_diagnostics,
collect_camera_visibility_diagnostics, collect_precision_diagnostics,
};
pub(super) use self::dynamic::collect_dynamic_light_from_world;
pub(super) use self::environment::collect_environment_lighting;
#[doc(hidden)]
pub use self::environment::precompute_environment_sidecar;
pub(in crate::render) use self::environment::{
EnvironmentLightingProfile, PreparedEnvironmentCubemap, PreparedEnvironmentLighting,
};
use self::lighting::PreparedLights;
pub(super) use self::lighting::{PreparedGpuLightUniform, collect_gpu_light_uniform};
use self::materials::validate_material_texture_handles;
use self::primitives::append_geometry_primitives;
pub(in crate::render) use self::primitives::draw_uniform_tint;
pub(super) use self::resources::{
PreparedLogicalResourceStats, PreparedMaterialSlot, collect_backend_material_slots,
collect_logical_resource_stats, collect_material_texture_diagnostics,
};
use self::shadows::{collect_shadow_occluders, cpu_shadow_visibility_required};
pub(super) use self::stats::{
PreparedDepthStats, PreparedEnvironmentStats, PreparedLightingStats,
collect_depth_prepass_stats, collect_environment_prepare_stats, collect_lighting_stats,
};
use self::transforms::{compose_transform, identity_matrix4, prepared_primitive};
use self::types::{
DeformationInputs, GeometryPrimitiveSource, PreparedScene, PrimitiveBakeParams, PrimitiveSinks,
TransparentPrimitive,
};
use super::{RasterTarget, camera::CameraProjection};
mod cpu_bake;
mod diagnostics;
mod dynamic;
mod environment;
mod environment_prefilter;
mod labels;
mod material_batch;
pub(in crate::render) use self::material_batch::compute_material_batch_plan;
mod lighting;
mod materials;
mod pbr_contract;
mod primitives;
mod resources;
mod shadows;
mod stats;
mod strokes;
mod tangents;
#[cfg(test)]
mod tests;
pub(super) mod transforms;
mod types;
pub(in crate::render) use types::{
PreparedInstanceRecord, PreparedInstanceSet, PreparedPrimitive, PreparedStrokeSegment,
};
pub(super) fn collect_prepared_primitives<F>(
target: RasterTarget,
scene: &Scene,
assets: Option<&Assets<F>>,
camera_projection: Option<&CameraProjection>,
backend_sampled_base_color_textures: &[TextureHandle],
backend_material_slots: &[crate::assets::MaterialHandle],
environment_lighting: PreparedEnvironmentLighting,
) -> Result<PreparedScene, PrepareError> {
if let Some(model_node) = scene.model_nodes().next() {
return Err(PrepareError::UnsupportedModelNode { node: model_node });
}
let origin_shift = scene.origin_shift();
let lights = PreparedLights::from_scene(scene, origin_shift);
let needs_cpu_shadow_visibility = cpu_shadow_visibility_required(scene, backend_material_slots);
let shadow_occluders = if needs_cpu_shadow_visibility {
collect_shadow_occluders(scene, assets, origin_shift)?
} else {
Vec::new()
};
let shadow_projection_points = if needs_cpu_shadow_visibility {
None
} else {
Some(shadows::collect_shadow_projection_points(
scene,
assets,
origin_shift,
)?)
};
let mut primitives: Vec<PreparedPrimitive> = scene
.renderable_nodes()
.flat_map(|(node, renderable, transform)| {
renderable.primitives().iter().map(move |primitive| {
PreparedPrimitive::new(
prepared_primitive(primitive, transform, origin_shift),
Some(node),
draw_uniform_tint(scene.node_tint(node).unwrap_or(None)),
)
})
})
.collect();
labels::append_label_primitives(scene, origin_shift, &mut primitives);
let mut transparent_primitives = Vec::new();
let mut strokes = Vec::new();
let mut instances = Vec::new();
let gpu_instance_path = matches!(
target.backend,
crate::diagnostics::Backend::HeadlessGpu
| crate::diagnostics::Backend::WebGpu
| crate::diagnostics::Backend::WebGl2
);
for (node, mesh, transform) in scene.mesh_nodes() {
let Some(assets) = assets else {
return Err(PrepareError::AssetsRequired { node });
};
let geometry = assets
.geometry(mesh.geometry())
.ok_or(PrepareError::GeometryNotFound {
node,
geometry: mesh.geometry(),
})?;
let material = assets
.material(mesh.material())
.ok_or(PrepareError::MaterialNotFound {
node,
material: mesh.material(),
})?;
validate_material_texture_handles(node, mesh.material(), &material, assets)?;
append_geometry_primitives(
GeometryPrimitiveSource {
node,
material_handle: mesh.material(),
geometry: &geometry,
material: &material,
assets,
tint: scene.node_tint(node).unwrap_or(None),
},
DeformationInputs {
morph_weights: scene.morph_weights(node),
skin_matrices: scene.skin_matrices(node).as_deref(),
},
PrimitiveBakeParams {
target,
transform,
origin_shift,
lights: &lights,
shadow_occluders: &shadow_occluders,
camera_projection,
backend_sampled_base_color_textures,
backend_material_slots,
environment_lighting: environment_lighting.clone(),
},
PrimitiveSinks {
primitives: &mut primitives,
strokes: &mut strokes,
transparent_primitives: &mut transparent_primitives,
},
)?;
}
for (node, instance_set, node_transform) in scene.instance_set_nodes() {
let Some(assets) = assets else {
return Err(PrepareError::AssetsRequired { node });
};
let geometry =
assets
.geometry(instance_set.geometry())
.ok_or(PrepareError::GeometryNotFound {
node,
geometry: instance_set.geometry(),
})?;
let material =
assets
.material(instance_set.material())
.ok_or(PrepareError::MaterialNotFound {
node,
material: instance_set.material(),
})?;
validate_material_texture_handles(node, instance_set.material(), &material, assets)?;
let can_use_gpu_instance_path = gpu_instance_path
&& !matches!(
material.kind(),
crate::material::MaterialKind::Line
| crate::material::MaterialKind::Wireframe
| crate::material::MaterialKind::Edge
);
if can_use_gpu_instance_path {
let mut retained = Vec::new();
let mut instance_strokes = Vec::new();
let mut transparent = Vec::new();
append_geometry_primitives(
GeometryPrimitiveSource {
node,
material_handle: instance_set.material(),
geometry: &geometry,
material: &material,
assets,
tint: None,
},
DeformationInputs::default(),
PrimitiveBakeParams {
target,
transform: node_transform,
origin_shift,
lights: &lights,
shadow_occluders: &shadow_occluders,
camera_projection,
backend_sampled_base_color_textures,
backend_material_slots,
environment_lighting: environment_lighting.clone(),
},
PrimitiveSinks {
primitives: &mut retained,
strokes: &mut instance_strokes,
transparent_primitives: &mut transparent,
},
)?;
retained.extend(
transparent
.into_iter()
.map(|transparent| transparent.primitive),
);
strokes.extend(instance_strokes);
if !retained.is_empty() {
let Some(source_set) = instance_set_key_for_node(scene, node) else {
continue;
};
instances.push(PreparedInstanceSet::new(
node,
source_set,
retained,
collect_prepared_instance_records(scene, node, instance_set),
));
}
} else {
for instance in instance_set
.instances()
.filter(|instance| instance.visible())
{
append_geometry_primitives(
GeometryPrimitiveSource {
node,
material_handle: instance_set.material(),
geometry: &geometry,
material: &material,
assets,
tint: multiply_tint(scene.node_tint(node).unwrap_or(None), instance.tint()),
},
DeformationInputs::default(),
PrimitiveBakeParams {
target,
transform: compose_transform(node_transform, instance.transform()),
origin_shift,
lights: &lights,
shadow_occluders: &shadow_occluders,
camera_projection,
backend_sampled_base_color_textures,
backend_material_slots,
environment_lighting: environment_lighting.clone(),
},
PrimitiveSinks {
primitives: &mut primitives,
strokes: &mut strokes,
transparent_primitives: &mut transparent_primitives,
},
)?;
}
}
}
transparent_primitives
.sort_by(|left: &TransparentPrimitive, right| right.depth.total_cmp(&left.depth));
primitives.extend(
transparent_primitives
.into_iter()
.map(|transparent| transparent.primitive),
);
let light_from_world = lights
.primary_shadow_ray_direction()
.map(|to_light_dir| {
let light_direction = Vec3::new(-to_light_dir.x, -to_light_dir.y, -to_light_dir.z);
match shadow_projection_points.as_ref() {
Some(points) => shadows::directional_light_view_projection_from_points(
light_direction,
points.iter().copied(),
),
None => {
shadows::directional_light_view_projection(light_direction, &shadow_occluders)
}
}
})
.unwrap_or_else(identity_matrix4);
Ok(PreparedScene {
primitives,
strokes,
instances,
light_from_world,
})
}
fn collect_prepared_instance_records(
scene: &Scene,
node: crate::scene::NodeKey,
instance_set: &crate::scene::InstanceSet,
) -> Vec<PreparedInstanceRecord> {
let node_tint = scene.node_tint(node).unwrap_or(None);
instance_set
.instances()
.map(|instance| {
PreparedInstanceRecord::new(
instance.id(),
transforms::world_from_model_matrix(instance.transform(), Vec3::ZERO),
transforms::normal_from_model_matrix(instance.transform()),
draw_uniform_tint(multiply_tint(node_tint, instance.tint())),
)
})
.collect()
}
fn instance_set_key_for_node(
scene: &Scene,
node: crate::scene::NodeKey,
) -> Option<crate::scene::InstanceSetKey> {
let crate::scene::NodeKind::InstanceSet(instance_set) = *scene.node(node)?.kind() else {
return None;
};
Some(instance_set)
}
fn multiply_tint(
node_tint: Option<crate::material::Color>,
instance_tint: Option<crate::material::Color>,
) -> Option<crate::material::Color> {
match (node_tint, instance_tint) {
(Some(left), Some(right)) => Some(crate::material::Color::from_linear_rgba(
left.r * right.r,
left.g * right.g,
left.b * right.b,
left.a * right.a,
)),
(Some(tint), None) | (None, Some(tint)) => Some(tint),
(None, None) => None,
}
}