///|
/// Actionable optimization planning for glTF asset pipelines.
///
/// The analyzer reports facts; this module turns those facts into a stable
/// ordered plan. A plan is intentionally descriptive rather than destructive:
/// a build system can review or apply each action with its own renderer and
/// compression policy.
///|
pub(all) enum OptimizationKind {
IndexGeometry
ReorderVertices
RemoveDegenerateTriangles
AddMissingNormals
ReduceDrawCalls
MergeDuplicateMaterials
RemoveUnusedMaterials
ShareDuplicateResources
PackageExternalResources
FlattenSceneHelpers
ReduceAnimationKeys
LimitSkinInfluences
NormalizeSkinWeights
RenameNodes
RejectUnsafeUri
} derive(Debug, ToJson, Eq)
///|
pub(all) enum OptimizationRisk {
Safe
Review
RendererDependent
Destructive
} derive(Debug, ToJson, Eq)
///|
pub(all) struct OptimizationFinding {
kind : OptimizationKind
risk : OptimizationRisk
priority : Int
target : String
title : String
reason : String
action : String
estimated_bytes_saved : Int
estimated_draw_calls_saved : Int
estimated_triangles_saved : Int
valid : Bool
} derive(Debug, ToJson, Eq)
///|
pub(all) struct OptimizationPlan {
score : Int
findings : Array[OptimizationFinding]
safe_count : Int
review_count : Int
renderer_dependent_count : Int
destructive_count : Int
estimated_bytes_saved : Int
estimated_draw_calls_saved : Int
estimated_triangles_saved : Int
valid : Bool
} derive(Debug, ToJson, Eq)
///|
pub(all) struct OptimizationPolicy {
index_unindexed : Bool
reject_degenerate : Bool
target_cache_hit_ratio : Double
remove_unused_materials : Bool
merge_duplicate_materials : Bool
package_external_resources : Bool
max_draw_calls : Int
max_animation_keys : Int
max_skin_influences : Int
} derive(Debug, ToJson, Eq)
///|
pub fn default_optimization_policy() -> OptimizationPolicy {
{
index_unindexed: true,
reject_degenerate: false,
target_cache_hit_ratio: 0.55,
remove_unused_materials: true,
merge_duplicate_materials: true,
package_external_resources: true,
max_draw_calls: 1000,
max_animation_keys: 100000,
max_skin_influences: 4,
}
}
///|
pub fn mobile_optimization_policy() -> OptimizationPolicy {
{
index_unindexed: true,
reject_degenerate: true,
target_cache_hit_ratio: 0.65,
remove_unused_materials: true,
merge_duplicate_materials: true,
package_external_resources: true,
max_draw_calls: 300,
max_animation_keys: 50000,
max_skin_influences: 4,
}
}
///|
pub fn optimization_kind_name(kind : OptimizationKind) -> String {
match kind {
IndexGeometry => "index-geometry"
ReorderVertices => "reorder-vertices"
RemoveDegenerateTriangles => "remove-degenerate-triangles"
AddMissingNormals => "add-missing-normals"
ReduceDrawCalls => "reduce-draw-calls"
MergeDuplicateMaterials => "merge-duplicate-materials"
RemoveUnusedMaterials => "remove-unused-materials"
ShareDuplicateResources => "share-duplicate-resources"
PackageExternalResources => "package-external-resources"
FlattenSceneHelpers => "flatten-scene-helpers"
ReduceAnimationKeys => "reduce-animation-keys"
LimitSkinInfluences => "limit-skin-influences"
NormalizeSkinWeights => "normalize-skin-weights"
RenameNodes => "rename-nodes"
RejectUnsafeUri => "reject-unsafe-uri"
}
}
///|
pub fn optimization_risk_name(risk : OptimizationRisk) -> String {
match risk {
Safe => "safe"
Review => "review"
RendererDependent => "renderer-dependent"
Destructive => "destructive"
}
}
///|
fn finding(
kind : OptimizationKind,
risk : OptimizationRisk,
priority : Int,
target : String,
title : String,
reason : String,
action : String,
bytes : Int,
draws : Int,
triangles : Int,
) -> OptimizationFinding {
{
kind,
risk,
priority,
target,
title,
reason,
action,
estimated_bytes_saved: if bytes < 0 {
0
} else {
bytes
},
estimated_draw_calls_saved: if draws < 0 {
0
} else {
draws
},
estimated_triangles_saved: if triangles < 0 {
0
} else {
triangles
},
valid: true,
}
}
///|
fn add_mesh_findings(
doc : GltfDocument,
asset : BinaryAsset,
policy : OptimizationPolicy,
findings : Array[OptimizationFinding],
) -> Unit {
let geometry = document_geometry_report(doc, asset)
for mesh in geometry.meshes {
for primitive in mesh.primitives {
if policy.index_unindexed &&
!primitive.indexed &&
primitive.vertex_count > 0 {
let estimated = primitive.vertex_count * 4
findings.push(
finding(
IndexGeometry,
RendererDependent,
60,
"meshes[\{mesh.mesh_index}].primitives[\{primitive.primitive_index}]",
"index repeated vertex data",
"primitive is not indexed and repeats vertex attributes",
"generate an index buffer and deduplicate equivalent vertices",
estimated,
0,
0,
),
)
}
if primitive.triangles.degenerate_count > 0 {
findings.push(
finding(
RemoveDegenerateTriangles,
if policy.reject_degenerate {
Destructive
} else {
Review
},
90,
"meshes[\{mesh.mesh_index}].primitives[\{primitive.primitive_index}]",
"remove degenerate triangles",
"\{primitive.triangles.degenerate_count} triangles have zero area",
"remove zero-area triangles after preserving material groups",
0,
0,
primitive.triangles.degenerate_count,
),
)
}
if primitive.cache_hit_ratio < policy.target_cache_hit_ratio &&
primitive.indexed {
findings.push(
finding(
ReorderVertices,
RendererDependent,
55,
"meshes[\{mesh.mesh_index}].primitives[\{primitive.primitive_index}]",
"reorder vertices for post-transform cache locality",
"measured vertex cache hit ratio is below the target",
"run a cache-aware triangle reorderer for the target GPU profile",
0,
0,
0,
),
)
}
if !primitive.channels.normal && primitive.triangle_count > 0 {
findings.push(
finding(
AddMissingNormals,
RendererDependent,
40,
"meshes[\{mesh.mesh_index}].primitives[\{primitive.primitive_index}]",
"generate missing normals",
"primitive has triangles but no NORMAL channel",
"generate angle-weighted vertex normals before runtime upload",
0,
0,
0,
),
)
}
if primitive.overdraw_proxy > 0.25 {
findings.push(
finding(
ReduceDrawCalls,
Review,
35,
"meshes[\{mesh.mesh_index}].primitives[\{primitive.primitive_index}]",
"review projected overdraw",
"triangle area distribution suggests high overlap",
"inspect material splits and simplify hidden geometry",
0,
0,
0,
),
)
}
}
}
if geometry.primitive_count > policy.max_draw_calls {
findings.push(
finding(
ReduceDrawCalls,
Review,
95,
"document",
"reduce draw calls",
"primitive count exceeds the configured draw-call budget",
"merge compatible primitives and reduce material splits",
0,
geometry.primitive_count - policy.max_draw_calls,
0,
),
)
}
}
///|
fn add_material_findings(
doc : GltfDocument,
policy : OptimizationPolicy,
findings : Array[OptimizationFinding],
) -> Unit {
if policy.remove_unused_materials {
let unused = unused_material_indices(doc)
if unused.length() > 0 {
findings.push(
finding(
RemoveUnusedMaterials,
Safe,
45,
"materials",
"remove unused materials",
"\{unused.length()} materials are not referenced by any primitive",
"prune unreferenced material records after export",
0,
0,
0,
),
)
}
}
if policy.merge_duplicate_materials {
let groups = duplicate_material_groups(doc)
if groups.length() > 0 {
let mut duplicate_count = 0
for group in groups {
duplicate_count += group.length() - 1
}
findings.push(
finding(
MergeDuplicateMaterials,
Safe,
50,
"materials",
"merge duplicate material definitions",
"\{duplicate_count} material records share a stable signature",
"replace duplicate references with one canonical material",
0,
0,
0,
),
)
}
}
let double_sided = material_double_sided_count(doc)
if double_sided > 0 {
findings.push(
finding(
ReduceDrawCalls,
Review,
20,
"materials",
"review double-sided materials",
"\{double_sided} materials disable back-face culling",
"keep double-sided only where thin-surface visibility requires it",
0,
0,
0,
),
)
}
}
///|
fn add_resource_findings(
doc : GltfDocument,
policy : OptimizationPolicy,
findings : Array[OptimizationFinding],
) -> Unit {
let manifest = resource_manifest_default(doc)
if manifest.duplicate_uri_count > 0 {
findings.push(
finding(
ShareDuplicateResources,
Safe,
70,
"resources",
"share duplicate resource paths",
"\{manifest.duplicate_uri_count} resource records resolve to duplicate fingerprints",
"deduplicate packaged resources while preserving references",
0,
0,
0,
),
)
}
if policy.package_external_resources && manifest.external_count > 0 {
findings.push(
finding(
PackageExternalResources,
Safe,
80,
"resources",
"package external dependencies",
"asset depends on \{manifest.external_count} external resource records",
"copy dependencies into a deterministic asset package manifest",
0,
0,
0,
),
)
}
if manifest.unsafe_count > 0 {
findings.push(
finding(
RejectUnsafeUri,
Destructive,
100,
"resources",
"reject unsafe resource URIs",
"\{manifest.unsafe_count} resource paths violate URI safety policy",
"fail the import or require an explicit path rewrite",
0,
0,
0,
),
)
}
}
///|
fn add_scene_findings(
doc : GltfDocument,
findings : Array[OptimizationFinding],
) -> Unit {
let report = document_scene_graph_report(doc)
if report.orphan_count > 0 {
findings.push(
finding(
FlattenSceneHelpers,
Review,
30,
"nodes",
"remove orphan scene nodes",
"\{report.orphan_count} nodes are not reachable from the selected scene",
"prune editor-only helpers or attach them to an explicit scene",
0,
0,
0,
),
)
}
if report.max_depth > 64 {
findings.push(
finding(
FlattenSceneHelpers,
RendererDependent,
40,
"nodes",
"flatten excessively deep helper chains",
"scene depth is \{report.max_depth}",
"bake transforms for helper-only nodes where runtime semantics allow",
0,
0,
0,
),
)
}
if report.duplicate_name_count > 0 {
findings.push(
finding(
RenameNodes,
Review,
25,
"nodes",
"make node names unique",
"\{report.duplicate_name_count} duplicate name occurrences were found",
"rename nodes used by animation or editor lookup",
0,
0,
0,
),
)
}
}
///|
fn add_animation_findings(
doc : GltfDocument,
asset : BinaryAsset,
policy : OptimizationPolicy,
findings : Array[OptimizationFinding],
) -> Unit {
let reports = all_animation_clip_reports(doc, asset)
let keyframes = total_animation_keyframes(reports)
if keyframes > policy.max_animation_keys {
findings.push(
finding(
ReduceAnimationKeys,
RendererDependent,
75,
"animations",
"reduce animation keyframes",
"animation data contains \{keyframes} keyframes",
"run a tolerance-aware keyframe simplifier per animated property",
0,
0,
0,
),
)
}
for report in reports {
if report.invalid_track_count > 0 {
findings.push(
finding(
ReduceAnimationKeys,
Destructive,
100,
"animations[\{report.animation_index}]",
"repair invalid animation tracks",
"\{report.invalid_track_count} tracks failed timeline or decode checks",
"fix accessors and channel references before optimizing keyframes",
0,
0,
0,
),
)
}
}
}
///|
fn add_skin_findings(
doc : GltfDocument,
asset : BinaryAsset,
policy : OptimizationPolicy,
findings : Array[OptimizationFinding],
) -> Unit {
let reports = all_skin_reports(doc, asset)
for report in reports {
if report.weight_summary.max_influences_per_vertex >
policy.max_skin_influences {
findings.push(
finding(
LimitSkinInfluences,
RendererDependent,
70,
"skins[\{report.skin_index}]",
"limit vertex skin influences",
"skin exceeds the configured influence count",
"retain the strongest influences and renormalize weights",
0,
0,
0,
),
)
}
if report.weight_summary.non_normalized_vertex_count > 0 {
findings.push(
finding(
NormalizeSkinWeights,
Safe,
65,
"skins[\{report.skin_index}]",
"normalize skin weights",
"\{report.weight_summary.non_normalized_vertex_count} vertices are not normalized",
"clamp negative values and divide each influence set by its sum",
0,
0,
0,
),
)
}
}
}
///|
pub fn optimization_plan(
doc : GltfDocument,
asset : BinaryAsset,
policy : OptimizationPolicy,
) -> OptimizationPlan {
let findings : Array[OptimizationFinding] = []
add_mesh_findings(doc, asset, policy, findings)
add_material_findings(doc, policy, findings)
add_resource_findings(doc, policy, findings)
add_scene_findings(doc, findings)
add_animation_findings(doc, asset, policy, findings)
add_skin_findings(doc, asset, policy, findings)
let mut safe = 0
let mut review = 0
let mut renderer = 0
let mut destructive = 0
let mut bytes = 0
let mut draws = 0
let mut triangles = 0
let mut score = 100
for item in findings {
match item.risk {
Safe => safe += 1
Review => review += 1
RendererDependent => renderer += 1
Destructive => {
destructive += 1
score -= 10
}
}
bytes += item.estimated_bytes_saved
draws += item.estimated_draw_calls_saved
triangles += item.estimated_triangles_saved
score -= if item.priority >= 90 {
5
} else if item.priority >= 60 {
2
} else {
1
}
}
{
score: if score < 0 {
0
} else {
score
},
findings,
safe_count: safe,
review_count: review,
renderer_dependent_count: renderer,
destructive_count: destructive,
estimated_bytes_saved: bytes,
estimated_draw_calls_saved: draws,
estimated_triangles_saved: triangles,
valid: destructive == 0,
}
}
///|
pub fn optimization_plan_default(
doc : GltfDocument,
asset : BinaryAsset,
) -> OptimizationPlan {
optimization_plan(doc, asset, default_optimization_policy())
}
///|
pub fn optimization_findings_by_risk(
plan : OptimizationPlan,
risk : OptimizationRisk,
) -> Array[OptimizationFinding] {
plan.findings.filter(item => item.risk == risk)
}
///|
pub fn optimization_findings_by_kind(
plan : OptimizationPlan,
kind : OptimizationKind,
) -> Array[OptimizationFinding] {
plan.findings.filter(item => item.kind == kind)
}
///|
pub fn optimization_findings_above_priority(
plan : OptimizationPlan,
priority : Int,
) -> Array[OptimizationFinding] {
plan.findings.filter(item => item.priority >= priority)
}
///|
pub fn optimization_plan_has_blockers(plan : OptimizationPlan) -> Bool {
plan.destructive_count > 0
}
///|
pub fn optimization_plan_is_empty(plan : OptimizationPlan) -> Bool {
plan.findings.length() == 0
}
///|
pub fn optimization_plan_top_priority(
plan : OptimizationPlan,
) -> OptimizationFinding? {
let mut best : OptimizationFinding? = None
for item in plan.findings {
match best {
None => best = Some(item)
Some(old) => if item.priority > old.priority { best = Some(item) }
}
}
best
}
///|
pub fn optimization_plan_summary_lines(
plan : OptimizationPlan,
) -> Array[String] {
[
"score=\{plan.score}, findings=\{plan.findings.length()}, valid=\{plan.valid}",
"safe=\{plan.safe_count}, review=\{plan.review_count}, renderer_dependent=\{plan.renderer_dependent_count}, destructive=\{plan.destructive_count}",
"estimated_bytes_saved=\{plan.estimated_bytes_saved}",
"estimated_draw_calls_saved=\{plan.estimated_draw_calls_saved}, triangles_saved=\{plan.estimated_triangles_saved}",
]
}
///|
pub fn optimization_finding_line(item : OptimizationFinding) -> String {
"[\{item.priority}] \{optimization_kind_name(item.kind)} (\{optimization_risk_name(item.risk)}) \{item.target}: \{item.title} - \{item.reason}"
}
///|
pub fn optimization_finding_lines(plan : OptimizationPlan) -> Array[String] {
plan.findings.map(optimization_finding_line)
}