@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
542 lines (541 loc) • 25.8 kB
JavaScript
/* eslint-disable @typescript-eslint/naming-convention */
import { Clamp } from "../Maths/math.scalar.functions.js";
import { Logger } from "../Misc/logger.js";
import { CubeMapToSphericalPolynomialTools } from "../Misc/HighDynamicRange/cubemapToSphericalPolynomial.js";
import { FromHalfFloat, ToHalfFloat } from "./textureTools.js";
import "../Engines/AbstractEngine/abstractEngine.cubeTexture.js";
// Based on demo done by Brandon Jones - http://media.tojicode.com/webgl-samples/dds.html
// All values and structures referenced from:
// http://msdn.microsoft.com/en-us/library/bb943991.aspx/
const DDS_MAGIC = 0x20534444;
const //DDSD_CAPS = 0x1,
//DDSD_HEIGHT = 0x2,
//DDSD_WIDTH = 0x4,
//DDSD_PITCH = 0x8,
//DDSD_PIXELFORMAT = 0x1000,
DDSD_MIPMAPCOUNT = 0x20000;
//DDSD_LINEARSIZE = 0x80000,
//DDSD_DEPTH = 0x800000;
// var DDSCAPS_COMPLEX = 0x8,
// DDSCAPS_MIPMAP = 0x400000,
// DDSCAPS_TEXTURE = 0x1000;
const DDSCAPS2_CUBEMAP = 0x200;
// DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,
// DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,
// DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,
// DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,
// DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,
// DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,
// DDSCAPS2_VOLUME = 0x200000;
const //DDPF_ALPHAPIXELS = 0x1,
//DDPF_ALPHA = 0x2,
DDPF_FOURCC = 0x4, DDPF_RGB = 0x40,
//DDPF_YUV = 0x200,
DDPF_LUMINANCE = 0x20000;
function FourCCToInt32(value) {
return value.charCodeAt(0) + (value.charCodeAt(1) << 8) + (value.charCodeAt(2) << 16) + (value.charCodeAt(3) << 24);
}
function Int32ToFourCC(value) {
return String.fromCharCode(value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, (value >> 24) & 0xff);
}
const FOURCC_DXT1 = FourCCToInt32("DXT1");
const FOURCC_DXT3 = FourCCToInt32("DXT3");
const FOURCC_DXT5 = FourCCToInt32("DXT5");
const FOURCC_DX10 = FourCCToInt32("DX10");
const FOURCC_D3DFMT_R16G16B16A16F = 113;
const FOURCC_D3DFMT_R32G32B32A32F = 116;
const DXGI_FORMAT_R32G32B32A32_FLOAT = 2;
const DXGI_FORMAT_R16G16B16A16_FLOAT = 10;
const DXGI_FORMAT_B8G8R8X8_UNORM = 88;
const headerLengthInt = 31; // The header length in 32 bit ints
// Offsets into the header array
const off_magic = 0;
const off_size = 1;
const off_flags = 2;
const off_height = 3;
const off_width = 4;
const off_mipmapCount = 7;
const off_pfFlags = 20;
const off_pfFourCC = 21;
const off_RGBbpp = 22;
const off_RMask = 23;
const off_GMask = 24;
const off_BMask = 25;
const off_AMask = 26;
// var off_caps1 = 27;
const off_caps2 = 28;
// var off_caps3 = 29;
// var off_caps4 = 30;
const off_dxgiFormat = 32;
/**
* Class used to provide DDS decompression tools
*/
export class DDSTools {
/**
* Gets DDS information from an array buffer
* @param data defines the array buffer view to read data from
* @returns the DDS information
*/
static GetDDSInfo(data) {
const header = new Int32Array(data.buffer, data.byteOffset, headerLengthInt);
const extendedHeader = new Int32Array(data.buffer, data.byteOffset, headerLengthInt + 4);
let mipmapCount = 1;
if (header[off_flags] & DDSD_MIPMAPCOUNT) {
mipmapCount = Math.max(1, header[off_mipmapCount]);
}
const fourCC = header[off_pfFourCC];
const dxgiFormat = fourCC === FOURCC_DX10 ? extendedHeader[off_dxgiFormat] : 0;
let textureType = 0;
switch (fourCC) {
case FOURCC_D3DFMT_R16G16B16A16F:
textureType = 2;
break;
case FOURCC_D3DFMT_R32G32B32A32F:
textureType = 1;
break;
case FOURCC_DX10:
if (dxgiFormat === DXGI_FORMAT_R16G16B16A16_FLOAT) {
textureType = 2;
break;
}
if (dxgiFormat === DXGI_FORMAT_R32G32B32A32_FLOAT) {
textureType = 1;
break;
}
}
return {
width: header[off_width],
height: header[off_height],
mipmapCount: mipmapCount,
isFourCC: (header[off_pfFlags] & DDPF_FOURCC) === DDPF_FOURCC,
isRGB: (header[off_pfFlags] & DDPF_RGB) === DDPF_RGB,
isLuminance: (header[off_pfFlags] & DDPF_LUMINANCE) === DDPF_LUMINANCE,
isCube: (header[off_caps2] & DDSCAPS2_CUBEMAP) === DDSCAPS2_CUBEMAP,
isCompressed: fourCC === FOURCC_DXT1 || fourCC === FOURCC_DXT3 || fourCC === FOURCC_DXT5,
dxgiFormat: dxgiFormat,
textureType: textureType,
};
}
static _GetHalfFloatAsFloatRGBAArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer, lod) {
const destArray = new Float32Array(dataLength);
const srcData = new Uint16Array(arrayBuffer, dataOffset);
let index = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const srcPos = (x + y * width) * 4;
destArray[index] = FromHalfFloat(srcData[srcPos]);
destArray[index + 1] = FromHalfFloat(srcData[srcPos + 1]);
destArray[index + 2] = FromHalfFloat(srcData[srcPos + 2]);
if (DDSTools.StoreLODInAlphaChannel) {
destArray[index + 3] = lod;
}
else {
destArray[index + 3] = FromHalfFloat(srcData[srcPos + 3]);
}
index += 4;
}
}
return destArray;
}
static _GetHalfFloatRGBAArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer, lod) {
if (DDSTools.StoreLODInAlphaChannel) {
const destArray = new Uint16Array(dataLength);
const srcData = new Uint16Array(arrayBuffer, dataOffset);
let index = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const srcPos = (x + y * width) * 4;
destArray[index] = srcData[srcPos];
destArray[index + 1] = srcData[srcPos + 1];
destArray[index + 2] = srcData[srcPos + 2];
destArray[index + 3] = ToHalfFloat(lod);
index += 4;
}
}
return destArray;
}
return new Uint16Array(arrayBuffer, dataOffset, dataLength);
}
static _GetFloatRGBAArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer, lod) {
if (DDSTools.StoreLODInAlphaChannel) {
const destArray = new Float32Array(dataLength);
const srcData = new Float32Array(arrayBuffer, dataOffset);
let index = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const srcPos = (x + y * width) * 4;
destArray[index] = srcData[srcPos];
destArray[index + 1] = srcData[srcPos + 1];
destArray[index + 2] = srcData[srcPos + 2];
destArray[index + 3] = lod;
index += 4;
}
}
return destArray;
}
return new Float32Array(arrayBuffer, dataOffset, dataLength);
}
static _GetFloatAsHalfFloatRGBAArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer, lod) {
const destArray = new Uint16Array(dataLength);
const srcData = new Float32Array(arrayBuffer, dataOffset);
let index = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
destArray[index] = ToHalfFloat(srcData[index]);
destArray[index + 1] = ToHalfFloat(srcData[index + 1]);
destArray[index + 2] = ToHalfFloat(srcData[index + 2]);
if (DDSTools.StoreLODInAlphaChannel) {
destArray[index + 3] = ToHalfFloat(lod);
}
else {
destArray[index + 3] = ToHalfFloat(srcData[index + 3]);
}
index += 4;
}
}
return destArray;
}
static _GetFloatAsUIntRGBAArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer, lod) {
const destArray = new Uint8Array(dataLength);
const srcData = new Float32Array(arrayBuffer, dataOffset);
let index = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const srcPos = (x + y * width) * 4;
destArray[index] = Clamp(srcData[srcPos]) * 255;
destArray[index + 1] = Clamp(srcData[srcPos + 1]) * 255;
destArray[index + 2] = Clamp(srcData[srcPos + 2]) * 255;
if (DDSTools.StoreLODInAlphaChannel) {
destArray[index + 3] = lod;
}
else {
destArray[index + 3] = Clamp(srcData[srcPos + 3]) * 255;
}
index += 4;
}
}
return destArray;
}
static _GetHalfFloatAsUIntRGBAArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer, lod) {
const destArray = new Uint8Array(dataLength);
const srcData = new Uint16Array(arrayBuffer, dataOffset);
let index = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const srcPos = (x + y * width) * 4;
destArray[index] = Clamp(FromHalfFloat(srcData[srcPos])) * 255;
destArray[index + 1] = Clamp(FromHalfFloat(srcData[srcPos + 1])) * 255;
destArray[index + 2] = Clamp(FromHalfFloat(srcData[srcPos + 2])) * 255;
if (DDSTools.StoreLODInAlphaChannel) {
destArray[index + 3] = lod;
}
else {
destArray[index + 3] = Clamp(FromHalfFloat(srcData[srcPos + 3])) * 255;
}
index += 4;
}
}
return destArray;
}
static _GetRGBAArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer, rOffset, gOffset, bOffset, aOffset) {
const byteArray = new Uint8Array(dataLength);
const srcData = new Uint8Array(arrayBuffer, dataOffset);
let index = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const srcPos = (x + y * width) * 4;
byteArray[index] = srcData[srcPos + rOffset];
byteArray[index + 1] = srcData[srcPos + gOffset];
byteArray[index + 2] = srcData[srcPos + bOffset];
byteArray[index + 3] = srcData[srcPos + aOffset];
index += 4;
}
}
return byteArray;
}
static _ExtractLongWordOrder(value) {
if (value === 0 || value === 255 || value === -16777216) {
return 0;
}
return 1 + DDSTools._ExtractLongWordOrder(value >> 8);
}
static _GetRGBArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer, rOffset, gOffset, bOffset) {
const byteArray = new Uint8Array(dataLength);
const srcData = new Uint8Array(arrayBuffer, dataOffset);
let index = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const srcPos = (x + y * width) * 3;
byteArray[index] = srcData[srcPos + rOffset];
byteArray[index + 1] = srcData[srcPos + gOffset];
byteArray[index + 2] = srcData[srcPos + bOffset];
index += 3;
}
}
return byteArray;
}
static _GetLuminanceArrayBuffer(width, height, dataOffset, dataLength, arrayBuffer) {
const byteArray = new Uint8Array(dataLength);
const srcData = new Uint8Array(arrayBuffer, dataOffset);
let index = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const srcPos = x + y * width;
byteArray[index] = srcData[srcPos];
index++;
}
}
return byteArray;
}
/**
* Uploads DDS Levels to a Babylon Texture
* @internal
*/
static UploadDDSLevels(engine, texture, data, info, loadMipmaps, faces, lodIndex = -1, currentFace, destTypeMustBeFilterable = true) {
let sphericalPolynomialFaces = null;
if (info.sphericalPolynomial) {
sphericalPolynomialFaces = [];
}
const ext = !!engine.getCaps().s3tc;
// TODO WEBGPU Once generateMipMaps is split into generateMipMaps + hasMipMaps in InternalTexture this line can be removed
texture.generateMipMaps = loadMipmaps;
const header = new Int32Array(data.buffer, data.byteOffset, headerLengthInt);
let fourCC, width, height, dataLength = 0, dataOffset;
let byteArray, mipmapCount, mip;
let internalCompressedFormat = 0;
let blockBytes = 1;
if (header[off_magic] !== DDS_MAGIC) {
Logger.Error("Invalid magic number in DDS header");
return;
}
if (!info.isFourCC && !info.isRGB && !info.isLuminance) {
Logger.Error("Unsupported format, must contain a FourCC, RGB or LUMINANCE code");
return;
}
if (info.isCompressed && !ext) {
Logger.Error("Compressed textures are not supported on this platform.");
return;
}
let bpp = header[off_RGBbpp];
dataOffset = header[off_size] + 4;
let computeFormats = false;
if (info.isFourCC) {
fourCC = header[off_pfFourCC];
switch (fourCC) {
case FOURCC_DXT1:
blockBytes = 8;
internalCompressedFormat = 33777;
break;
case FOURCC_DXT3:
blockBytes = 16;
internalCompressedFormat = 33778;
break;
case FOURCC_DXT5:
blockBytes = 16;
internalCompressedFormat = 33779;
break;
case FOURCC_D3DFMT_R16G16B16A16F:
computeFormats = true;
bpp = 64;
break;
case FOURCC_D3DFMT_R32G32B32A32F:
computeFormats = true;
bpp = 128;
break;
case FOURCC_DX10: {
// There is an additionnal header so dataOffset need to be changed
dataOffset += 5 * 4; // 5 uints
let supported = false;
switch (info.dxgiFormat) {
case DXGI_FORMAT_R16G16B16A16_FLOAT:
computeFormats = true;
bpp = 64;
supported = true;
break;
case DXGI_FORMAT_R32G32B32A32_FLOAT:
computeFormats = true;
bpp = 128;
supported = true;
break;
case DXGI_FORMAT_B8G8R8X8_UNORM:
info.isRGB = true;
info.isFourCC = false;
bpp = 32;
supported = true;
break;
}
if (supported) {
break;
}
}
// eslint-disable-next-line no-fallthrough
default:
Logger.Error(["Unsupported FourCC code:", Int32ToFourCC(fourCC)]);
return;
}
}
const rOffset = DDSTools._ExtractLongWordOrder(header[off_RMask]);
const gOffset = DDSTools._ExtractLongWordOrder(header[off_GMask]);
const bOffset = DDSTools._ExtractLongWordOrder(header[off_BMask]);
const aOffset = DDSTools._ExtractLongWordOrder(header[off_AMask]);
if (computeFormats) {
internalCompressedFormat = engine._getRGBABufferInternalSizedFormat(info.textureType);
}
mipmapCount = 1;
if (header[off_flags] & DDSD_MIPMAPCOUNT && loadMipmaps !== false) {
mipmapCount = Math.max(1, header[off_mipmapCount]);
}
const startFace = currentFace || 0;
const caps = engine.getCaps();
for (let face = startFace; face < faces; face++) {
width = header[off_width];
height = header[off_height];
for (mip = 0; mip < mipmapCount; ++mip) {
if (lodIndex === -1 || lodIndex === mip) {
// In case of fixed LOD, if the lod has just been uploaded, early exit.
const i = lodIndex === -1 ? mip : 0;
if (!info.isCompressed && info.isFourCC) {
texture.format = 5;
dataLength = width * height * 4;
let floatArray = null;
if (engine._badOS || engine._badDesktopOS || (!caps.textureHalfFloat && !caps.textureFloat)) {
// Required because iOS has many issues with float and half float generation
if (bpp === 128) {
floatArray = DDSTools._GetFloatAsUIntRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i);
if (sphericalPolynomialFaces && i == 0) {
sphericalPolynomialFaces.push(DDSTools._GetFloatRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i));
}
}
else if (bpp === 64) {
floatArray = DDSTools._GetHalfFloatAsUIntRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i);
if (sphericalPolynomialFaces && i == 0) {
sphericalPolynomialFaces.push(DDSTools._GetHalfFloatAsFloatRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i));
}
}
texture.type = 0;
}
else {
const floatAvailable = caps.textureFloat && ((destTypeMustBeFilterable && caps.textureFloatLinearFiltering) || !destTypeMustBeFilterable);
const halfFloatAvailable = caps.textureHalfFloat && ((destTypeMustBeFilterable && caps.textureHalfFloatLinearFiltering) || !destTypeMustBeFilterable);
const destType = (bpp === 128 || (bpp === 64 && !halfFloatAvailable)) && floatAvailable
? 1
: (bpp === 64 || (bpp === 128 && !floatAvailable)) && halfFloatAvailable
? 2
: 0;
let dataGetter;
let dataGetterPolynomial = null;
switch (bpp) {
case 128: {
switch (destType) {
case 1:
dataGetter = DDSTools._GetFloatRGBAArrayBuffer;
dataGetterPolynomial = null;
break;
case 2:
dataGetter = DDSTools._GetFloatAsHalfFloatRGBAArrayBuffer;
dataGetterPolynomial = DDSTools._GetFloatRGBAArrayBuffer;
break;
case 0:
dataGetter = DDSTools._GetFloatAsUIntRGBAArrayBuffer;
dataGetterPolynomial = DDSTools._GetFloatRGBAArrayBuffer;
break;
}
break;
}
default: {
// 64 bpp
switch (destType) {
case 1:
dataGetter = DDSTools._GetHalfFloatAsFloatRGBAArrayBuffer;
dataGetterPolynomial = null;
break;
case 2:
dataGetter = DDSTools._GetHalfFloatRGBAArrayBuffer;
dataGetterPolynomial = DDSTools._GetHalfFloatAsFloatRGBAArrayBuffer;
break;
case 0:
dataGetter = DDSTools._GetHalfFloatAsUIntRGBAArrayBuffer;
dataGetterPolynomial = DDSTools._GetHalfFloatAsFloatRGBAArrayBuffer;
break;
}
break;
}
}
texture.type = destType;
floatArray = dataGetter(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i);
if (sphericalPolynomialFaces && i == 0) {
sphericalPolynomialFaces.push(dataGetterPolynomial ? dataGetterPolynomial(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i) : floatArray);
}
}
if (floatArray) {
engine._uploadDataToTextureDirectly(texture, floatArray, face, i);
}
}
else if (info.isRGB) {
texture.type = 0;
if (bpp === 24) {
texture.format = 4;
dataLength = width * height * 3;
byteArray = DDSTools._GetRGBArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, rOffset, gOffset, bOffset);
engine._uploadDataToTextureDirectly(texture, byteArray, face, i);
}
else {
// 32
texture.format = 5;
dataLength = width * height * 4;
byteArray = DDSTools._GetRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, rOffset, gOffset, bOffset, aOffset);
engine._uploadDataToTextureDirectly(texture, byteArray, face, i);
}
}
else if (info.isLuminance) {
const unpackAlignment = engine._getUnpackAlignement();
const unpaddedRowSize = width;
const paddedRowSize = Math.floor((width + unpackAlignment - 1) / unpackAlignment) * unpackAlignment;
dataLength = paddedRowSize * (height - 1) + unpaddedRowSize;
byteArray = DDSTools._GetLuminanceArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer);
texture.format = 1;
texture.type = 0;
engine._uploadDataToTextureDirectly(texture, byteArray, face, i);
}
else {
dataLength = (((Math.max(4, width) / 4) * Math.max(4, height)) / 4) * blockBytes;
byteArray = new Uint8Array(data.buffer, data.byteOffset + dataOffset, dataLength);
texture.type = 0;
engine._uploadCompressedDataToTextureDirectly(texture, internalCompressedFormat, width, height, byteArray, face, i);
}
}
dataOffset += bpp ? width * height * (bpp / 8) : dataLength;
width *= 0.5;
height *= 0.5;
width = Math.max(1.0, width);
height = Math.max(1.0, height);
}
if (currentFace !== undefined) {
// Loading a single face
break;
}
}
if (sphericalPolynomialFaces && sphericalPolynomialFaces.length > 0) {
info.sphericalPolynomial = CubeMapToSphericalPolynomialTools.ConvertCubeMapToSphericalPolynomial({
size: header[off_width],
right: sphericalPolynomialFaces[0],
left: sphericalPolynomialFaces[1],
up: sphericalPolynomialFaces[2],
down: sphericalPolynomialFaces[3],
front: sphericalPolynomialFaces[4],
back: sphericalPolynomialFaces[5],
format: 5,
type: 1,
gammaSpace: false,
});
}
else {
info.sphericalPolynomial = undefined;
}
}
}
/**
* Gets or sets a boolean indicating that LOD info is stored in alpha channel (false by default)
*/
DDSTools.StoreLODInAlphaChannel = false;
//# sourceMappingURL=dds.js.map