Skip to content

Commit

Permalink
chore: Consolidate core and WebGL texture format info handling (#2220)
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen authored Sep 2, 2024
1 parent 357ab71 commit 0b21732
Show file tree
Hide file tree
Showing 23 changed files with 859 additions and 1,012 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
node-version: [20]
Expand Down
70 changes: 64 additions & 6 deletions modules/core/src/adapter/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import {StatsManager, lumaStats} from '../utils/stats-manager';
import {log} from '../utils/log';
import {uid} from '../utils/uid';
import type {TextureFormat} from '../gpu-type-utils//texture-formats';
import type {TextureFormat} from '../gpu-type-utils/texture-formats';
import type {TextureFormatCapabilities} from '../gpu-type-utils/texture-format-capabilities';
import type {CanvasContext, CanvasContextProps} from './canvas-context';
import type {BufferProps} from './resources/buffer';
import {Buffer} from './resources/buffer';
Expand All @@ -24,6 +25,7 @@ import type {TransformFeedback, TransformFeedbackProps} from './resources/transf
import type {QuerySet, QuerySetProps} from './resources/query-set';

import {isTextureFormatCompressed} from '../gpu-type-utils/decode-texture-format';
import {getTextureFormatCapabilities} from '../gpu-type-utils/texture-format-capabilities';

/**
* Identifies the GPU vendor and driver.
Expand Down Expand Up @@ -170,7 +172,7 @@ export type WebGLDeviceFeature =
// texture rendering
| 'float32-renderable-webgl'
| 'float16-renderable-webgl'
| 'rgb9e5ufloat_renderable-webgl'
| 'rgb9e5ufloat-renderable-webgl'
| 'snorm8-renderable-webgl'
| 'norm16-renderable-webgl'
| 'snorm16-renderable-webgl'
Expand All @@ -192,6 +194,21 @@ type WebGLCompressedTextureFeatures =
| 'texture-compression-pvrtc-webgl'
| 'texture-compression-atc-webgl';

/** Texture format capabilities that have been checked against a specific device */
export type DeviceTextureFormatCapabilities = {
format: TextureFormat;
/** Can the format be created */
create: boolean;
/** If a feature string, the specified device feature determines if format is renderable. */
render: boolean;
/** If a feature string, the specified device feature determines if format is filterable. */
filter: boolean;
/** If a feature string, the specified device feature determines if format is blendable. */
blend: boolean;
/** If a feature string, the specified device feature determines if format is storeable. */
store: boolean;
};

/** Device properties */
export type DeviceProps = {
/** string id for debugging. Stored on the object, used in logging and set on underlying GPU objects when feasible. */
Expand Down Expand Up @@ -348,14 +365,47 @@ export abstract class Device {
/** WebGPU style device limits */
abstract get limits(): DeviceLimits;

/** Determines what operations are supported on a texture format, checking against supported device features */
getTextureFormatCapabilities(format: TextureFormat): DeviceTextureFormatCapabilities {
const genericCapabilities = getTextureFormatCapabilities(format);

// Check standard features
const checkFeature = (featureOrBoolean: DeviceFeature | boolean | undefined) =>
(typeof featureOrBoolean === 'string'
? this.features.has(featureOrBoolean)
: featureOrBoolean) ?? true;

const supported = checkFeature(genericCapabilities.create);

const deviceCapabilities: DeviceTextureFormatCapabilities = {
format,
create: supported,
render: supported && checkFeature(genericCapabilities.render),
filter: supported && checkFeature(genericCapabilities.filter),
blend: supported && checkFeature(genericCapabilities.blend),
store: supported && checkFeature(genericCapabilities.store)
};

return this._getDeviceSpecificTextureFormatCapabilities(deviceCapabilities);
}

/** Check if device supports a specific texture format (creation and `nearest` sampling) */
abstract isTextureFormatSupported(format: TextureFormat): boolean;
isTextureFormatSupported(
format: TextureFormat,
capabilities: Partial<TextureFormatCapabilities>
): boolean {
return this.getTextureFormatCapabilities(format).create;
}

/** Check if linear filtering (sampler interpolation) is supported for a specific texture format */
abstract isTextureFormatFilterable(format: TextureFormat): boolean;
isTextureFormatFilterable(format: TextureFormat): boolean {
return this.getTextureFormatCapabilities(format).filter;
}

/** Check if device supports rendering to a specific texture format */
abstract isTextureFormatRenderable(format: TextureFormat): boolean;
/** Check if device supports rendering to a framebuffer color attachment of a specific texture format */
isTextureFormatRenderable(format: TextureFormat): boolean {
return this.getTextureFormatCapabilities(format).render;
}

/** Check if a specific texture format is GPU compressed */
isTextureFormatCompressed(format: TextureFormat): boolean {
Expand Down Expand Up @@ -532,6 +582,14 @@ export abstract class Device {

// IMPLEMENTATION

/**
* Determines what operations are supported on a texture format, checking against supported device features
* Subclasses override to apply additional checks
*/
protected abstract _getDeviceSpecificTextureFormatCapabilities(
format: DeviceTextureFormatCapabilities
): DeviceTextureFormatCapabilities;

/** Subclasses use this to support .createBuffer() overloads */
protected _normalizeBufferProps(props: BufferProps | ArrayBuffer | ArrayBufferView): BufferProps {
if (props instanceof ArrayBuffer || ArrayBuffer.isView(props)) {
Expand Down
152 changes: 88 additions & 64 deletions modules/core/src/gpu-type-utils/decode-texture-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,83 +29,107 @@ export function isTextureFormatCompressed(
* Decodes a texture format, returning e.g. attatchment type, components, byte length and flags (integer, signed, normalized)
*/
export function decodeTextureFormat(format: TextureFormat): TextureFormatInfo {
let formatInfo: TextureFormatInfo = decodeTextureFormatUsingTable(format);

if (isTextureFormatCompressed(format)) {
return decodeCompressedTextureFormat(format);
formatInfo.channels = 'rgb';
formatInfo.components = 3;
formatInfo.bytesPerPixel = 1;
formatInfo.srgb = false;
formatInfo.compressed = true;

const blockSize = getCompressedTextureBlockSize(format);
if (blockSize) {
formatInfo.blockWidth = blockSize.blockWidth;
formatInfo.blockHeight = blockSize.blockHeight;
}
}

// Fill in missing information that can be derived from the format string
const matches = RGB_FORMAT_REGEX.exec(format as string);
if (!matches) {
return decodeNonStandardFormat(format);
if (matches) {
const [, channels, length, type, srgb, suffix] = matches;
const dataType = `${type}${length}` as VertexType;
const decodedType = decodeVertexType(dataType);
const bits = decodedType.byteLength * 8;
const components = channels.length as 1 | 2 | 3 | 4;
const bitsPerChannel: [number, number, number, number] = [
bits,
components >= 2 ? bits : 0,
components >= 3 ? bits : 0,
components >= 4 ? bits : 0
];

formatInfo = {
format,
attachment: formatInfo.attachment,
dataType: decodedType.dataType,
components,
channels: channels as 'r' | 'rg' | 'rgb' | 'rgba',
integer: decodedType.integer,
signed: decodedType.signed,
normalized: decodedType.normalized,
bitsPerChannel,
bytesPerPixel: decodedType.byteLength * channels.length,
packed: formatInfo.packed,
srgb: formatInfo.srgb
};

if (suffix === '-webgl') {
formatInfo.webgl = true;
}
// dataType - overwritten by decodedType
if (srgb === '-srgb') {
formatInfo.srgb = true;
}
}

const [, channels, length, type, srgb, suffix] = matches;
const dataType = `${type}${length}` as VertexType;
const decodedType = decodeVertexType(dataType);
const bits = decodedType.byteLength * 8;
const components = channels.length as 1 | 2 | 3 | 4;
const bitsPerChannel: [number, number, number, number] = [
bits,
components >= 2 ? bits : 0,
components >= 3 ? bits : 0,
components >= 4 ? bits : 0
];

const info: TextureFormatInfo = {
format,
channels: channels as 'r' | 'rg' | 'rgb' | 'rgba',
components,
bitsPerChannel,
bytesPerPixel: decodedType.byteLength * channels.length,
dataType: decodedType.dataType,
integer: decodedType.integer,
signed: decodedType.signed,
normalized: decodedType.normalized
};

if (suffix === '-webgl') {
info.webgl = true;
if (format.endsWith('-webgl')) {
formatInfo.webgl = true;
}
// dataType - overwritten by decodedType
if (srgb === '-srgb') {
info.srgb = true;
if (format.endsWith('-srgb')) {
formatInfo.srgb = true;
}
return info;
}

function decodeCompressedTextureFormat(format: CompressedTextureFormat): TextureFormatInfo {
const info: TextureFormatInfo = {
channels: 'rgb',
components: 3,
bytesPerPixel: 1,
srgb: false,
compressed: true
} as TextureFormatInfo;

const blockSize = getCompressedTextureBlockSize(format);
if (blockSize) {
info.blockWidth = blockSize.blockWidth;
info.blockHeight = blockSize.blockHeight;
}
return info;
return formatInfo;
}

function decodeNonStandardFormat(format: TextureFormat): TextureFormatInfo {
const data = TEXTURE_FORMAT_TABLE[format];
if (!data) {
throw new Error(`Unknown format ${format}`);
/** Decode texture format info from the table */
function decodeTextureFormatUsingTable(format: TextureFormat): TextureFormatInfo {
const info = TEXTURE_FORMAT_TABLE[format];
if (!info) {
throw new Error(`Unknown texture format ${format}`);
}

const info: TextureFormatInfo = {
...data,
channels: data.channels || '',
components: data.components || data.channels?.length || 1,
bytesPerPixel: data.bytesPerPixel || 1,
srgb: false
} as TextureFormatInfo;
if (data.packed) {
info.packed = data.packed;
}
return info;
const bytesPerPixel = info.bytesPerPixel || 1;
const bitsPerChannel = info.bitsPerChannel || [8, 8, 8, 8];
delete info.bitsPerChannel;
delete info.bytesPerPixel;
delete info.create;
delete info.render;
delete info.filter;
delete info.blend;
delete info.store;

const formatInfo: TextureFormatInfo = {
...info,
format,
attachment: info.attachment || 'color',
channels: info.channels || 'r',
components: (info.components || info.channels?.length || 1) as 1 | 2 | 3 | 4,
bytesPerPixel,
bitsPerChannel,
dataType: info.dataType || 'uint8',
srgb: info.srgb ?? false,
packed: info.packed ?? false,
webgl: info.webgl ?? false,
integer: info.integer ?? false,
signed: info.signed ?? false,
normalized: info.normalized ?? false,
compressed: info.compressed ?? false
};

return formatInfo;
}

/** Parses ASTC block widths from format string */
Expand Down
2 changes: 1 addition & 1 deletion modules/core/src/gpu-type-utils/texture-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type TextureFeature =
| 'texture-compression-atc-webgl'
| 'float32-renderable-webgl'
| 'float16-renderable-webgl'
| 'rgb9e5ufloat_renderable-webgl'
| 'rgb9e5ufloat-renderable-webgl'
| 'snorm8-renderable-webgl'
| 'norm16-renderable-webgl'
| 'snorm16-renderable-webgl'
Expand Down
56 changes: 56 additions & 0 deletions modules/core/src/gpu-type-utils/texture-format-capabilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import type {TextureFormat} from './texture-formats';
import type {TextureFeature} from './texture-features';
import {decodeTextureFormat} from './decode-texture-format';

import {TEXTURE_FORMAT_TABLE} from './texture-format-table';

/**
* Texture format capabilities.
* @note Not directly usable. Can contain TextureFeature strings that need to be checked against a specific device.
*/
export type TextureFormatCapabilities = {
format: TextureFormat;
/** Can the format be created */
create: TextureFeature | boolean;
/** If a feature string, the specified device feature determines if format is renderable. */
render: TextureFeature | boolean;
/** If a feature string, the specified device feature determines if format is filterable. */
filter: TextureFeature | boolean;
/** If a feature string, the specified device feature determines if format is blendable. */
blend: TextureFeature | boolean;
/** If a feature string, the specified device feature determines if format is storeable. */
store: TextureFeature | boolean;
};

export function getTextureFormatCapabilities(format: TextureFormat): TextureFormatCapabilities {
const info = TEXTURE_FORMAT_TABLE[format];
if (!info) {
throw new Error(`Unknown texture format ${format}`);
}

const formatCapabilities: Required<TextureFormatCapabilities> = {
format,
create: info.create ?? true,
render: info.render ?? true,
filter: info.filter ?? true,
blend: info.blend ?? true,
store: info.store ?? true
};

const formatInfo = decodeTextureFormat(format);
const isDepthStencil = format.startsWith('depth') || format.startsWith('stencil');
const isSigned = formatInfo?.signed;
const isInteger = formatInfo?.integer;
const isWebGLSpecific = formatInfo?.webgl;

// signed formats are not renderable
formatCapabilities.render &&= !isSigned;
// signed and integer formats are not filterable
formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;

return formatCapabilities;
}
10 changes: 0 additions & 10 deletions modules/core/src/gpu-type-utils/texture-format-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import {TextureFormat} from './texture-formats';
import {VertexType} from './vertex-formats';
import {TextureFeature} from './texture-features';

/** Information about the structure of a texture format */
export type TextureFormatInfo = {
Expand Down Expand Up @@ -40,13 +39,4 @@ export type TextureFormatInfo = {
blockWidth?: number;
/** Compressed formats only: Block size for ASTC formats (texture height must be a multiple of this value) */
blockHeight?: number;

/** If a feature, the specified device feature determines if format is renderable. true if not set. */
render?: TextureFeature | boolean;
/** If a feature, the specified device feature determines if format is filterable. true if not set. */
filter?: TextureFeature | boolean;
/** If a feature, the specified device feature determines if format is blendable. true if not set. */
blend?: TextureFeature | boolean;
/** If a feature, the specified device feature determines if format is storeable. true if not set. */
store?: TextureFeature | boolean;
};
Loading

0 comments on commit 0b21732

Please sign in to comment.