// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import type { DeviceFeature, TextureFormat, TextureFormatCapabilities, DeviceTextureFormatCapabilities } from '@luma.gl/core'; import {textureFormatDecoder} from '@luma.gl/core'; import {GL, GLPixelType, GLExtensions, GLTexelDataFormat} from '@luma.gl/webgl/constants'; import {getWebGLExtension} from '../../context/helpers/webgl-extensions'; import {getGLFromVertexType} from './webgl-vertex-formats'; /* eslint-disable camelcase */ // TEXTURE FEATURES // Define local webgl extension strings to optimize minification const X_S3TC = 'WEBGL_compressed_texture_s3tc'; // BC1, BC2, BC3 const X_S3TC_SRGB = 'WEBGL_compressed_texture_s3tc_srgb'; // BC1, BC2, BC3 const X_RGTC = 'EXT_texture_compression_rgtc'; // BC4, BC5 const X_BPTC = 'EXT_texture_compression_bptc'; // BC6, BC7 const X_ETC2 = 'WEBGL_compressed_texture_etc'; // Renamed from 'WEBGL_compressed_texture_es3' const X_ASTC = 'WEBGL_compressed_texture_astc'; const X_ETC1 = 'WEBGL_compressed_texture_etc1'; const X_PVRTC = 'WEBGL_compressed_texture_pvrtc'; const X_ATC = 'WEBGL_compressed_texture_atc'; // Define local webgl extension strings to optimize minification const EXT_texture_norm16 = 'EXT_texture_norm16'; const EXT_render_snorm = 'EXT_render_snorm'; const EXT_color_buffer_float = 'EXT_color_buffer_float'; const SNORM8_COLOR_RENDERABLE: DeviceFeature = 'snorm8-renderable-webgl'; const NORM16_COLOR_RENDERABLE: DeviceFeature = 'norm16-renderable-webgl'; const SNORM16_COLOR_RENDERABLE: DeviceFeature = 'snorm16-renderable-webgl'; const FLOAT16_COLOR_RENDERABLE: DeviceFeature = 'float16-renderable-webgl'; const FLOAT32_COLOR_RENDERABLE: DeviceFeature = 'float32-renderable-webgl'; const RGB9E5UFLOAT_COLOR_RENDERABLE: DeviceFeature = 'rgb9e5ufloat-renderable-webgl'; type TextureFeatureDefinition = { extensions?: string[]; features?: DeviceFeature[]; }; // biome-ignore format: preserve layout export const TEXTURE_FEATURES: Partial> = { 'float32-renderable-webgl': {extensions: [EXT_color_buffer_float]}, 'float16-renderable-webgl': {extensions: ['EXT_color_buffer_half_float']}, 'rgb9e5ufloat-renderable-webgl': {extensions: ['WEBGL_render_shared_exponent']}, 'snorm8-renderable-webgl': {extensions: [EXT_render_snorm]}, 'norm16-webgl': {extensions: [EXT_texture_norm16]}, 'norm16-renderable-webgl': {features: ['norm16-webgl']}, 'snorm16-renderable-webgl': {features: ['norm16-webgl'], extensions: [EXT_render_snorm]}, 'float32-filterable': {extensions: ['OES_texture_float_linear']}, 'float16-filterable-webgl': {extensions: ['OES_texture_half_float_linear']}, 'texture-filterable-anisotropic-webgl': {extensions: ['EXT_texture_filter_anisotropic']}, 'texture-blend-float-webgl': {extensions: ['EXT_float_blend']}, 'texture-compression-bc': {extensions: [X_S3TC, X_S3TC_SRGB, X_RGTC, X_BPTC]}, // 'texture-compression-bc3-srgb-webgl': [X_S3TC_SRGB], // 'texture-compression-bc3-webgl': [X_S3TC], 'texture-compression-bc5-webgl': {extensions: [X_RGTC]}, 'texture-compression-bc7-webgl': {extensions: [X_BPTC]}, 'texture-compression-etc2': {extensions: [X_ETC2]}, 'texture-compression-astc': {extensions: [X_ASTC]}, 'texture-compression-etc1-webgl': {extensions: [X_ETC1]}, 'texture-compression-pvrtc-webgl': {extensions: [X_PVRTC]}, 'texture-compression-atc-webgl': {extensions: [X_ATC]} }; export function isTextureFeature(feature: DeviceFeature): boolean { return feature in TEXTURE_FEATURES; } /** Checks a texture feature (for Device.features). Mainly compressed texture support */ export function checkTextureFeature( gl: WebGL2RenderingContext, feature: DeviceFeature, extensions: GLExtensions ): boolean { return hasTextureFeature(gl, feature, extensions, new Set()); } function hasTextureFeature( gl: WebGL2RenderingContext, feature: DeviceFeature, extensions: GLExtensions, seenFeatures: Set ): boolean { const definition = TEXTURE_FEATURES[feature]; if (!definition) { return false; } if (seenFeatures.has(feature)) { return false; } seenFeatures.add(feature); const hasDependentFeatures = (definition.features || []).every(dependentFeature => hasTextureFeature(gl, dependentFeature, extensions, seenFeatures) ); seenFeatures.delete(feature); if (!hasDependentFeatures) { return false; } return (definition.extensions || []).every(extension => Boolean(getWebGLExtension(gl, extension, extensions)) ); } // TEXTURE FORMATS /** Map a format to webgl and constants */ type WebGLFormatInfo = { gl?: GL; /** compressed */ x?: string; /** color-renderable capability gate. false means never color-renderable on WebGL. */ r?: DeviceFeature | false; types?: GLPixelType[]; dataFormat?: GLTexelDataFormat; /** if depthTexture is set this is a depth/stencil format that can be set to a texture */ depthTexture?: boolean; /** @deprecated can this format be used with renderbuffers */ rb?: boolean; }; // TABLES /** * Texture format data - * Exported but can change without notice */ // biome-ignore format: preserve layout export const WEBGL_TEXTURE_FORMATS: Record = { // 8-bit formats 'r8unorm': {gl: GL.R8, rb: true}, 'r8snorm': {gl: GL.R8_SNORM, r: SNORM8_COLOR_RENDERABLE}, 'r8uint': {gl: GL.R8UI, rb: true}, 'r8sint': {gl: GL.R8I, rb: true}, // 16-bit formats 'rg8unorm': {gl: GL.RG8, rb: true}, 'rg8snorm': {gl: GL.RG8_SNORM, r: SNORM8_COLOR_RENDERABLE}, 'rg8uint': {gl: GL.RG8UI, rb: true}, 'rg8sint': {gl: GL.RG8I, rb: true}, 'r16uint': {gl: GL.R16UI, rb: true}, 'r16sint': {gl: GL.R16I, rb: true}, 'r16float': {gl: GL.R16F, rb: true, r: FLOAT16_COLOR_RENDERABLE}, 'r16unorm': {gl: GL.R16_EXT, rb: true, r: NORM16_COLOR_RENDERABLE}, 'r16snorm': {gl: GL.R16_SNORM_EXT, r: SNORM16_COLOR_RENDERABLE}, // Packed 16-bit formats 'rgba4unorm-webgl': {gl: GL.RGBA4, rb: true}, 'rgb565unorm-webgl': {gl: GL.RGB565, rb: true}, 'rgb5a1unorm-webgl': {gl: GL.RGB5_A1, rb: true}, // 24-bit formats 'rgb8unorm-webgl': {gl: GL.RGB8}, 'rgb8snorm-webgl': {gl: GL.RGB8_SNORM}, // 32-bit formats 'rgba8unorm': {gl: GL.RGBA8}, 'rgba8unorm-srgb': {gl: GL.SRGB8_ALPHA8}, 'rgba8snorm': {gl: GL.RGBA8_SNORM, r: SNORM8_COLOR_RENDERABLE}, 'rgba8uint': {gl: GL.RGBA8UI}, 'rgba8sint': {gl: GL.RGBA8I}, // reverse colors, webgpu only 'bgra8unorm': {}, 'bgra8unorm-srgb': {}, 'rg16uint': {gl: GL.RG16UI}, 'rg16sint': {gl: GL.RG16I}, 'rg16float': {gl: GL.RG16F, rb: true, r: FLOAT16_COLOR_RENDERABLE}, 'rg16unorm': {gl: GL.RG16_EXT, r: NORM16_COLOR_RENDERABLE}, 'rg16snorm': {gl: GL.RG16_SNORM_EXT, r: SNORM16_COLOR_RENDERABLE}, 'r32uint': {gl: GL.R32UI, rb: true}, 'r32sint': {gl: GL.R32I, rb: true}, 'r32float': {gl: GL.R32F, r: FLOAT32_COLOR_RENDERABLE}, // Packed 32-bit formats 'rgb9e5ufloat': {gl: GL.RGB9_E5, r: RGB9E5UFLOAT_COLOR_RENDERABLE}, // , filter: true}, 'rg11b10ufloat': {gl: GL.R11F_G11F_B10F, rb: true}, 'rgb10a2unorm': {gl: GL.RGB10_A2, rb: true}, 'rgb10a2uint': {gl: GL.RGB10_A2UI, rb: true}, // 48-bit formats 'rgb16unorm-webgl': {gl: GL.RGB16_EXT, r: false}, // rgb not renderable 'rgb16snorm-webgl': {gl: GL.RGB16_SNORM_EXT, r: false}, // rgb not renderable // 64-bit formats 'rg32uint': {gl: GL.RG32UI, rb: true}, 'rg32sint': {gl: GL.RG32I, rb: true}, 'rg32float': {gl: GL.RG32F, rb: true, r: FLOAT32_COLOR_RENDERABLE}, 'rgba16uint': {gl: GL.RGBA16UI, rb: true}, 'rgba16sint': {gl: GL.RGBA16I, rb: true}, 'rgba16float': {gl: GL.RGBA16F, r: FLOAT16_COLOR_RENDERABLE}, 'rgba16unorm': {gl: GL.RGBA16_EXT, rb: true, r: NORM16_COLOR_RENDERABLE}, 'rgba16snorm': {gl: GL.RGBA16_SNORM_EXT, r: SNORM16_COLOR_RENDERABLE}, // 96-bit formats (deprecated!) 'rgb32float-webgl': {gl: GL.RGB32F, x: EXT_color_buffer_float, r: FLOAT32_COLOR_RENDERABLE, dataFormat: GL.RGB, types: [GL.FLOAT]}, // 128-bit formats 'rgba32uint': {gl: GL.RGBA32UI, rb: true}, 'rgba32sint': {gl: GL.RGBA32I, rb: true}, 'rgba32float': {gl: GL.RGBA32F, rb: true, r: FLOAT32_COLOR_RENDERABLE}, // Depth and stencil formats 'stencil8': {gl: GL.STENCIL_INDEX8, rb: true}, // 8 stencil bits 'depth16unorm': {gl: GL.DEPTH_COMPONENT16, dataFormat: GL.DEPTH_COMPONENT, types: [GL.UNSIGNED_SHORT], rb: true}, // 16 depth bits 'depth24plus': {gl: GL.DEPTH_COMPONENT24, dataFormat: GL.DEPTH_COMPONENT, types: [GL.UNSIGNED_INT]}, 'depth32float': {gl: GL.DEPTH_COMPONENT32F, dataFormat: GL.DEPTH_COMPONENT, types: [GL.FLOAT], rb: true}, // The depth component of the "depth24plus" and "depth24plus-stencil8" formats may be implemented as either a 24-bit depth value or a "depth32float" value. 'depth24plus-stencil8': {gl: GL.DEPTH24_STENCIL8, rb: true, depthTexture: true, dataFormat: GL.DEPTH_STENCIL, types: [GL.UNSIGNED_INT_24_8]}, // "depth32float-stencil8" feature - TODO below is render buffer only? 'depth32float-stencil8': {gl: GL.DEPTH32F_STENCIL8, dataFormat: GL.DEPTH_STENCIL, types: [GL.FLOAT_32_UNSIGNED_INT_24_8_REV], rb: true}, // BC compressed formats: check device.features.has("texture-compression-bc"); 'bc1-rgb-unorm-webgl': {gl: GL.COMPRESSED_RGB_S3TC_DXT1_EXT, x: X_S3TC}, 'bc1-rgb-unorm-srgb-webgl': {gl: GL.COMPRESSED_SRGB_S3TC_DXT1_EXT, x: X_S3TC_SRGB}, 'bc1-rgba-unorm': {gl: GL.COMPRESSED_RGBA_S3TC_DXT1_EXT, x: X_S3TC}, 'bc1-rgba-unorm-srgb': {gl: GL.COMPRESSED_SRGB_S3TC_DXT1_EXT, x: X_S3TC_SRGB}, 'bc2-rgba-unorm': {gl: GL.COMPRESSED_RGBA_S3TC_DXT3_EXT, x: X_S3TC}, 'bc2-rgba-unorm-srgb': {gl: GL.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, x: X_S3TC_SRGB}, 'bc3-rgba-unorm': {gl: GL.COMPRESSED_RGBA_S3TC_DXT5_EXT, x: X_S3TC}, 'bc3-rgba-unorm-srgb': {gl: GL.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, x: X_S3TC_SRGB}, 'bc4-r-unorm': {gl: GL.COMPRESSED_RED_RGTC1_EXT, x: X_RGTC}, 'bc4-r-snorm': {gl: GL.COMPRESSED_SIGNED_RED_RGTC1_EXT, x: X_RGTC}, 'bc5-rg-unorm': {gl: GL.COMPRESSED_RED_GREEN_RGTC2_EXT, x: X_RGTC}, 'bc5-rg-snorm': {gl: GL.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT, x: X_RGTC}, 'bc6h-rgb-ufloat': {gl: GL.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT, x: X_BPTC}, 'bc6h-rgb-float': {gl: GL.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT, x: X_BPTC}, 'bc7-rgba-unorm': {gl: GL.COMPRESSED_RGBA_BPTC_UNORM_EXT, x: X_BPTC}, 'bc7-rgba-unorm-srgb': {gl: GL.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT, x: X_BPTC}, // WEBGL_compressed_texture_etc: device.features.has("texture-compression-etc2") // Note: Supposedly guaranteed availability compressed formats in WebGL2, but through CPU decompression 'etc2-rgb8unorm': {gl: GL.COMPRESSED_RGB8_ETC2}, 'etc2-rgb8unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ETC2}, 'etc2-rgb8a1unorm': {gl: GL.COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2}, 'etc2-rgb8a1unorm-srgb': {gl: GL.COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2}, 'etc2-rgba8unorm': {gl: GL.COMPRESSED_RGBA8_ETC2_EAC}, 'etc2-rgba8unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC}, 'eac-r11unorm': {gl: GL.COMPRESSED_R11_EAC}, 'eac-r11snorm': {gl: GL.COMPRESSED_SIGNED_R11_EAC}, 'eac-rg11unorm': {gl: GL.COMPRESSED_RG11_EAC}, 'eac-rg11snorm': {gl: GL.COMPRESSED_SIGNED_RG11_EAC}, // X_ASTC compressed formats: device.features.has("texture-compression-astc") 'astc-4x4-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_4x4_KHR}, 'astc-4x4-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}, 'astc-5x4-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_5x4_KHR}, 'astc-5x4-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}, 'astc-5x5-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_5x5_KHR}, 'astc-5x5-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}, 'astc-6x5-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_6x5_KHR}, 'astc-6x5-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}, 'astc-6x6-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_6x6_KHR}, 'astc-6x6-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}, 'astc-8x5-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_8x5_KHR}, 'astc-8x5-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}, 'astc-8x6-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_8x6_KHR}, 'astc-8x6-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}, 'astc-8x8-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_8x8_KHR}, 'astc-8x8-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}, 'astc-10x5-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_10x5_KHR}, 'astc-10x5-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR}, 'astc-10x6-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_10x6_KHR}, 'astc-10x6-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR}, 'astc-10x8-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_10x8_KHR}, 'astc-10x8-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}, 'astc-10x10-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_10x10_KHR}, 'astc-10x10-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}, 'astc-12x10-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_12x10_KHR}, 'astc-12x10-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR}, 'astc-12x12-unorm': {gl: GL.COMPRESSED_RGBA_ASTC_12x12_KHR}, 'astc-12x12-unorm-srgb': {gl: GL.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}, // WEBGL_compressed_texture_pvrtc 'pvrtc-rgb4unorm-webgl': {gl: GL.COMPRESSED_RGB_PVRTC_4BPPV1_IMG}, 'pvrtc-rgba4unorm-webgl': {gl: GL.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG}, 'pvrtc-rgb2unorm-webgl': {gl: GL.COMPRESSED_RGB_PVRTC_2BPPV1_IMG}, 'pvrtc-rgba2unorm-webgl': {gl: GL.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG}, // WEBGL_compressed_texture_etc1 'etc1-rbg-unorm-webgl': {gl: GL.COMPRESSED_RGB_ETC1_WEBGL}, // WEBGL_compressed_texture_atc 'atc-rgb-unorm-webgl': {gl: GL.COMPRESSED_RGB_ATC_WEBGL}, 'atc-rgba-unorm-webgl': {gl: GL.COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL}, 'atc-rgbai-unorm-webgl': {gl: GL.COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL} }; // FUNCTIONS /** Checks if a texture format is supported, renderable, filterable etc */ export function getTextureFormatCapabilitiesWebGL( gl: WebGL2RenderingContext, formatSupport: TextureFormatCapabilities, extensions: GLExtensions ): DeviceTextureFormatCapabilities { let supported = formatSupport.create; const webglFormatInfo = WEBGL_TEXTURE_FORMATS[formatSupport.format]; // Support Check that we have a GL constant if (webglFormatInfo?.gl === undefined) { supported = false; } if (webglFormatInfo?.x) { supported = supported && Boolean(getWebGLExtension(gl, webglFormatInfo.x, extensions)); } // WebGL2 exposes STENCIL_INDEX8 for renderbuffers, but standalone stencil textures are not // valid texture storage targets. Report them as unsupported texture formats to avoid invalid // constructor paths and misleading capability checks. if (formatSupport.format === 'stencil8') { supported = false; } const renderFeatureSupported = webglFormatInfo?.r === false ? false : webglFormatInfo?.r === undefined || checkTextureFeature(gl, webglFormatInfo.r, extensions); const renderable = supported && formatSupport.render && renderFeatureSupported && isColorRenderableTextureFormat(gl, formatSupport.format, extensions); return { format: formatSupport.format, // @ts-ignore create: supported && formatSupport.create, // @ts-ignore render: renderable, // @ts-ignore filter: supported && formatSupport.filter, // @ts-ignore blend: supported && formatSupport.blend, // @ts-ignore store: supported && formatSupport.store }; } function isColorRenderableTextureFormat( gl: WebGL2RenderingContext, format: TextureFormat, extensions: GLExtensions ): boolean { const webglFormatInfo = WEBGL_TEXTURE_FORMATS[format]; const internalFormat = webglFormatInfo?.gl; if (internalFormat === undefined) { return false; } if (webglFormatInfo?.x && !getWebGLExtension(gl, webglFormatInfo.x, extensions)) { return false; } const previousTexture = gl.getParameter(GL.TEXTURE_BINDING_2D) as WebGLTexture | null; const previousFramebuffer = gl.getParameter(GL.FRAMEBUFFER_BINDING) as WebGLFramebuffer | null; const texture = gl.createTexture(); const framebuffer = gl.createFramebuffer(); if (!texture || !framebuffer) { return false; } // Isolate the probe from any prior driver errors so the result reflects only this format. const noError = Number(GL.NO_ERROR); let error = Number(gl.getError()); while (error !== noError) { error = gl.getError(); } let renderable = false; try { gl.bindTexture(GL.TEXTURE_2D, texture); gl.texStorage2D(GL.TEXTURE_2D, 1, internalFormat, 1, 1); if (Number(gl.getError()) !== noError) { return false; } gl.bindFramebuffer(GL.FRAMEBUFFER, framebuffer); gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_2D, texture, 0); renderable = Number(gl.checkFramebufferStatus(GL.FRAMEBUFFER)) === Number(GL.FRAMEBUFFER_COMPLETE) && Number(gl.getError()) === noError; } finally { gl.bindFramebuffer(GL.FRAMEBUFFER, previousFramebuffer); gl.deleteFramebuffer(framebuffer); gl.bindTexture(GL.TEXTURE_2D, previousTexture); gl.deleteTexture(texture); } return renderable; } /** Get parameters necessary to work with format in WebGL: internalFormat, dataFormat, type, compressed, */ export function getTextureFormatWebGL(format: TextureFormat): { internalFormat: GL; format: GLTexelDataFormat; type: GLPixelType; compressed: boolean; } { const formatData = WEBGL_TEXTURE_FORMATS[format]; const webglFormat = convertTextureFormatToGL(format); const decoded = textureFormatDecoder.getInfo(format); if (decoded.compressed) { // TODO: Unclear whether this is always valid, this may be why ETC2 RGBA8 fails. formatData.dataFormat = webglFormat as GLTexelDataFormat; } return { internalFormat: webglFormat, format: formatData?.dataFormat || getWebGLPixelDataFormat(decoded.channels, decoded.integer, decoded.normalized, webglFormat), // depth formats don't have a type type: decoded.dataType ? getGLFromVertexType(decoded.dataType) : formatData?.types?.[0] || GL.UNSIGNED_BYTE, compressed: decoded.compressed || false }; } export function getDepthStencilAttachmentWebGL( format: TextureFormat ): GL.DEPTH_ATTACHMENT | GL.STENCIL_ATTACHMENT | GL.DEPTH_STENCIL_ATTACHMENT { const formatInfo = textureFormatDecoder.getInfo(format); switch (formatInfo.attachment) { case 'depth': return GL.DEPTH_ATTACHMENT; case 'stencil': return GL.STENCIL_ATTACHMENT; case 'depth-stencil': return GL.DEPTH_STENCIL_ATTACHMENT; default: throw new Error(`Not a depth stencil format: ${format}`); } } /** TODO - VERY roundabout legacy way of calculating bytes per pixel */ export function getTextureFormatBytesPerPixel(format: TextureFormat): number { const formatInfo = textureFormatDecoder.getInfo(format); return formatInfo.bytesPerPixel; } // DATA TYPE HELPERS export function getWebGLPixelDataFormat( channels: 'r' | 'rg' | 'rgb' | 'rgba' | 'bgra', integer: boolean, normalized: boolean, format: GL ): GLTexelDataFormat { // WebGL1 formats use same internalFormat if (format === GL.RGBA || format === GL.RGB) { return format; } // biome-ignore format: preserve layout switch (channels) { case 'r': return integer && !normalized ? GL.RED_INTEGER : GL.RED; case 'rg': return integer && !normalized ? GL.RG_INTEGER : GL.RG; case 'rgb': return integer && !normalized ? GL.RGB_INTEGER : GL.RGB; case 'rgba': return integer && !normalized ? GL.RGBA_INTEGER : GL.RGBA; case 'bgra': throw new Error('bgra pixels not supported by WebGL'); default: return GL.RGBA; } } /** * Map WebGPU style texture format strings to GL constants */ function convertTextureFormatToGL(format: TextureFormat): GL { const formatInfo = WEBGL_TEXTURE_FORMATS[format]; const webglFormat = formatInfo?.gl; if (webglFormat === undefined) { throw new Error(`Unsupported texture format ${format}`); } return webglFormat; }