///|
/// Buffer and accessor layout analysis for zero-copy import decisions.
///
/// A valid byte range can still be inefficient: overlapping views, excessive
/// stride, unused buffers, and scattered accessors increase upload and cache
/// cost. These routines expose those trade-offs as deterministic reports.
///|
pub(all) enum BufferLayoutSeverity {
LayoutNote
LayoutWarning
LayoutError
} derive(Debug, ToJson, Eq)
///|
pub(all) struct BufferViewRelation {
left : Int
right : Int
same_buffer : Bool
overlaps : Bool
adjacent : Bool
shared_bytes : Int
valid : Bool
} derive(Debug, ToJson, Eq)
///|
pub(all) struct AccessorAudit {
accessor : Int
buffer_view : Int?
component_size : Int
component_count : Int
element_size : Int
stride : Int
count : Int
payload_bytes : Int
padding_bytes : Int
contiguous : Bool
aligned : Bool
valid : Bool
issues : Array[String]
} derive(Debug, ToJson, Eq)
///|
pub(all) struct BufferAudit {
buffer : Int
declared_bytes : Int
loaded_bytes : Int
view_count : Int
accessor_count : Int
referenced_bytes : Int
unused_bytes : Int
overlap_bytes : Int
utilization : Double
valid : Bool
issues : Array[String]
} derive(Debug, ToJson, Eq)
///|
pub(all) struct BufferLayoutReport {
accessors : Array[AccessorAudit]
buffers : Array[BufferAudit]
relations : Array[BufferViewRelation]
total_declared_bytes : Int
total_referenced_bytes : Int
total_padding_bytes : Int
total_overlap_bytes : Int
unused_buffer_count : Int
invalid_accessor_count : Int
valid : Bool
} derive(Debug, ToJson, Eq)
///|
pub(all) struct BufferLayoutPolicy {
max_stride : Int
require_alignment : Bool
reject_overlaps : Bool
warn_unused_buffers : Bool
max_padding_ratio : Double
} derive(Debug, ToJson, Eq)
///|
pub fn default_buffer_layout_policy() -> BufferLayoutPolicy {
{
max_stride: 252,
require_alignment: true,
reject_overlaps: false,
warn_unused_buffers: true,
max_padding_ratio: 0.25,
}
}
///|
pub fn buffer_view_range(doc : GltfDocument, view_index : Int) -> BinaryRange? {
match doc.buffer_views.get(view_index) {
Some(view) => Some({ offset: view.byte_offset, length: view.byte_length })
None => None
}
}
///|
pub fn buffer_range_end(range : BinaryRange) -> Int {
range.offset + range.length
}
///|
pub fn buffer_ranges_overlap(left : BinaryRange, right : BinaryRange) -> Bool {
left.length > 0 &&
right.length > 0 &&
left.offset < buffer_range_end(right) &&
right.offset < buffer_range_end(left)
}
///|
pub fn buffer_ranges_adjacent(left : BinaryRange, right : BinaryRange) -> Bool {
buffer_range_end(left) == right.offset ||
buffer_range_end(right) == left.offset
}
///|
pub fn buffer_overlap_bytes(left : BinaryRange, right : BinaryRange) -> Int {
if !buffer_ranges_overlap(left, right) {
0
} else {
let start = if left.offset > right.offset {
left.offset
} else {
right.offset
}
let finish = if buffer_range_end(left) < buffer_range_end(right) {
buffer_range_end(left)
} else {
buffer_range_end(right)
}
if finish > start {
finish - start
} else {
0
}
}
}
///|
pub fn buffer_view_relation(
doc : GltfDocument,
left : Int,
right : Int,
) -> BufferViewRelation {
match (doc.buffer_views.get(left), doc.buffer_views.get(right)) {
(Some(a), Some(b)) => {
let same = a.buffer == b.buffer
let left_range = { offset: a.byte_offset, length: a.byte_length }
let right_range = { offset: b.byte_offset, length: b.byte_length }
{
left,
right,
same_buffer: same,
overlaps: same && buffer_ranges_overlap(left_range, right_range),
adjacent: same && buffer_ranges_adjacent(left_range, right_range),
shared_bytes: if same {
buffer_overlap_bytes(left_range, right_range)
} else {
0
},
valid: a.byte_offset >= 0 &&
a.byte_length >= 0 &&
b.byte_offset >= 0 &&
b.byte_length >= 0,
}
}
_ =>
{
left,
right,
same_buffer: false,
overlaps: false,
adjacent: false,
shared_bytes: 0,
valid: false,
}
}
}
///|
pub fn all_buffer_view_relations(
doc : GltfDocument,
) -> Array[BufferViewRelation] {
let relations : Array[BufferViewRelation] = []
for left in 0.. Array[BufferViewRelation] {
all_buffer_view_relations(doc).filter(relation => relation.overlaps)
}
///|
pub fn total_buffer_overlap_bytes(doc : GltfDocument) -> Int {
overlapping_buffer_view_relations(doc).fold(init=0, (sum, relation) => {
sum + relation.shared_bytes
})
}
///|
pub fn buffer_view_reference_counts(doc : GltfDocument) -> Array[Int] {
let counts : Array[Int] = []
for _ in doc.buffer_views {
counts.push(0)
}
for accessor in doc.accessors {
match accessor.buffer_view {
Some(view) => if view >= 0 && view < counts.length() { counts[view] += 1 }
None => ()
}
}
for image in doc.images {
match image.buffer_view {
Some(view) => if view >= 0 && view < counts.length() { counts[view] += 1 }
None => ()
}
}
counts
}
///|
pub fn unused_buffer_view_indices(doc : GltfDocument) -> Array[Int] {
let counts = buffer_view_reference_counts(doc)
let result : Array[Int] = []
for index, count in counts {
if count == 0 {
result.push(index)
}
}
result
}
///|
pub fn buffer_view_buffer_indices(doc : GltfDocument) -> Array[Array[Int]] {
let result : Array[Array[Int]] = []
for _ in doc.buffers {
result.push([])
}
for index, view in doc.buffer_views {
if view.buffer >= 0 && view.buffer < result.length() {
result[view.buffer].push(index)
}
}
result
}
///|
pub fn buffer_reference_counts(doc : GltfDocument) -> Array[Int] {
let counts : Array[Int] = []
for _ in doc.buffers {
counts.push(0)
}
for view in doc.buffer_views {
if view.buffer >= 0 && view.buffer < counts.length() {
counts[view.buffer] += 1
}
}
for accessor in doc.accessors {
match accessor.buffer_view {
Some(view) =>
match doc.buffer_views.get(view) {
Some(value) =>
if value.buffer >= 0 && value.buffer < counts.length() {
counts[value.buffer] += 1
}
None => ()
}
None => ()
}
}
counts
}
///|
pub fn unused_buffer_indices(doc : GltfDocument) -> Array[Int] {
let counts = buffer_reference_counts(doc)
let result : Array[Int] = []
for index, count in counts {
if count == 0 {
result.push(index)
}
}
result
}
///|
pub fn accessor_padding_bytes(doc : GltfDocument, accessor_index : Int) -> Int {
let layout = accessor_layout(doc, accessor_index)
if !layout.valid || layout.stride <= layout.element_size {
0
} else {
let count = match doc.accessors.get(accessor_index) {
Some(accessor) => accessor.count
None => 0
}
(layout.stride - layout.element_size) * count
}
}
///|
pub fn accessor_is_contiguous(doc : GltfDocument, accessor_index : Int) -> Bool {
let layout = accessor_layout(doc, accessor_index)
layout.valid && layout.stride == layout.element_size
}
///|
pub fn accessor_payload_bytes(doc : GltfDocument, accessor_index : Int) -> Int {
match doc.accessors.get(accessor_index) {
Some(accessor) => accessor_element_size(accessor) * accessor.count
None => 0
}
}
///|
pub fn accessor_data_density(
doc : GltfDocument,
accessor_index : Int,
) -> Double {
let payload = accessor_payload_bytes(doc, accessor_index)
let padding = accessor_padding_bytes(doc, accessor_index)
if payload + padding == 0 {
0.0
} else {
payload.to_double() / (payload + padding).to_double()
}
}
///|
pub fn accessor_audit(
doc : GltfDocument,
accessor_index : Int,
policy : BufferLayoutPolicy,
) -> AccessorAudit {
match doc.accessors.get(accessor_index) {
None =>
{
accessor: accessor_index,
buffer_view: None,
component_size: 0,
component_count: 0,
element_size: 0,
stride: 0,
count: 0,
payload_bytes: 0,
padding_bytes: 0,
contiguous: false,
aligned: false,
valid: false,
issues: ["accessor index is outside the document"],
}
Some(accessor) => {
let layout = accessor_layout(doc, accessor_index)
let issues : Array[String] = []
if policy.require_alignment &&
layout.first_byte % accessor_alignment(accessor.component_type) != 0 {
issues.push("accessor start is not aligned to its component type")
}
if layout.stride > policy.max_stride {
issues.push("accessor stride exceeds the configured maximum")
}
if layout.stride < layout.element_size {
issues.push("accessor stride is smaller than the element")
}
for problem in layout.problems {
issues.push(problem)
}
{
accessor: accessor_index,
buffer_view: accessor.buffer_view,
component_size: component_info(accessor.component_type).size,
component_count: accessor_component_count(accessor.accessor_type),
element_size: layout.element_size,
stride: layout.stride,
count: accessor.count,
payload_bytes: accessor_payload_bytes(doc, accessor_index),
padding_bytes: accessor_padding_bytes(doc, accessor_index),
contiguous: layout.stride == layout.element_size,
aligned: layout.first_byte % accessor_alignment(accessor.component_type) ==
0,
valid: issues.length() == 0,
issues,
}
}
}
}
///|
pub fn all_accessor_audits(
doc : GltfDocument,
policy : BufferLayoutPolicy,
) -> Array[AccessorAudit] {
let result : Array[AccessorAudit] = []
for index in 0.. Array[Int] {
let result : Array[Int] = []
for accessor, value in doc.accessors {
if value.buffer_view == Some(view_index) {
result.push(accessor)
}
}
result
}
///|
pub fn buffer_view_payload_bytes(doc : GltfDocument, view_index : Int) -> Int {
buffer_view_accessor_indices(doc, view_index).fold(init=0, (sum, accessor) => {
sum + accessor_payload_bytes(doc, accessor)
})
}
///|
pub fn buffer_view_padding_bytes(doc : GltfDocument, view_index : Int) -> Int {
buffer_view_accessor_indices(doc, view_index).fold(init=0, (sum, accessor) => {
sum + accessor_padding_bytes(doc, accessor)
})
}
///|
pub fn buffer_audit(
doc : GltfDocument,
buffer_index : Int,
policy : BufferLayoutPolicy,
) -> BufferAudit {
let views = buffer_view_buffer_indices(doc).get(buffer_index)
match (doc.buffers.get(buffer_index), views) {
(Some(buffer), Some(view_indices)) => {
let accessor_count = view_indices.fold(init=0, (sum, view) => {
sum + buffer_view_accessor_indices(doc, view).length()
})
let referenced = view_indices.fold(init=0, (sum, view) => {
sum + buffer_view_payload_bytes(doc, view)
})
let padding = view_indices.fold(init=0, (sum, view) => {
sum + buffer_view_padding_bytes(doc, view)
})
let relations = overlapping_buffer_view_relations(doc)
let overlap = relations.fold(init=0, (sum, relation) => {
match doc.buffer_views.get(relation.left) {
Some(view) =>
if view.buffer == buffer_index {
sum + relation.shared_bytes
} else {
sum
}
None => sum
}
})
let unused = if buffer.byte_length > referenced + padding {
buffer.byte_length - referenced - padding
} else {
0
}
let issues : Array[String] = []
if unused > 0 && policy.warn_unused_buffers {
issues.push("buffer contains unused declared bytes")
}
if buffer.byte_length < 0 {
issues.push("buffer has a negative declared length")
}
{
buffer: buffer_index,
declared_bytes: buffer.byte_length,
loaded_bytes: 0,
view_count: view_indices.length(),
accessor_count,
referenced_bytes: referenced,
unused_bytes: unused,
overlap_bytes: overlap,
utilization: if buffer.byte_length <= 0 {
0.0
} else {
referenced.to_double() / buffer.byte_length.to_double()
},
valid: issues.length() == 0,
issues,
}
}
_ =>
{
buffer: buffer_index,
declared_bytes: 0,
loaded_bytes: 0,
view_count: 0,
accessor_count: 0,
referenced_bytes: 0,
unused_bytes: 0,
overlap_bytes: 0,
utilization: 0.0,
valid: false,
issues: ["buffer index is outside the document"],
}
}
}
///|
pub fn all_buffer_audits(
doc : GltfDocument,
policy : BufferLayoutPolicy,
) -> Array[BufferAudit] {
let result : Array[BufferAudit] = []
for index in 0.. BufferLayoutReport {
let accessors = all_accessor_audits(doc, policy)
let buffers = all_buffer_audits(doc, policy)
let relations = all_buffer_view_relations(doc)
let declared = buffers.fold(init=0, (sum, buffer) => {
sum + buffer.declared_bytes
})
let referenced = buffers.fold(init=0, (sum, buffer) => {
sum + buffer.referenced_bytes
})
let padding = accessors.fold(init=0, (sum, accessor) => {
sum + accessor.padding_bytes
})
let overlaps = relations.fold(init=0, (sum, relation) => {
sum + relation.shared_bytes
})
let invalid = accessors.fold(init=0, (count, accessor) => {
if accessor.valid {
count
} else {
count + 1
}
})
let unused = buffers.fold(init=0, (count, buffer) => {
if buffer.unused_bytes > 0 {
count + 1
} else {
count
}
})
{
accessors,
buffers,
relations,
total_declared_bytes: declared,
total_referenced_bytes: referenced,
total_padding_bytes: padding,
total_overlap_bytes: overlaps,
unused_buffer_count: unused,
invalid_accessor_count: invalid,
valid: invalid == 0 && (!policy.reject_overlaps || overlaps == 0),
}
}
///|
pub fn buffer_layout_efficiency(report : BufferLayoutReport) -> Double {
if report.total_declared_bytes <= 0 {
0.0
} else {
report.total_referenced_bytes.to_double() /
report.total_declared_bytes.to_double()
}
}
///|
pub fn buffer_layout_has_overlap(report : BufferLayoutReport) -> Bool {
report.total_overlap_bytes > 0
}
///|
pub fn buffer_layout_has_padding(report : BufferLayoutReport) -> Bool {
report.total_padding_bytes > 0
}
///|
pub fn buffer_layout_summary_lines(
report : BufferLayoutReport,
) -> Array[String] {
[
"declared_bytes=\{report.total_declared_bytes}, referenced_bytes=\{report.total_referenced_bytes}",
"padding_bytes=\{report.total_padding_bytes}, overlap_bytes=\{report.total_overlap_bytes}",
"unused_buffers=\{report.unused_buffer_count}, invalid_accessors=\{report.invalid_accessor_count}",
"efficiency=\{buffer_layout_efficiency(report)}, valid=\{report.valid}",
]
}