Skip to content

Commit

Permalink
chore: Consolidate core and WebGL texture format info handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen committed Aug 25, 2024
1 parent a0d4d3d commit 044875c
Show file tree
Hide file tree
Showing 20 changed files with 872 additions and 970 deletions.
50 changes: 44 additions & 6 deletions modules/core/src/adapter/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
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, TextureFormatSupport} from '../gpu-type-utils/texture-formats';
import type {CanvasContext, CanvasContextProps} from './canvas-context';
import type {BufferProps} from './resources/buffer';
import {Buffer} from './resources/buffer';
Expand All @@ -23,7 +23,10 @@ import type {VertexArray, VertexArrayProps} from './resources/vertex-array';
import type {TransformFeedback, TransformFeedbackProps} from './resources/transform-feedback';
import type {QuerySet, QuerySetProps} from './resources/query-set';

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

/**
* Identifies the GPU vendor and driver.
Expand Down Expand Up @@ -339,14 +342,25 @@ 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 */
getTextureFormatSupport(format: TextureFormat) {
return this._getTextureFormatSupport(format);
}

/** Check if device supports a specific texture format (creation and `nearest` sampling) */
abstract isTextureFormatSupported(format: TextureFormat): boolean;
isTextureFormatSupported(format: TextureFormat): boolean {
return this.getTextureFormatSupport(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.getTextureFormatSupport(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.getTextureFormatSupport(format).render;
}

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

// IMPLEMENTATION

/**
* Determines what operations are supported on a texture format, checking against supported device features
* Subclasses may override to apply addition checks
*/
protected _getTextureFormatSupport(format: TextureFormat): TextureFormatSupport {
const formatInfo = decodeTextureFormat(format);

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

const supported = checkFeature(formatInfo.create);

return {
format,
create: supported,
render: supported && checkFeature(formatInfo.render),
filter: supported && checkFeature(formatInfo.filter),
blend: supported && checkFeature(formatInfo.blend),
store: supported && checkFeature(formatInfo.store)
};
}

/** Subclasses use this to support .createBuffer() overloads */
protected _normalizeBufferProps(props: BufferProps | ArrayBuffer | ArrayBufferView): BufferProps {
if (props instanceof ArrayBuffer || ArrayBuffer.isView(props)) {
Expand Down
143 changes: 79 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,98 @@ 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 = {
...formatInfo,
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') {
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
};
// Overrides

if (suffix === '-webgl') {
info.webgl = true;
}
// dataType - overwritten by decodedType
if (srgb === '-srgb') {
info.srgb = true;
}
return info;
}
const isDepthStencil = format.startsWith('depth') || format.startsWith('stencil');
const isSigned = formatInfo?.signed;

function decodeCompressedTextureFormat(format: CompressedTextureFormat): TextureFormatInfo {
const info: TextureFormatInfo = {
channels: 'rgb',
components: 3,
bytesPerPixel: 1,
srgb: false,
compressed: true
} as TextureFormatInfo;
// signed formats are not renderable
formatInfo.render &&= !isSigned;
// signed formats are not filterable
formatInfo.filter &&= !isDepthStencil && !isSigned;

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): Required<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;
// @ts-expect-error TODO fix
const formatInfo: Required<TextureFormatInfo> = {
...info,
format,
attachment: info.attachment || 'color',
channels: info.channels || 'r',
components: (info.components || info.channels?.length || 1) as 1 | 2 | 3 | 4,
bytesPerPixel: info.bytesPerPixel || 1,
dataType: info.dataType || 'uint8',
srgb: false,
packed: info.packed ?? false,
create: info.create ?? true,
filter: info.filter ?? true,
render: info.render ?? true,
blend: info.blend ?? true,
store: info.store ?? true
};

return formatInfo;
}

/** Parses ASTC block widths from format string */
Expand Down
10 changes: 6 additions & 4 deletions modules/core/src/gpu-type-utils/texture-format-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ export type TextureFormatInfo = {
/** 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. */
/** create */
create?: TextureFeature | boolean;
/** If a feature string, the specified device feature determines if format is renderable. */
render?: TextureFeature | boolean;
/** If a feature, the specified device feature determines if format is filterable. true if not set. */
/** If a feature string, the specified device feature determines if format is filterable. */
filter?: TextureFeature | boolean;
/** If a feature, the specified device feature determines if format is blendable. true if not set. */
/** If a feature string, the specified device feature determines if format is blendable. */
blend?: TextureFeature | boolean;
/** If a feature, the specified device feature determines if format is storeable. true if not set. */
/** If a feature string, the specified device feature determines if format is storeable. */
store?: TextureFeature | boolean;
};
Loading

0 comments on commit 044875c

Please sign in to comment.