@ibltools/ibla-loader
v0.2.1
Published
Parser-only TypeScript loader for .ibla assets.
Maintainers
Readme
@ibltools/ibla-loader
@ibltools/ibla-loader is the parser-only TypeScript loader for .ibla assets.
This package exposes the public v1 parsing contract for applications and renderer-specific integrations.
The shared .ibla container contract is defined in the repository format specification:
https://github.com/shawn0326/ibl-baker/blob/master/docs/format-spec.md
For manual browser validation, use the IBL Asset Viewer.
Installation
npm install @ibltools/ibla-loaderUsage
import { parseIBLA } from "@ibltools/ibla-loader";
const parsed = parseIBLA(buffer);
console.log(parsed.manifest.faceCount, parsed.manifest.encoding);Scope
The TypeScript loader is parser-only in v1.
Its responsibility is limited to:
- parsing the
.iblacontainer - validating basic format structure while parsing
- exposing chunk payloads as encoded bytes with derived metadata
It does not:
- decode PNG payloads
- decode RGBD into HDR float data
- prepare WebGL or WebGPU upload objects
- create engine-specific runtime textures
Those concerns are left to the application or renderer integration layer.
For KTX2 assets produced by the CLI (--output-format ktx2), use the sibling
@ibltools/ktx2-loader package. This package does not handle .ktx2 files.
Core Model
One parsed .ibla file maps to one texture payload set.
faceCount = 1means a 2D texture withmipCountimagesfaceCount = 6means a cubemap withmipCount * 6images- chunk identity is reconstructed from deterministic ordering, not stored explicitly in the file
- chunk payload bytes remain encoded according to manifest metadata
In the current CLI workflow, .ibla is used for specular and irradiance outputs.
BRDF LUT is emitted as a direct .png and does not go through the .ibla loader.
Public API
export function parseIBLA(buffer: ArrayBuffer | Uint8Array): ParsedIBLAparseIBLA is a fail-fast API.
In v1 it throws IBLAParseError when the container is invalid or unsupported.
Public Types
export type FaceName = 'px' | 'nx' | 'py' | 'ny' | 'pz' | 'nz'
export type BakeQuality = 'low' | 'medium' | 'high'
export type IBLAParseErrorCode =
| 'INVALID_HEADER'
| 'UNSUPPORTED_VERSION'
| 'INVALID_MANIFEST'
| 'UNSUPPORTED_FACE_COUNT'
| 'INVALID_CUBEMAP_DIMENSIONS'
| 'INVALID_CHUNK_TABLE_LENGTH'
| 'CHUNK_RANGE_OUT_OF_BOUNDS'
export class IBLAParseError extends Error {
readonly code: IBLAParseErrorCode
}
export interface ParsedIBLA {
header: {
version: number
flags: number
}
manifest: {
generator: string
generatorVersion: string
encoding: 'rgbd-srgb' | 'srgb' | 'linear'
container: 'png'
width: number
height: number
mipCount: number
faceCount: 1 | 6
build: {
rotation: number
samples: number
quality: BakeQuality
sourceFormat: 'hdr' | 'exr' | 'png' | 'jpg' | 'jpeg' | 'unknown'
}
}
chunks: ParsedChunk[]
}
export interface ParsedChunk {
index: number
mipLevel: number
face: FaceName | null
width: number
height: number
byteOffset: number
byteLength: number
encodedBytes: Uint8Array
}parseIBLA(buffer) Semantics
parseIBLA must:
- validate header magic and version
- read manifest metadata
- reconstruct chunk identity from
mipCount,faceCount, and deterministic ordering - derive each chunk's
widthandheightfrom manifestwidth,height, andmipLevel - reconstruct
byteOffsetfrom chunk-tablebyteLengthprefix sums - expose each chunk as encoded bytes plus derived chunk metadata
parseIBLA must throw IBLAParseError when:
- the header is invalid
- the manifest is missing required fields
- the manifest contains unsupported enum values
mipCountexceeds the natural mip-chain limit for the base dimensionsfaceCountis not supported by the format- cubemap dimensions are invalid for the format
- the chunk table length is inconsistent with manifest-declared topology
- chunk payload ranges exceed or do not exactly cover the binary section
Recommended v1 error-code mapping:
- invalid header bytes or magic ->
INVALID_HEADER - unsupported format version ->
UNSUPPORTED_VERSION - malformed JSON, missing required fields, unsupported manifest enums, or an excessive
mipCount->INVALID_MANIFEST - unsupported
faceCount->UNSUPPORTED_FACE_COUNT - invalid cubemap
width/heightrelationship ->INVALID_CUBEMAP_DIMENSIONS - inconsistent chunk-table byte length or entry count ->
INVALID_CHUNK_TABLE_LENGTH - payload byte ranges exceeding or not exactly covering the binary section ->
CHUNK_RANGE_OUT_OF_BOUNDS
Mip-chain topology in v1:
widthandheightare positive; non-power-of-two dimensions are supportedmipCountis in1..=1 + floor(log2(max(width, height)))- truncated chains are valid and need not reach
1x1 faceCount = 1may use rectangular dimensions; the longer dimension determines the mip limitfaceCount = 6requires square dimensions1x1allows one level; additional repeated1x1tail levels are rejected withINVALID_MANIFEST
Earlier loader versions did not enforce the natural mip limit. This validation rejects
otherwise self-consistent files with extra 1x1 tail levels; valid complete and
truncated chains remain supported. The parser derives image dimensions from metadata
and does not decode PNG payloads to validate their contents.
Dimension derivation in v1:
For 2D textures:
chunkWidth = max(1, floor(width / 2^mipLevel))
chunkHeight = max(1, floor(height / 2^mipLevel))For cubemaps:
chunkWidth = chunkHeight = max(1, floor(width / 2^mipLevel))Equivalently, readers may use:
dimensionAtMip(base, mipLevel) = max(1, base >> mipLevel)Parser Output Contract
See the runtime integration contract for irradiance normalization, BRDF LUT semantics and efficient GPU sampling.
With ArrayBuffer or same-realm Uint8Array/Node Buffer inputs, encodedBytes are
zero-copy views into the supplied backing buffer. Nonzero input-view offsets,
including Node Buffer slices, are supported. Metadata and
view objects are allocated, but PNG payload bytes are not copied. Keep the bytes
immutable while views are in use; a retained view keeps its backing buffer alive.
ArrayBuffer transfer detaches the sender's views. Parse/decode in the owning
thread and release loading buffers when upload completes.
Chunk byteOffset is relative to the binary section, not the whole file or its
underlying ArrayBuffer. Prefer encodedBytes over manually reconstructing views.
For specular assets, upload every supplied face and mip and use
manifest.mipCount for LOD = clamp(perceptualRoughness, 0, 1) * (mipCount - 1).
Interpolate adjacent levels. One level always uses LOD zero and cannot represent
roughness variation. Disable automatic mip generation and configure the runtime's
accessible level range to the uploaded images. Do not derive the count from base
width: the baker can emit a freshly filtered truncated chain with
--specular-min-size. Cropping an existing chain does not preserve its roughness
mapping. These are renderer integration responsibilities; the parser API and
IBLA v1 container remain unchanged.
The parser output is intentionally still encoded.
That means:
- PNG chunks remain PNG bytes
manifest.encodingremains metadata, not a decode stepbuild.sourceFormatremains provenance metadata, not a parse input- no promise is made about returning upload-ready pixel buffers
Applications can build on top of ParsedIBLA to implement:
- PNG decode helpers
- RGBD-to-float conversion
- WebGL upload preparation
- WebGPU upload preparation
- engine-specific runtime adapters
Those higher-level helpers can derive texture topology directly from manifest metadata:
faceCount = 1means a 2D texture withmipCountimagesfaceCount = 6means a cubemap withmipCount * 6images- cubemap face order remains the fixed v1 sequence
px, nx, py, ny, pz, nz
Reference decode code for encoding = "rgbd-srgb":
This describes a texel's encoding. Applying it after ordinary hardware filtering of packed RGBD is a fast approximation: interpolating RGB and D before division does not equal filtering reconstructed HDR. Integrations may use that path when visually adequate, or decode to filterable linear texels at load time if visible bleed or banding requires it. Preserve unassociated PNG alpha: D is data, not transparency. This does not change v1 bytes or add a decode API to the parser.
vec3 decodeRgbdSrgb(vec4 encodedSample) {
vec3 linearRgb = sRGBToLinear(encodedSample.rgb);
return linearRgb / encodedSample.a;
}The exact helper name is not fixed. What matters for v1 is the decode contract:
- apply sRGB-to-linear conversion to sampled RGB
- keep alpha as the linear
Dvalue - reconstruct linear HDR values as
rgb / D
If a runtime uploads the payload into an sRGB texture format and relies on hardware sRGB sampling, the explicit sRGBToLinear(encodedSample.rgb) step can be omitted because the sampled RGB values are already linearized.
Reference consumption contract for the other v1 encodings:
srgb- after PNG decode, payload RGB data must be treated as sRGB color data
- higher-level helpers may keep the decoded bytes in sRGB form or convert them to linear values depending on their output contract
linear- after PNG decode, payload values must be treated as linear data
- higher-level helpers must not apply sRGB-to-linear conversion implicitly
Defaults and Constraints
- v1 only supports
.iblaparsing, not direct PNG/LUT handling - v1 only supports
encoding = rgbd-srgb | srgb | linear - v1 only supports
container = png - v1 only supports
build.quality = low | medium | high - v1 chunk ordering is
mipLevelascending, then fixed cubemap face order when applicable
