From 707fc18b5473b7b08bd82c549c9eed8398acbe9c Mon Sep 17 00:00:00 2001 From: Tom Pohl Date: Wed, 1 Apr 2026 10:40:15 +0200 Subject: [PATCH 1/3] Renderers: Support EXT_texture_norm16 formats. (#33303) Co-authored-by: Michael Herzog --- src/loaders/ObjectLoader.js | 1 + .../webgl-fallback/utils/WebGLTextureUtils.js | 26 ++++++++++- src/renderers/webgl/WebGLTextures.js | 34 +++++++++++--- src/renderers/webgpu/utils/WebGPUConstants.js | 6 +++ .../webgpu/utils/WebGPUTextureUtils.js | 44 +++++++++++++++---- src/textures/Texture.js | 11 +++++ 6 files changed, 106 insertions(+), 16 deletions(-) diff --git a/src/loaders/ObjectLoader.js b/src/loaders/ObjectLoader.js index b29f8e6fa863cc..e88fe18262bd18 100644 --- a/src/loaders/ObjectLoader.js +++ b/src/loaders/ObjectLoader.js @@ -757,6 +757,7 @@ class ObjectLoader extends Loader { if ( data.premultiplyAlpha !== undefined ) texture.premultiplyAlpha = data.premultiplyAlpha; if ( data.unpackAlignment !== undefined ) texture.unpackAlignment = data.unpackAlignment; if ( data.compareFunction !== undefined ) texture.compareFunction = data.compareFunction; + if ( data.normalized !== undefined ) texture.normalized = data.normalized; if ( data.userData !== undefined ) texture.userData = data.userData; diff --git a/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js b/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js index 498114e1560ce7..2515f55498b55b 100644 --- a/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +++ b/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js @@ -162,7 +162,7 @@ class WebGLTextureUtils { * @param {boolean} [forceLinearTransfer=false] - Whether to force a linear transfer or not. * @return {GLenum} The internal format. */ - getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { + getInternalFormat( internalFormatName, glFormat, glType, normalized, colorSpace, forceLinearTransfer = false ) { const { gl, extensions } = this; @@ -174,6 +174,20 @@ class WebGLTextureUtils { } + let ext_texture_norm16; + + if ( normalized ) { + + ext_texture_norm16 = extensions.get( 'EXT_texture_norm16' ); + + if ( ! ext_texture_norm16 ) { + + warn( 'WebGLRenderer: Unable to use normalized textures without EXT_texture_norm16 extension' ); + + } + + } + let internalFormat = glFormat; if ( glFormat === gl.RED ) { @@ -182,6 +196,8 @@ class WebGLTextureUtils { if ( glType === gl.HALF_FLOAT ) internalFormat = gl.R16F; if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8; if ( glType === gl.BYTE ) internalFormat = gl.R8_SNORM; + if ( glType === gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.R16_EXT; + if ( glType === gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.R16_SNORM_EXT; } @@ -202,6 +218,8 @@ class WebGLTextureUtils { if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RG16F; if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RG8; if ( glType === gl.BYTE ) internalFormat = gl.RG8_SNORM; + if ( glType === gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RG16_EXT; + if ( glType === gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RG16_SNORM_EXT; } @@ -224,6 +242,8 @@ class WebGLTextureUtils { if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGB16F; if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? gl.SRGB8 : gl.RGB8; if ( glType === gl.BYTE ) internalFormat = gl.RGB8_SNORM; + if ( glType === gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGB16_EXT; + if ( glType === gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGB16_SNORM_EXT; if ( glType === gl.UNSIGNED_SHORT_5_6_5 ) internalFormat = gl.RGB565; if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1; if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGB4; @@ -251,6 +271,8 @@ class WebGLTextureUtils { if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGBA16F; if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? gl.SRGB8_ALPHA8 : gl.RGBA8; if ( glType === gl.BYTE ) internalFormat = gl.RGBA8_SNORM; + if ( glType === gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGBA16_EXT; + if ( glType === gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGBA16_SNORM_EXT; if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGBA4; if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1; @@ -411,7 +433,7 @@ class WebGLTextureUtils { const glFormat = backend.utils.convert( texture.format, texture.colorSpace ); const glType = backend.utils.convert( texture.type ); - const glInternalFormat = this.getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); + const glInternalFormat = this.getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace, texture.isVideoTexture ); const textureGPU = gl.createTexture(); const glTextureType = this.getGLTextureType( texture ); diff --git a/src/renderers/webgl/WebGLTextures.js b/src/renderers/webgl/WebGLTextures.js index fe7975b0d920f9..2f9f03359e5705 100644 --- a/src/renderers/webgl/WebGLTextures.js +++ b/src/renderers/webgl/WebGLTextures.js @@ -125,7 +125,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - function getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { + function getInternalFormat( internalFormatName, glFormat, glType, normalized, colorSpace, forceLinearTransfer = false ) { if ( internalFormatName !== null ) { @@ -135,6 +135,20 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } + let ext_texture_norm16; + + if ( normalized ) { + + ext_texture_norm16 = extensions.get( 'EXT_texture_norm16' ); + + if ( ! ext_texture_norm16 ) { + + warn( 'WebGLRenderer: Unable to use normalized textures without EXT_texture_norm16 extension' ); + + } + + } + let internalFormat = glFormat; if ( glFormat === _gl.RED ) { @@ -142,6 +156,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( glType === _gl.FLOAT ) internalFormat = _gl.R32F; if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.R16F; if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8; + if ( glType === _gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.R16_EXT; + if ( glType === _gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.R16_SNORM_EXT; } @@ -161,6 +177,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( glType === _gl.FLOAT ) internalFormat = _gl.RG32F; if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RG16F; if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8; + if ( glType === _gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RG16_EXT; + if ( glType === _gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RG16_SNORM_EXT; } @@ -199,6 +217,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( glFormat === _gl.RGB ) { + if ( glType === _gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGB16_EXT; + if ( glType === _gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGB16_SNORM_EXT; if ( glType === _gl.UNSIGNED_INT_5_9_9_9_REV ) internalFormat = _gl.RGB9_E5; if ( glType === _gl.UNSIGNED_INT_10F_11F_11F_REV ) internalFormat = _gl.R11F_G11F_B10F; @@ -211,6 +231,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; + if ( glType === _gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGBA16_EXT; + if ( glType === _gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGBA16_SNORM_EXT; if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; @@ -908,7 +930,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const glFormat = utils.convert( texture.format, texture.colorSpace ); const glType = utils.convert( texture.type ); - let glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); + let glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace, texture.isVideoTexture ); setTextureParameters( textureType, texture ); @@ -1357,7 +1379,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const image = cubeImage[ 0 ], glFormat = utils.convert( texture.format, texture.colorSpace ), glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace ); const useTexStorage = ( texture.isVideoTexture !== true ); const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); @@ -1553,7 +1575,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const glFormat = utils.convert( texture.format, texture.colorSpace ); const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace ); const renderTargetProperties = properties.get( renderTarget ); const textureProperties = properties.get( texture ); @@ -1632,7 +1654,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const glFormat = utils.convert( texture.format, texture.colorSpace ); const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace ); if ( useMultisampledRTT( renderTarget ) ) { @@ -2022,7 +2044,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const glFormat = utils.convert( texture.format, texture.colorSpace ); const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, renderTarget.isXRRenderTarget === true ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace, renderTarget.isXRRenderTarget === true ); const samples = getRenderTargetSamples( renderTarget ); _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); diff --git a/src/renderers/webgpu/utils/WebGPUConstants.js b/src/renderers/webgpu/utils/WebGPUConstants.js index 3915848688978a..19119074fbf921 100644 --- a/src/renderers/webgpu/utils/WebGPUConstants.js +++ b/src/renderers/webgpu/utils/WebGPUConstants.js @@ -96,6 +96,8 @@ export const GPUTextureFormat = { RG8Snorm: 'rg8snorm', RG8Uint: 'rg8uint', RG8Sint: 'rg8sint', + R16Unorm: 'r16unorm', + R16Snorm: 'r16snorm', // 32-bit formats @@ -112,6 +114,8 @@ export const GPUTextureFormat = { RGBA8Sint: 'rgba8sint', BGRA8Unorm: 'bgra8unorm', BGRA8UnormSRGB: 'bgra8unorm-srgb', + RG16Unorm: 'rg16unorm', + RG16Snorm: 'rg16snorm', // Packed 32-bit formats RGB9E5UFloat: 'rgb9e5ufloat', RGB10A2Unorm: 'rgb10a2unorm', @@ -125,6 +129,8 @@ export const GPUTextureFormat = { RGBA16Uint: 'rgba16uint', RGBA16Sint: 'rgba16sint', RGBA16Float: 'rgba16float', + RGBA16Unorm: 'rgba16unorm', + RGBA16Snorm: 'rgba16snorm', // 128-bit formats diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index f12d5cf897b099..e7895dd1244a2b 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -1073,7 +1073,9 @@ class WebGPUTextureUtils { format === GPUTextureFormat.RG8Unorm || format === GPUTextureFormat.RG8Snorm || format === GPUTextureFormat.RG8Uint || - format === GPUTextureFormat.RG8Sint ) return 2; + format === GPUTextureFormat.RG8Sint || + format === GPUTextureFormat.R16Unorm || + format === GPUTextureFormat.R16Snorm ) return 2; // 32-bit formats if ( format === GPUTextureFormat.R32Uint || @@ -1089,6 +1091,8 @@ class WebGPUTextureUtils { format === GPUTextureFormat.RGBA8Sint || format === GPUTextureFormat.BGRA8Unorm || format === GPUTextureFormat.BGRA8UnormSRGB || + format === GPUTextureFormat.RG16Unorm || + format === GPUTextureFormat.RG16Snorm || // Packed 32-bit formats format === GPUTextureFormat.RGB9E5UFloat || format === GPUTextureFormat.RGB10A2Unorm || @@ -1104,7 +1108,9 @@ class WebGPUTextureUtils { format === GPUTextureFormat.RG32Float || format === GPUTextureFormat.RGBA16Uint || format === GPUTextureFormat.RGBA16Sint || - format === GPUTextureFormat.RGBA16Float ) return 8; + format === GPUTextureFormat.RGBA16Float || + format === GPUTextureFormat.RGBA16Unorm || + format === GPUTextureFormat.RGBA16Snorm ) return 8; // 128-bit formats if ( format === GPUTextureFormat.RGBA32Uint || @@ -1147,6 +1153,12 @@ class WebGPUTextureUtils { if ( format === GPUTextureFormat.RG16Float ) return Uint16Array; if ( format === GPUTextureFormat.RGBA16Float ) return Uint16Array; + if ( format === GPUTextureFormat.R16Unorm ) return Uint16Array; + if ( format === GPUTextureFormat.R16Snorm ) return Int16Array; + if ( format === GPUTextureFormat.RG16Unorm ) return Uint16Array; + if ( format === GPUTextureFormat.RG16Snorm ) return Int16Array; + if ( format === GPUTextureFormat.RGBA16Unorm ) return Uint16Array; + if ( format === GPUTextureFormat.RGBA16Snorm ) return Int16Array; if ( format === GPUTextureFormat.R32Uint ) return Uint32Array; if ( format === GPUTextureFormat.R32Sint ) return Int32Array; @@ -1209,11 +1221,26 @@ export function getFormat( texture, device = null ) { const format = texture.format; const type = texture.type; + const normalized = texture.normalized; const colorSpace = texture.colorSpace; const transfer = ColorManagement.getTransfer( colorSpace ); let formatGPU; + let texture_formats_tier1; + + if ( normalized ) { + + texture_formats_tier1 = device && device.features.has( GPUFeatureName.TextureFormatsTier1 ); + + if ( texture_formats_tier1 === false ) { + + warn( 'WebGPURenderer: Unable to use normalized textures without texture-formats-tier1 feature.' ); + + } + + } + if ( texture.isCompressedTexture === true || texture.isCompressedArrayTexture === true ) { switch ( format ) { @@ -1354,12 +1381,13 @@ export function getFormat( texture, device = null ) { break; case ShortType: - formatGPU = GPUTextureFormat.RGBA16Sint; + formatGPU = texture_formats_tier1 ? GPUTextureFormat.RGBA16Snorm : GPUTextureFormat.RGBA16Sint; break; case UnsignedShortType: - formatGPU = GPUTextureFormat.RGBA16Uint; + formatGPU = texture_formats_tier1 ? GPUTextureFormat.RGBA16Unorm : GPUTextureFormat.RGBA16Uint; break; + case UnsignedIntType: formatGPU = GPUTextureFormat.RGBA32Uint; break; @@ -1415,11 +1443,11 @@ export function getFormat( texture, device = null ) { break; case ShortType: - formatGPU = GPUTextureFormat.R16Sint; + formatGPU = texture_formats_tier1 ? GPUTextureFormat.R16Snorm : GPUTextureFormat.R16Sint; break; case UnsignedShortType: - formatGPU = GPUTextureFormat.R16Uint; + formatGPU = texture_formats_tier1 ? GPUTextureFormat.R16Unorm : GPUTextureFormat.R16Uint; break; case UnsignedIntType: @@ -1458,11 +1486,11 @@ export function getFormat( texture, device = null ) { break; case ShortType: - formatGPU = GPUTextureFormat.RG16Sint; + formatGPU = texture_formats_tier1 ? GPUTextureFormat.RG16Snorm : GPUTextureFormat.RG16Sint; break; case UnsignedShortType: - formatGPU = GPUTextureFormat.RG16Uint; + formatGPU = texture_formats_tier1 ? GPUTextureFormat.RG16Unorm : GPUTextureFormat.RG16Uint; break; case UnsignedIntType: diff --git a/src/textures/Texture.js b/src/textures/Texture.js index 42f0f2d0679438..695b1a32184b44 100644 --- a/src/textures/Texture.js +++ b/src/textures/Texture.js @@ -368,6 +368,15 @@ class Texture extends EventDispatcher { */ this.pmremVersion = 0; + /** + * Whether the texture should use one of the 16 bit integer formats which are normalized + * to [0, 1] or [-1, 1] (depending on signed/unsigned) when sampled. + * + * @type {boolean} + * @default false + */ + this.normalized = false; + } /** @@ -483,6 +492,7 @@ class Texture extends EventDispatcher { this.format = source.format; this.internalFormat = source.internalFormat; this.type = source.type; + this.normalized = source.normalized; this.offset.copy( source.offset ); this.repeat.copy( source.repeat ); @@ -601,6 +611,7 @@ class Texture extends EventDispatcher { format: this.format, internalFormat: this.internalFormat, type: this.type, + normalized: this.normalized, colorSpace: this.colorSpace, minFilter: this.minFilter, From 8ebbd0a93ddc7d249c29170be064a72ac75dd542 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 1 Apr 2026 11:16:16 +0200 Subject: [PATCH 2/3] WebGPURenderer: Clean up. (#33305) --- .../webgl-fallback/utils/WebGLTextureUtils.js | 22 ++++++++-------- src/renderers/webgpu/nodes/WGSLNodeBuilder.js | 7 +++--- src/renderers/webgpu/utils/WebGPUConstants.js | 2 ++ .../webgpu/utils/WebGPUTextureUtils.js | 25 +++++++++---------- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js b/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js index 2515f55498b55b..6041181c8faa3d 100644 --- a/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +++ b/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js @@ -174,13 +174,13 @@ class WebGLTextureUtils { } - let ext_texture_norm16; + let extTextureNorm16 = null; if ( normalized ) { - ext_texture_norm16 = extensions.get( 'EXT_texture_norm16' ); + extTextureNorm16 = extensions.get( 'EXT_texture_norm16' ); - if ( ! ext_texture_norm16 ) { + if ( ! extTextureNorm16 ) { warn( 'WebGLRenderer: Unable to use normalized textures without EXT_texture_norm16 extension' ); @@ -196,8 +196,8 @@ class WebGLTextureUtils { if ( glType === gl.HALF_FLOAT ) internalFormat = gl.R16F; if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8; if ( glType === gl.BYTE ) internalFormat = gl.R8_SNORM; - if ( glType === gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.R16_EXT; - if ( glType === gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.R16_SNORM_EXT; + if ( glType === gl.UNSIGNED_SHORT && extTextureNorm16 ) internalFormat = extTextureNorm16.R16_EXT; + if ( glType === gl.SHORT && extTextureNorm16 ) internalFormat = extTextureNorm16.R16_SNORM_EXT; } @@ -218,8 +218,8 @@ class WebGLTextureUtils { if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RG16F; if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RG8; if ( glType === gl.BYTE ) internalFormat = gl.RG8_SNORM; - if ( glType === gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RG16_EXT; - if ( glType === gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RG16_SNORM_EXT; + if ( glType === gl.UNSIGNED_SHORT && extTextureNorm16 ) internalFormat = extTextureNorm16.RG16_EXT; + if ( glType === gl.SHORT && extTextureNorm16 ) internalFormat = extTextureNorm16.RG16_SNORM_EXT; } @@ -242,8 +242,8 @@ class WebGLTextureUtils { if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGB16F; if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? gl.SRGB8 : gl.RGB8; if ( glType === gl.BYTE ) internalFormat = gl.RGB8_SNORM; - if ( glType === gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGB16_EXT; - if ( glType === gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGB16_SNORM_EXT; + if ( glType === gl.UNSIGNED_SHORT && extTextureNorm16 ) internalFormat = extTextureNorm16.RGB16_EXT; + if ( glType === gl.SHORT && extTextureNorm16 ) internalFormat = extTextureNorm16.RGB16_SNORM_EXT; if ( glType === gl.UNSIGNED_SHORT_5_6_5 ) internalFormat = gl.RGB565; if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1; if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGB4; @@ -271,8 +271,8 @@ class WebGLTextureUtils { if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGBA16F; if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? gl.SRGB8_ALPHA8 : gl.RGBA8; if ( glType === gl.BYTE ) internalFormat = gl.RGBA8_SNORM; - if ( glType === gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGBA16_EXT; - if ( glType === gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGBA16_SNORM_EXT; + if ( glType === gl.UNSIGNED_SHORT && extTextureNorm16 ) internalFormat = extTextureNorm16.RGBA16_EXT; + if ( glType === gl.SHORT && extTextureNorm16 ) internalFormat = extTextureNorm16.RGBA16_SNORM_EXT; if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGBA4; if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1; diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js index 9aadc494186446..7a196ffe585b37 100644 --- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -1876,6 +1876,7 @@ ${ flowData.code } */ getUniforms( shaderStage ) { + const backend = this.renderer.backend; const uniforms = this.uniforms[ shaderStage ]; const bindingSnippets = []; @@ -1913,7 +1914,7 @@ ${ flowData.code } let multisampled = ''; - const { primarySamples } = this.renderer.backend.utils.getTextureSampleData( texture ); + const { primarySamples } = backend.utils.getTextureSampleData( texture ); if ( primarySamples > 1 ) { @@ -1931,7 +1932,7 @@ ${ flowData.code } } else if ( texture.isDepthTexture === true ) { - if ( this.renderer.backend.compatibilityMode && texture.compareFunction === null ) { + if ( backend.compatibilityMode && texture.compareFunction === null ) { textureType = `texture${ multisampled }_2d`; @@ -1943,7 +1944,7 @@ ${ flowData.code } } else if ( uniform.node.isStorageTextureNode === true ) { - const format = getFormat( texture ); + const format = getFormat( texture, backend.device ); const access = this.getStorageAccess( uniform.node, shaderStage ); const is3D = uniform.node.value.is3DTexture; diff --git a/src/renderers/webgpu/utils/WebGPUConstants.js b/src/renderers/webgpu/utils/WebGPUConstants.js index 19119074fbf921..91b9934d022611 100644 --- a/src/renderers/webgpu/utils/WebGPUConstants.js +++ b/src/renderers/webgpu/utils/WebGPUConstants.js @@ -116,7 +116,9 @@ export const GPUTextureFormat = { BGRA8UnormSRGB: 'bgra8unorm-srgb', RG16Unorm: 'rg16unorm', RG16Snorm: 'rg16snorm', + // Packed 32-bit formats + RGB9E5UFloat: 'rgb9e5ufloat', RGB10A2Unorm: 'rgb10a2unorm', RG11B10UFloat: 'rg11b10ufloat', diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index e7895dd1244a2b..cc452d9fefc9b3 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -198,7 +198,7 @@ class WebGPUTextureUtils { let textureGPU; - const format = getFormat( texture ); + const format = getFormat( texture, this.backend.device ); if ( texture.isCubeTexture ) { @@ -1213,11 +1213,10 @@ class WebGPUTextureUtils { * Returns the GPU format for the given texture. * * @param {Texture} texture - The texture. - * @param {?GPUDevice} [device=null] - The GPU device which is used for feature detection. - * It is not necessary to apply the device for most formats. + * @param {GPUDevice} [device] - The GPU device which is used for feature detection. * @return {string} The GPU format. */ -export function getFormat( texture, device = null ) { +export function getFormat( texture, device ) { const format = texture.format; const type = texture.type; @@ -1227,13 +1226,13 @@ export function getFormat( texture, device = null ) { let formatGPU; - let texture_formats_tier1; + let textureFormatsTier1 = false; if ( normalized ) { - texture_formats_tier1 = device && device.features.has( GPUFeatureName.TextureFormatsTier1 ); + textureFormatsTier1 = device.features.has( GPUFeatureName.TextureFormatsTier1 ); - if ( texture_formats_tier1 === false ) { + if ( textureFormatsTier1 === false ) { warn( 'WebGPURenderer: Unable to use normalized textures without texture-formats-tier1 feature.' ); @@ -1381,11 +1380,11 @@ export function getFormat( texture, device = null ) { break; case ShortType: - formatGPU = texture_formats_tier1 ? GPUTextureFormat.RGBA16Snorm : GPUTextureFormat.RGBA16Sint; + formatGPU = textureFormatsTier1 ? GPUTextureFormat.RGBA16Snorm : GPUTextureFormat.RGBA16Sint; break; case UnsignedShortType: - formatGPU = texture_formats_tier1 ? GPUTextureFormat.RGBA16Unorm : GPUTextureFormat.RGBA16Uint; + formatGPU = textureFormatsTier1 ? GPUTextureFormat.RGBA16Unorm : GPUTextureFormat.RGBA16Uint; break; case UnsignedIntType: @@ -1443,11 +1442,11 @@ export function getFormat( texture, device = null ) { break; case ShortType: - formatGPU = texture_formats_tier1 ? GPUTextureFormat.R16Snorm : GPUTextureFormat.R16Sint; + formatGPU = textureFormatsTier1 ? GPUTextureFormat.R16Snorm : GPUTextureFormat.R16Sint; break; case UnsignedShortType: - formatGPU = texture_formats_tier1 ? GPUTextureFormat.R16Unorm : GPUTextureFormat.R16Uint; + formatGPU = textureFormatsTier1 ? GPUTextureFormat.R16Unorm : GPUTextureFormat.R16Uint; break; case UnsignedIntType: @@ -1486,11 +1485,11 @@ export function getFormat( texture, device = null ) { break; case ShortType: - formatGPU = texture_formats_tier1 ? GPUTextureFormat.RG16Snorm : GPUTextureFormat.RG16Sint; + formatGPU = textureFormatsTier1 ? GPUTextureFormat.RG16Snorm : GPUTextureFormat.RG16Sint; break; case UnsignedShortType: - formatGPU = texture_formats_tier1 ? GPUTextureFormat.RG16Unorm : GPUTextureFormat.RG16Uint; + formatGPU = textureFormatsTier1 ? GPUTextureFormat.RG16Unorm : GPUTextureFormat.RG16Uint; break; case UnsignedIntType: From 1e144d8784851c9512465084328eee096aac5676 Mon Sep 17 00:00:00 2001 From: Don McCurdy <1848368+donmccurdy@users.noreply.github.com> Date: Wed, 1 Apr 2026 05:32:37 -0400 Subject: [PATCH 3/3] KTX2Loader: Support RGBA 16-bit unsigned normalized formats (#33245) Co-authored-by: Michael Herzog --- examples/jsm/loaders/KTX2Loader.js | 10 ++++++++-- .../textures/ktx2/2d_rgba16unorm_linear.ktx2 | Bin 0 -> 17408 bytes examples/webgl_loader_texture_ktx2.html | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 examples/textures/ktx2/2d_rgba16unorm_linear.ktx2 diff --git a/examples/jsm/loaders/KTX2Loader.js b/examples/jsm/loaders/KTX2Loader.js index b2ad541a296a31..1c6492a9e96044 100644 --- a/examples/jsm/loaders/KTX2Loader.js +++ b/examples/jsm/loaders/KTX2Loader.js @@ -42,7 +42,8 @@ import { SRGBColorSpace, UnsignedByteType, UnsignedInt5999Type, - UnsignedInt101111Type + UnsignedInt101111Type, + UnsignedShortType } from 'three'; import { WorkerPool } from '../utils/WorkerPool.js'; import { @@ -83,6 +84,7 @@ import { VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG, VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG, VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT, @@ -964,6 +966,8 @@ const FORMAT_MAP = { [ VK_FORMAT_R16G16_SFLOAT ]: RGFormat, [ VK_FORMAT_R16_SFLOAT ]: RedFormat, + [ VK_FORMAT_R16G16B16A16_UNORM ]: RGBAFormat, + [ VK_FORMAT_R8G8B8A8_SRGB ]: RGBAFormat, [ VK_FORMAT_R8G8B8A8_UNORM ]: RGBAFormat, [ VK_FORMAT_R8G8_SRGB ]: RGFormat, @@ -1022,6 +1026,8 @@ const TYPE_MAP = { [ VK_FORMAT_R16G16_SFLOAT ]: HalfFloatType, [ VK_FORMAT_R16_SFLOAT ]: HalfFloatType, + [ VK_FORMAT_R16G16B16A16_UNORM ]: UnsignedShortType, + [ VK_FORMAT_R8G8B8A8_SRGB ]: UnsignedByteType, [ VK_FORMAT_R8G8B8A8_UNORM ]: UnsignedByteType, [ VK_FORMAT_R8G8_SRGB ]: UnsignedByteType, @@ -1149,7 +1155,7 @@ async function createRawTexture( container ) { ); - } else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) { + } else if ( TYPE_MAP[ vkFormat ] === HalfFloatType || TYPE_MAP[ vkFormat ] === UnsignedShortType ) { data = new Uint16Array( diff --git a/examples/textures/ktx2/2d_rgba16unorm_linear.ktx2 b/examples/textures/ktx2/2d_rgba16unorm_linear.ktx2 new file mode 100644 index 0000000000000000000000000000000000000000..fc86b4b3cbdeceecdf37b00cef61a14ec50f3a39 GIT binary patch literal 17408 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