Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for compressed sRGB formats #6743

Merged
merged 7 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/src/examples/graphics/texture-basis.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pc.basisInitialize({
});

const assets = {
color: new pc.Asset('color', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-color.basis' }),
color: new pc.Asset('color', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-color.basis' }, { srgb: true }),
gloss: new pc.Asset('gloss', 'texture', { url: rootPath + '/static/assets/textures/seaside-rocks01-gloss.basis' }),
normal: new pc.Asset(
'normal',
Expand Down
8 changes: 4 additions & 4 deletions src/framework/handlers/basis-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ function BasisWorker() {
};

// return true if the texture dimensions are valid for the target format
const dimensionsValid = (width, height, format, webgl2) => {
const dimensionsValid = (width, height, format) => {
switch (format) {
// etc1, 2
case BASIS_FORMAT.cTFETC1:
Expand All @@ -171,7 +171,7 @@ function BasisWorker() {
// pvrtc
case BASIS_FORMAT.cTFPVRTC1_4_RGB:
case BASIS_FORMAT.cTFPVRTC1_4_RGBA:
return isPOT(width, height) && ((width === height) || webgl2);
return isPOT(width, height);
// astc
case BASIS_FORMAT.cTFASTC_4x4:
return true;
Expand Down Expand Up @@ -221,7 +221,7 @@ function BasisWorker() {
basisFormat = hasAlpha ? alphaMapping[format] : opaqueMapping[format];

// if image dimensions don't work on target, fall back to uncompressed
if (!dimensionsValid(width, height, basisFormat, options.deviceDetails.webgl2)) {
if (!dimensionsValid(width, height, basisFormat)) {
basisFormat = hasAlpha ? BASIS_FORMAT.cTFRGBA32 : BASIS_FORMAT.cTFRGB565;
}
}
Expand Down Expand Up @@ -307,7 +307,7 @@ function BasisWorker() {
basisFormat = hasAlpha ? alphaMapping[format] : opaqueMapping[format];

// if image dimensions don't work on target, fall back to uncompressed
if (!dimensionsValid(width, height, basisFormat, options.deviceDetails.webgl2)) {
if (!dimensionsValid(width, height, basisFormat)) {
basisFormat = hasAlpha ? BASIS_FORMAT.cTFRGBA32 : BASIS_FORMAT.cTFRGB565;
}
}
Expand Down
1 change: 0 additions & 1 deletion src/framework/handlers/basis.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,6 @@ function basisTranscode(device, url, data, callback, options) {

if (!deviceDetails) {
deviceDetails = {
webgl2: device.isWebGL2,
formats: getCompressionFormats(device)
};
}
Expand Down
42 changes: 37 additions & 5 deletions src/framework/parsers/glb-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2050,7 +2050,31 @@ const createImages = (gltf, bufferViews, urlBase, registry, options) => {
'image/vnd-ms.dds': 'dds'
};

const loadTexture = (gltfImage, url, bufferView, mimeType, options) => {
// a Set of image indices that use sRGB textures (base and emissive)
const getGammaTextures = (gltf) => {
const set = new Set();

if (gltf.hasOwnProperty('materials')) {
gltf.materials.forEach((gltfMaterial) => {

// base texture
if (gltfMaterial.hasOwnProperty('pbrMetallicRoughness')) {
const pbrData = gltfMaterial.pbrMetallicRoughness;
if (pbrData.hasOwnProperty('baseColorTexture')) {
set.add(pbrData.baseColorTexture.index);
}
}

// emissive
if (gltfMaterial.hasOwnProperty('emissiveTexture')) {
set.add(gltfMaterial.emissiveTexture.index);
}
});
}
return set;
};

const loadTexture = (gltfImage, url, bufferView, mimeType, options, srgb) => {
return new Promise((resolve, reject) => {
const continuation = (bufferViewData) => {
const name = (gltfImage.name || 'gltf-texture') + '-' + gltfTextureUniqueId++;
Expand All @@ -2070,7 +2094,9 @@ const createImages = (gltf, bufferViews, urlBase, registry, options) => {
}

// create and load the asset
const asset = new Asset(name, 'texture', file, null, options);
const data = { srgb };

const asset = new Asset(name, 'texture', file, data, options);
asset.on('load', asset => resolve(asset));
asset.on('error', err => reject(err));
registry.add(asset);
Expand All @@ -2085,6 +2111,8 @@ const createImages = (gltf, bufferViews, urlBase, registry, options) => {
});
};

const gammaTextures = getGammaTextures(gltf);

return gltf.images.map((gltfImage, i) => {
if (preprocess) {
preprocess(gltfImage);
Expand All @@ -2108,17 +2136,21 @@ const createImages = (gltf, bufferViews, urlBase, registry, options) => {
}

promise = promise.then((textureAsset) => {

// if the image uses sRGB, pass it as an option to the texture creation
const srgb = gammaTextures.has(i);

if (textureAsset) {
return textureAsset;
} else if (gltfImage.hasOwnProperty('uri')) {
// uri specified
if (isDataURI(gltfImage.uri)) {
return loadTexture(gltfImage, gltfImage.uri, null, getDataURIMimeType(gltfImage.uri), null);
return loadTexture(gltfImage, gltfImage.uri, null, getDataURIMimeType(gltfImage.uri), null, srgb);
}
return loadTexture(gltfImage, ABSOLUTE_URL.test(gltfImage.uri) ? gltfImage.uri : path.join(urlBase, gltfImage.uri), null, null, { crossOrigin: 'anonymous' });
return loadTexture(gltfImage, ABSOLUTE_URL.test(gltfImage.uri) ? gltfImage.uri : path.join(urlBase, gltfImage.uri), null, null, { crossOrigin: 'anonymous' }, srgb);
} else if (gltfImage.hasOwnProperty('bufferView') && gltfImage.hasOwnProperty('mimeType')) {
// bufferview
return loadTexture(gltfImage, null, bufferViews[gltfImage.bufferView], gltfImage.mimeType, null);
return loadTexture(gltfImage, null, bufferViews[gltfImage.bufferView], gltfImage.mimeType, null, srgb);
}

// fail
Expand Down
5 changes: 3 additions & 2 deletions src/framework/parsers/texture/basis.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ADDRESS_CLAMP_TO_EDGE, ADDRESS_REPEAT, TEXHINT_ASSET } from '../../../platform/graphics/constants.js';
import { ADDRESS_CLAMP_TO_EDGE, ADDRESS_REPEAT, TEXHINT_ASSET, getPixelFormatLinearSampling } from '../../../platform/graphics/constants.js';
import { Texture } from '../../../platform/graphics/texture.js';

import { Asset } from '../../asset/asset.js';
Expand Down Expand Up @@ -46,6 +46,7 @@ class BasisParser extends TextureParser {

// our async transcode call provides the neat structure we need to create the texture instance
open(url, data, device, textureOptions = {}) {
const format = textureOptions.srgb ? getPixelFormatLinearSampling(data.format) : data.format;
const texture = new Texture(device, {
name: url,
// #if _PROFILER
Expand All @@ -55,7 +56,7 @@ class BasisParser extends TextureParser {
addressV: data.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
width: data.width,
height: data.height,
format: data.format,
format: format,
cubemap: data.cubemap,
levels: data.levels,

Expand Down
10 changes: 6 additions & 4 deletions src/framework/parsers/texture/ktx.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
PIXELFORMAT_PVRTC_4BPP_RGB_1, PIXELFORMAT_PVRTC_2BPP_RGB_1, PIXELFORMAT_PVRTC_4BPP_RGBA_1, PIXELFORMAT_PVRTC_2BPP_RGBA_1,
PIXELFORMAT_RGB8, PIXELFORMAT_RGBA8, PIXELFORMAT_SRGB8, PIXELFORMAT_SRGBA8,
PIXELFORMAT_111110F, PIXELFORMAT_RGB16F, PIXELFORMAT_RGBA16F,
TEXHINT_ASSET
TEXHINT_ASSET,
getPixelFormatLinearSampling
} from '../../../platform/graphics/constants.js';
import { Texture } from '../../../platform/graphics/texture.js';

Expand All @@ -32,8 +33,8 @@ const KNOWN_FORMATS = {
0x8C03: PIXELFORMAT_PVRTC_2BPP_RGBA_1,

// uncompressed formats
0x8051: PIXELFORMAT_RGB8, // GL_RGB8
0x8058: PIXELFORMAT_RGBA8, // GL_RGBA8
0x8051: PIXELFORMAT_RGB8, // GL_RGB8
0x8058: PIXELFORMAT_RGBA8, // GL_RGBA8
0x8C41: PIXELFORMAT_SRGB8, // GL_SRGB8
0x8C43: PIXELFORMAT_SRGBA8, // GL_SRGB8_ALPHA8
0x8C3A: PIXELFORMAT_111110F, // GL_R11F_G11F_B10F
Expand Down Expand Up @@ -69,6 +70,7 @@ class KtxParser extends TextureParser {
return null;
}

const format = textureOptions.srgb ? getPixelFormatLinearSampling(textureData.format) : textureData.format;
const texture = new Texture(device, {
name: url,
// #if _PROFILER
Expand All @@ -78,7 +80,7 @@ class KtxParser extends TextureParser {
addressV: textureData.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
width: textureData.width,
height: textureData.height,
format: textureData.format,
format: format,
cubemap: textureData.cubemap,
levels: textureData.levels,

Expand Down
5 changes: 3 additions & 2 deletions src/framework/parsers/texture/ktx2.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Debug } from '../../../core/debug.js';
import { ReadStream } from '../../../core/read-stream.js';

import { ADDRESS_CLAMP_TO_EDGE, ADDRESS_REPEAT, TEXHINT_ASSET } from '../../../platform/graphics/constants.js';
import { ADDRESS_CLAMP_TO_EDGE, ADDRESS_REPEAT, TEXHINT_ASSET, getPixelFormatLinearSampling } from '../../../platform/graphics/constants.js';
import { Texture } from '../../../platform/graphics/texture.js';

import { Asset } from '../../asset/asset.js';
Expand Down Expand Up @@ -37,6 +37,7 @@ class Ktx2Parser extends TextureParser {
}

open(url, data, device, textureOptions = {}) {
const format = textureOptions.srgb ? getPixelFormatLinearSampling(data.format) : data.format;
const texture = new Texture(device, {
name: url,
// #if _PROFILER
Expand All @@ -46,7 +47,7 @@ class Ktx2Parser extends TextureParser {
addressV: data.cubemap ? ADDRESS_CLAMP_TO_EDGE : ADDRESS_REPEAT,
width: data.width,
height: data.height,
format: data.format,
format: format,
cubemap: data.cubemap,
levels: data.levels,

Expand Down
142 changes: 128 additions & 14 deletions src/platform/graphics/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -946,8 +946,97 @@ export const PIXELFORMAT_R8 = 52;
*/
export const PIXELFORMAT_RG8 = 53;

// map of engine PIXELFORMAT_*** enums to information about the format
// .srgb: true if sampling the format returns value in sRGB space, otherwise in linear space
/**
* Format equivalent to {@link PIXELFORMAT_DXT1} but sampled in linear color space.
*
* @type {number}
* @category Graphics
*/
export const PIXELFORMAT_DXT1_SRGB = 54;
mvaligursky marked this conversation as resolved.
Show resolved Hide resolved

/**
* Format equivalent to {@link PIXELFORMAT_DXT3} but sampled in linear color space.
*
* @type {number}
* @category Graphics
*/
export const PIXELFORMAT_DXT3_SRGB = 55;

/**
* Format equivalent to {@link PIXELFORMAT_DXT5} but sampled in linear color space.
*
* @type {number}
* @category Graphics
*/
export const PIXELFORMAT_DXT5_SRGB = 56;

/**
* Format equivalent to {@link PIXELFORMAT_PVRTC_2BPP_RGB_1} but sampled in linear color space.
*
* @type {number}
* @category Graphics
*/
export const PIXELFORMAT_PVRTC_2BPP_SRGB_1 = 57;

/**
* Format equivalent to {@link PIXELFORMAT_PVRTC_2BPP_RGBA_1} but sampled in linear color space.
*
* @type {number}
* @category Graphics
*/
export const PIXELFORMAT_PVRTC_2BPP_SRGBA_1 = 58;

/**
* Format equivalent to {@link PIXELFORMAT_PVRTC_4BPP_RGB_1} but sampled in linear color space.
*
* @type {number}
* @category Graphics
*/
export const PIXELFORMAT_PVRTC_4BPP_SRGB_1 = 59;

/**
* Format equivalent to {@link PIXELFORMAT_PVRTC_4BPP_RGBA_1} but sampled in linear color space.
*
* @type {number}
* @category Graphics
*/
export const PIXELFORMAT_PVRTC_4BPP_SRGBA_1 = 60;

/**
* Format equivalent to {@link PIXELFORMAT_ETC2_RGB} but sampled in linear color space.
*
* @type {number}
* @category Graphics
*/
export const PIXELFORMAT_ETC2_SRGB = 61;

/**
* Format equivalent to {@link PIXELFORMAT_ETC2_RGBA} but sampled in linear color space.
*
* @type {number}
* @category Graphics
*/
export const PIXELFORMAT_ETC2_SRGBA = 62;

/**
* Format equivalent to {@link PIXELFORMAT_ASTC_4x4} but sampled in linear color space.
*
* @type {number}
* @category Graphics
*/
export const PIXELFORMAT_ASTC_4x4_SRGB = 63;

/**
* Information about pixel formats.
*
* srgb: true if sampling the sRGB space pixel value returns the value in sRGB space, otherwise
* the value gets automatically converted to a linear space. Only applicable to formats typically
* used for color data in LDR space, which are often stored in sRGB space.
* linearFormat: the corresponding linear, which automatically converts the sRGB value to linear
*
* @type {Map<number, { name: string, size?: number, blockSize?: number, srgb?: boolean, linearFormat?: number, isInt?: boolean }>}
* @ignore
*/
export const pixelFormatInfo = new Map([

// float formats
Expand All @@ -960,7 +1049,7 @@ export const pixelFormatInfo = new Map([
[PIXELFORMAT_RGBA5551, { name: 'RGBA5551', size: 2, srgb: true }],
[PIXELFORMAT_RGBA4, { name: 'RGBA4', size: 2, srgb: true }],
[PIXELFORMAT_RGB8, { name: 'RGB8', size: 4, srgb: true }],
[PIXELFORMAT_RGBA8, { name: 'RGBA8', size: 4, srgb: true }],
[PIXELFORMAT_RGBA8, { name: 'RGBA8', size: 4, srgb: true, linearFormat: PIXELFORMAT_SRGBA8 }],
[PIXELFORMAT_R16F, { name: 'R16F', size: 2 }],
[PIXELFORMAT_RG16F, { name: 'RG16F', size: 4 }],
[PIXELFORMAT_RGB16F, { name: 'RGB16F', size: 8 }],
Expand All @@ -976,20 +1065,32 @@ export const pixelFormatInfo = new Map([
[PIXELFORMAT_BGRA8, { name: 'BGRA8', size: 4 }],

// compressed formats
[PIXELFORMAT_DXT1, { name: 'DXT1', blockSize: 8, srgb: true }],
[PIXELFORMAT_DXT3, { name: 'DXT3', blockSize: 16, srgb: true }],
[PIXELFORMAT_DXT5, { name: 'DXT5', blockSize: 16, srgb: true }],
[PIXELFORMAT_DXT1, { name: 'DXT1', blockSize: 8, srgb: true, linearFormat: PIXELFORMAT_DXT1_SRGB }],
mvaligursky marked this conversation as resolved.
Show resolved Hide resolved
[PIXELFORMAT_DXT3, { name: 'DXT3', blockSize: 16, srgb: true, linearFormat: PIXELFORMAT_DXT3_SRGB }],
[PIXELFORMAT_DXT5, { name: 'DXT5', blockSize: 16, srgb: true, linearFormat: PIXELFORMAT_DXT5_SRGB }],
[PIXELFORMAT_ETC1, { name: 'ETC1', blockSize: 8, srgb: true }],
[PIXELFORMAT_ETC2_RGB, { name: 'ETC2_RGB', blockSize: 8, srgb: true }],
[PIXELFORMAT_ETC2_RGBA, { name: 'ETC2_RGBA', blockSize: 16, srgb: true }],
[PIXELFORMAT_PVRTC_2BPP_RGB_1, { name: 'PVRTC_2BPP_RGB_1', blockSize: 8, srgb: true }],
[PIXELFORMAT_PVRTC_2BPP_RGBA_1, { name: 'PVRTC_2BPP_RGBA_1', blockSize: 8, srgb: true }],
[PIXELFORMAT_PVRTC_4BPP_RGB_1, { name: 'PVRTC_4BPP_RGB_1', blockSize: 8, srgb: true }],
[PIXELFORMAT_PVRTC_4BPP_RGBA_1, { name: 'PVRTC_4BPP_RGBA_1', blockSize: 8, srgb: true }],
[PIXELFORMAT_ASTC_4x4, { name: 'ASTC_4x4', blockSize: 16, srgb: true }],
[PIXELFORMAT_ETC2_RGB, { name: 'ETC2_RGB', blockSize: 8, srgb: true, linearFormat: PIXELFORMAT_ETC2_SRGB }],
[PIXELFORMAT_ETC2_RGBA, { name: 'ETC2_RGBA', blockSize: 16, srgb: true, linearFormat: PIXELFORMAT_ETC2_SRGBA }],
[PIXELFORMAT_PVRTC_2BPP_RGB_1, { name: 'PVRTC_2BPP_RGB_1', blockSize: 8, srgb: true, linearFormat: PIXELFORMAT_PVRTC_2BPP_SRGB_1 }],
[PIXELFORMAT_PVRTC_2BPP_RGBA_1, { name: 'PVRTC_2BPP_RGBA_1', blockSize: 8, srgb: true, linearFormat: PIXELFORMAT_PVRTC_2BPP_SRGBA_1 }],
[PIXELFORMAT_PVRTC_4BPP_RGB_1, { name: 'PVRTC_4BPP_RGB_1', blockSize: 8, srgb: true, linearFormat: PIXELFORMAT_PVRTC_4BPP_SRGB_1 }],
[PIXELFORMAT_PVRTC_4BPP_RGBA_1, { name: 'PVRTC_4BPP_RGBA_1', blockSize: 8, srgb: true, linearFormat: PIXELFORMAT_PVRTC_4BPP_SRGBA_1 }],
[PIXELFORMAT_ASTC_4x4, { name: 'ASTC_4x4', blockSize: 16, srgb: true, linearFormat: PIXELFORMAT_ASTC_4x4_SRGB }],
[PIXELFORMAT_ATC_RGB, { name: 'ATC_RGB', blockSize: 8, srgb: true }],
[PIXELFORMAT_ATC_RGBA, { name: 'ATC_RGBA', blockSize: 16, srgb: true }],

// compressed sRGB formats
[PIXELFORMAT_DXT1_SRGB, { name: 'DXT1_SRGB', blockSize: 8 }],
[PIXELFORMAT_DXT3_SRGB, { name: 'DXT3_SRGB', blockSize: 16 }],
[PIXELFORMAT_DXT5_SRGB, { name: 'DXT5_SRGB', blockSize: 16 }],
[PIXELFORMAT_PVRTC_2BPP_SRGB_1, { name: 'PVRTC_2BPP_SRGB_1', blockSize: 8 }],
[PIXELFORMAT_PVRTC_2BPP_SRGBA_1, { name: 'PVRTC_2BPP_SRGBA_1', blockSize: 8 }],
[PIXELFORMAT_PVRTC_4BPP_SRGB_1, { name: 'PVRTC_4BPP_SRGB_1', blockSize: 8 }],
[PIXELFORMAT_PVRTC_4BPP_SRGBA_1, { name: 'PVRTC_4BPP_SRGBA_1', blockSize: 8 }],
[PIXELFORMAT_ETC2_SRGB, { name: 'ETC2_SRGB', blockSize: 8 }],
[PIXELFORMAT_ETC2_SRGBA, { name: 'ETC2_SRGBA', blockSize: 16 }],
[PIXELFORMAT_ASTC_4x4_SRGB, { name: 'ASTC_4x4_SRGB', blockSize: 16 }],

// integer formats
[PIXELFORMAT_R8I, { name: 'R8I', size: 1, isInt: true }],
[PIXELFORMAT_R8U, { name: 'R8U', size: 1, isInt: true }],
Expand Down Expand Up @@ -1020,9 +1121,22 @@ export const isIntegerPixelFormat = (format) => {
return pixelFormatInfo.get(format)?.isInt === true;
};

/**
* Returns for the format which can be used to sample the sRGB stored data in linear space, with
* automatic sRGB to linear conversion. If it does not exist, the input format is returned. For
* example for {@link PIXELFORMAT_RGBA8} the return value is {@link PIXELFORMAT_RGBA8}.
*
* @param {number} format - The texture format.
* @returns {number} The format allowing linear sampling of the texture.
* @ignore
*/
export const getPixelFormatLinearSampling = (format) => {
mvaligursky marked this conversation as resolved.
Show resolved Hide resolved
return pixelFormatInfo.get(format)?.linearFormat || format;
};

/**
* @param {number} format - The texture format.
* @returns {boolean} - Whether sampling the texture with this format returns a linear value.
* @returns {boolean} Whether sampling the texture with this format returns a linear value.
* @ignore
*/
export const isLinearFormat = (format) => {
Expand Down
Loading