Estimate glTF VRAM from JSON Manifests

Estimating the Video RAM (VRAM) footprint of a 3D asset directly from a glTF JSON manifest involves calculating the memory required for vertex attributes, index buffers, morph targets, and textures once uploaded to the GPU. Because the glTF manifest explicitly defines data structures, component types, and buffer allocations, deterministic parsing algorithms can evaluate these metrics prior to asset loading. This guide details the core algorithms used to parse the glTF JSON structure and compute an accurate projection of runtime graphics memory consumption.

1. Vertex Buffer Memory (Attribute Parsing Algorithm)

To calculate geometry memory, the parser traverses either the bufferViews array directly or reconstructs memory footprint through the accessors and meshes properties.

If the glTF file explicitly declares GPU buffer binding targets, the algorithm processes the bufferViews array:

\[\text{VRAM}_{\text{BufferViews}} = \sum_{i=0}^{n} \text{bufferView}[i]\text{.byteLength} \quad \text{where target} \in \{34962, 34963\}\]

Target 34962 represents ARRAY_BUFFER (vertex attributes) and 34963 represents ELEMENT_ARRAY_BUFFER (indices).

If binding targets are omitted (common in raw glTF files), the algorithm evaluates every primitive's attributes through accessors:

  1. Iterate over every mesh in meshes[] and primitive in mesh.primitives[].
  2. For each attribute key (e.g., POSITION, NORMAL, TEXCOORD_0):
    • Look up the accessor via the corresponding integer ID.
    • Determine the component byte size \(S_c\) from componentType:
      • 5120 (BYTE): 1 byte
      • 5121 (UNSIGNED_BYTE): 1 byte
      • 5122 (SHORT): 2 bytes
      • 5123 (UNSIGNED_SHORT): 2 bytes
      • 5125 (UNSIGNED_INT): 4 bytes
      • 5126 (FLOAT): 4 bytes
    • Determine the component count \(N_c\) from type:
      • SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4, MAT4: 16.
    • Calculate byte length: \[\text{Attribute Bytes} = \text{accessor.count} \times S_c \times N_c\]
  3. Pad the size of each vertex attribute to a multiple of 4 bytes to match standard GPU alignment constraints.

2. Index Buffer Memory Algorithm

Primitives containing an indices property reference an accessor for element arrays.

  1. Fetch the index accessor using the ID defined in primitive.indices.
  2. Compute index allocation: \[\text{VRAM}_{\text{Indices}} = \text{accessor.count} \times S_c\] (Where \(S_c\) is typically 1 for UNSIGNED_BYTE, 2 for UNSIGNED_SHORT, or 4 for UNSIGNED_INT).
  3. If memory alignment requires 4-byte boundaries, align \(\text{VRAM}_{\text{Indices}}\) up to the nearest multiple of 4: \[\text{Aligned Bytes} = \lceil \frac{\text{VRAM}_{\text{Indices}}}{4} \rceil \times 4\]

3. Morph Target and Skinning Extensions

Morph targets and skinning add separate vertex buffer objects (VBOs) in graphics memory:

  • Morph Targets: Iterate through primitive.targets[]. For each displacement attribute (POSITION, NORMAL, TANGENT), run the attribute algorithm and sum the byte size. Sparse accessors (accessor.sparse) must be accounted for by calculating: \[\text{Sparse Bytes} = \text{sparse.count} \times S_c \times N_c + \text{Index Bytes}\]
  • Skinning Data: Joints (JOINTS_0) and weights (WEIGHTS_0) exist as standard vertex attributes and are included during the general attribute parsing stage.

4. Texture Memory Estimation Algorithm

Textures consume the largest portion of VRAM. Standard glTF JSON files contain arrays for textures, samplers, and images. While the JSON points to image URIs or buffer views, it rarely stores pixel dimensions directly unless explicit vendor extensions are present.

Handling Base Dimension Metadata

When dimensions (\(W, H\)) are provided via extensions (such as KHR_texture_basisu) or retrieved via image header sniffing:

  1. Determine base uncompressed format (commonly 4 bytes per pixel for 8-bit RGBA).
  2. Compute base surface size: \[\text{Surface Bytes} = W \times H \times \text{BytesPerPixel}\]
  3. Account for mipmaps (standard complete chain adds approximately 33.3% memory): \[\text{VRAM}_{\text{Texture}} = \text{Surface Bytes} \times 1.333\]

Block-Compressed Textures (KTX2 / Basis Universal)

When the manifest references compressed GPU texture extensions (KHR_texture_basisu), calculate memory using compressed block sizes instead of uncompressed pixel counts:

  • BC1 / DXT1 / ETC2 RGB: 8 bytes per \(4 \times 4\) pixel block.
  • BC3 / BC7 / ASTC 4x4 / ETC2 RGBA: 16 bytes per \(4 \times 4\) pixel block.

Algorithm for block compression: \[\text{Blocks}_X = \max(1, \lceil \frac{W}{4} \rceil), \quad \text{Blocks}_Y = \max(1, \lceil \frac{H}{4} \rceil)\] \[\text{VRAM}_{\text{Mip0}} = \text{Blocks}_X \times \text{Blocks}_Y \times \text{BlockSize}\] \[\text{VRAM}_{\text{Total}} = \text{VRAM}_{\text{Mip0}} \times 1.333\]

5. Unified Estimation Formula

To arrive at the total estimated VRAM, deduplicate shared buffer references (multiple accessors pointing to the same bufferView) and combine the evaluated sub-systems:

\[\text{Total VRAM} = \sum \text{VRAM}_{\text{Unique BufferViews}} + \sum \text{VRAM}_{\text{Textures}} + \text{Overhead}\]

A typical driver-overhead margin of 5% to 10% is added to account for descriptor sets, vertex array objects (VAOs), frame-allocation padding, and staging buffers created by APIs such as Vulkan, DirectX 12, or WebGPU.