UNPKG

three

Version:

JavaScript 3D library

1,580 lines (1,106 loc) 54.9 kB
import { GPUTextureFormat, GPUAddressMode, GPUFilterMode, GPUTextureDimension, GPUFeatureName, GPUTextureViewDimension } from './WebGPUConstants.js'; import { ColorManagement } from '../../../math/ColorManagement.js'; import WebGPUTexturePassUtils from './WebGPUTexturePassUtils.js'; import { submit } from './WebGPUUtils.js'; import GPUBufferDescriptor from '../descriptors/GPUBufferDescriptor.js'; import GPUCommandEncoderDescriptor from '../descriptors/GPUCommandEncoderDescriptor.js'; import GPUSamplerDescriptor from '../descriptors/GPUSamplerDescriptor.js'; import GPUTexelCopyTextureInfo from '../descriptors/GPUTexelCopyTextureInfo.js'; import GPUTexelCopyBufferInfo from '../descriptors/GPUTexelCopyBufferInfo.js'; import GPUTexelCopyBufferLayout from '../descriptors/GPUTexelCopyBufferLayout.js'; import GPUCopyExternalImageSourceInfo from '../descriptors/GPUCopyExternalImageSourceInfo.js'; import GPUCopyExternalImageDestInfo from '../descriptors/GPUCopyExternalImageDestInfo.js'; import GPUTextureDescriptor from '../descriptors/GPUTextureDescriptor.js'; import GPUExtent3D from '../descriptors/GPUExtent3D.js'; const _bufferDescriptor = new GPUBufferDescriptor(); const _commandEncoderDescriptor = new GPUCommandEncoderDescriptor(); const _samplerDescriptor = new GPUSamplerDescriptor(); const _texelCopyTextureInfo = new GPUTexelCopyTextureInfo(); const _texelCopyBufferInfo = new GPUTexelCopyBufferInfo(); const _texelCopyBufferLayout = new GPUTexelCopyBufferLayout(); const _copyExternalImageSourceInfo = new GPUCopyExternalImageSourceInfo(); const _copyExternalImageDestInfo = new GPUCopyExternalImageDestInfo(); const _textureDescriptor = new GPUTextureDescriptor(); const _extent3D = new GPUExtent3D(); import { ByteType, ShortType, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapLinearFilter, RepeatWrapping, MirroredRepeatWrapping, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, RGBAFormat, RGBFormat, RedFormat, RGFormat, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, UnsignedByteType, FloatType, HalfFloatType, SRGBTransfer, DepthFormat, DepthStencilFormat, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, UnsignedIntType, UnsignedShortType, UnsignedInt248Type, UnsignedInt5999Type, NeverCompare, AlwaysCompare, LessCompare, LessEqualCompare, EqualCompare, GreaterEqualCompare, GreaterCompare, NotEqualCompare, IntType, RedIntegerFormat, RGIntegerFormat, RGBAIntegerFormat, UnsignedInt101111Type, RGBA_BPTC_Format, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RGB_ETC1_Format, RGB_S3TC_DXT1_Format, RED_RGTC1_Format, SIGNED_RED_RGTC1_Format, RED_GREEN_RGTC2_Format, SIGNED_RED_GREEN_RGTC2_Format, R11_EAC_Format, SIGNED_R11_EAC_Format, RG11_EAC_Format, SIGNED_RG11_EAC_Format, Compatibility } from '../../../constants.js'; import { CubeTexture } from '../../../textures/CubeTexture.js'; import { Texture } from '../../../textures/Texture.js'; import { warn, error } from '../../../utils.js'; const _compareToWebGPU = { [ NeverCompare ]: 'never', [ LessCompare ]: 'less', [ EqualCompare ]: 'equal', [ LessEqualCompare ]: 'less-equal', [ GreaterCompare ]: 'greater', [ GreaterEqualCompare ]: 'greater-equal', [ AlwaysCompare ]: 'always', [ NotEqualCompare ]: 'not-equal' }; const _flipMap = [ 0, 1, 3, 2, 4, 5 ]; function writeTextureLayer( device, textureGPU, mipLevel, layerIndex, mipmap, bytesPerImage, bytesPerRow, rowsPerImage, textureWidth, textureHeight ) { _texelCopyTextureInfo.texture = textureGPU; _texelCopyTextureInfo.mipLevel = mipLevel; _texelCopyTextureInfo.origin.z = layerIndex; _texelCopyBufferLayout.offset = layerIndex * bytesPerImage; _texelCopyBufferLayout.bytesPerRow = bytesPerRow; _texelCopyBufferLayout.rowsPerImage = rowsPerImage; _extent3D.width = textureWidth; _extent3D.height = textureHeight; device.queue.writeTexture( _texelCopyTextureInfo, mipmap.data, _texelCopyBufferLayout, _extent3D ); _texelCopyTextureInfo.reset(); _texelCopyBufferLayout.reset(); _extent3D.reset(); } /** * A WebGPU backend utility module for managing textures. * * @private */ class WebGPUTextureUtils { /** * Constructs a new utility object. * * @param {WebGPUBackend} backend - The WebGPU backend. */ constructor( backend ) { /** * A reference to the WebGPU backend. * * @type {WebGPUBackend} */ this.backend = backend; /** * A reference to the pass utils. * * @type {?WebGPUTexturePassUtils} * @default null */ this._passUtils = null; /** * A dictionary for managing default textures. The key * is the texture format, the value the texture object. * * @type {Object<string,Texture>} */ this.defaultTexture = {}; /** * A dictionary for managing default cube textures. The key * is the texture format, the value the texture object. * * @type {Object<string,CubeTexture>} */ this.defaultCubeTexture = {}; /** * A default video frame. * * @type {?VideoFrame} * @default null */ this.defaultVideoFrame = null; /** * A cache of shared texture samplers. * * @type {Map<string, Object>} */ this._samplerCache = new Map(); } /** * Creates a GPU sampler for the given texture. * * @param {Sampler} binding - The sampler binding to update. * @return {string} The current sampler key. */ updateSampler( binding ) { const backend = this.backend; const texture = binding.texture; const textureNode = binding.textureNode; const samplerKey = texture.minFilter + '-' + texture.magFilter + '-' + texture.wrapS + '-' + texture.wrapT + '-' + ( texture.wrapR || '0' ) + '-' + texture.anisotropy + '-' + ( texture.isDepthTexture === true ? 1 : 0 ) + '-' + ( texture.compareFunction !== null && textureNode.compareNode !== null ? texture.compareFunction : 0 ); let samplerData = this._samplerCache.get( samplerKey ); if ( samplerData === undefined ) { _samplerDescriptor.addressModeU = this._convertAddressMode( texture.wrapS ); _samplerDescriptor.addressModeV = this._convertAddressMode( texture.wrapT ); _samplerDescriptor.addressModeW = this._convertAddressMode( texture.wrapR ); _samplerDescriptor.magFilter = this._convertFilterMode( texture.magFilter ); _samplerDescriptor.minFilter = this._convertFilterMode( texture.minFilter ); _samplerDescriptor.mipmapFilter = this._convertMipmapFilterMode( texture.minFilter ); // Depth textures without compare function must use non-filtering (nearest) sampling if ( texture.isDepthTexture && ( texture.compareFunction === null || textureNode.compareNode === null ) ) { _samplerDescriptor.magFilter = GPUFilterMode.Nearest; _samplerDescriptor.minFilter = GPUFilterMode.Nearest; _samplerDescriptor.mipmapFilter = GPUFilterMode.Nearest; } // anisotropy can only be used when all filter modes are set to linear. if ( _samplerDescriptor.magFilter === GPUFilterMode.Linear && _samplerDescriptor.minFilter === GPUFilterMode.Linear && _samplerDescriptor.mipmapFilter === GPUFilterMode.Linear ) { _samplerDescriptor.maxAnisotropy = texture.anisotropy; } if ( texture.isDepthTexture && texture.compareFunction !== null && textureNode.compareNode !== null && backend.hasCompatibility( Compatibility.TEXTURE_COMPARE ) ) { _samplerDescriptor.compare = _compareToWebGPU[ texture.compareFunction ]; } const sampler = backend.device.createSampler( _samplerDescriptor ); _samplerDescriptor.reset(); samplerData = { sampler, usedTimes: 0 }; this._samplerCache.set( samplerKey, samplerData ); } const bindingData = backend.get( binding ); if ( bindingData.sampler !== samplerData.sampler ) { // release the previous sampler (if any) so it can be deleted when unused this._releaseSampler( bindingData ); // update to new sampler data bindingData.samplerKey = samplerKey; bindingData.sampler = samplerData.sampler; samplerData.usedTimes ++; } return samplerKey; } /** * Frees the GPU sampler referenced by the given sampler binding. * * @param {Sampler} binding - The sampler binding to free. */ destroySampler( binding ) { this._releaseSampler( this.backend.get( binding ) ); } /** * Releases the pooled sampler referenced by the given binding data and * removes it from the cache when no binding references it anymore. * * @private * @param {Object} bindingData - The binding data holding the sampler reference. */ _releaseSampler( bindingData ) { if ( bindingData.sampler !== undefined ) { const samplerData = this._samplerCache.get( bindingData.samplerKey ); samplerData.usedTimes --; if ( samplerData.usedTimes === 0 ) { this._samplerCache.delete( bindingData.samplerKey ); } bindingData.sampler = undefined; bindingData.samplerKey = undefined; } } /** * Creates a default texture for the given texture that can be used * as a placeholder until the actual texture is ready for usage. * * @param {Texture} texture - The texture to create a default texture for. */ createDefaultTexture( texture ) { let textureGPU; const format = getFormat( texture, this.backend.device ); if ( texture.isCubeTexture ) { textureGPU = this._getDefaultCubeTextureGPU( format ); } else { textureGPU = this._getDefaultTextureGPU( format ); } this.backend.get( texture ).texture = textureGPU; } /** * Defines a texture on the GPU for the given texture object. * * @param {Texture} texture - The texture. * @param {Object} [options={}] - Optional configuration parameter. */ createTexture( texture, options = {} ) { const backend = this.backend; const textureData = backend.get( texture ); if ( textureData.initialized ) { // Skip creation for external XR textures - they are already set up if ( textureData.externalTexture === true ) { return; } throw new Error( 'THREE.WebGPUTextureUtils: Texture already initialized.' ); } if ( texture.isExternalTexture ) { textureData.texture = texture.sourceTexture; textureData.initialized = true; return; } if ( options.needsMipmaps === undefined ) options.needsMipmaps = false; if ( options.levels === undefined ) options.levels = 1; if ( options.depth === undefined ) options.depth = 1; const { width, height, depth, levels } = options; if ( texture.isFramebufferTexture ) { if ( options.renderTarget ) { options.format = this.backend.utils.getCurrentColorFormat( options.renderTarget ); } else { options.format = this.backend.utils.getPreferredCanvasFormat(); } } const dimension = this._getDimension( texture ); const format = texture.internalFormat || options.format || getFormat( texture, backend.device ); textureData.format = format; const { samples, primarySamples, isMSAA } = backend.utils.getTextureSampleData( texture ); let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC; if ( texture.isStorageTexture === true ) { usage |= GPUTextureUsage.STORAGE_BINDING; } if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true && format !== GPUTextureFormat.RGB9E5UFloat ) { usage |= GPUTextureUsage.RENDER_ATTACHMENT; } const textureDescriptorGPU = new GPUTextureDescriptor(); textureDescriptorGPU.label = texture.name; textureDescriptorGPU.size.width = width; textureDescriptorGPU.size.height = height; textureDescriptorGPU.size.depthOrArrayLayers = depth; textureDescriptorGPU.mipLevelCount = levels; textureDescriptorGPU.sampleCount = primarySamples; textureDescriptorGPU.dimension = dimension; textureDescriptorGPU.format = format; textureDescriptorGPU.usage = usage; // texture creation if ( format === undefined ) { warn( 'WebGPURenderer: Texture format not supported.' ); this.createDefaultTexture( texture ); return; } if ( texture.isCubeTexture ) { textureDescriptorGPU.textureBindingViewDimension = GPUTextureViewDimension.Cube; } try { textureData.texture = backend.device.createTexture( textureDescriptorGPU ); } catch ( e ) { warn( 'WebGPURenderer: Failed to create texture with descriptor:', textureDescriptorGPU ); this.createDefaultTexture( texture ); return; } if ( isMSAA ) { const msaaTextureDescriptorGPU = Object.assign( {}, textureDescriptorGPU ); msaaTextureDescriptorGPU.label = msaaTextureDescriptorGPU.label + '-msaa'; msaaTextureDescriptorGPU.sampleCount = samples; msaaTextureDescriptorGPU.mipLevelCount = 1; // See https://www.w3.org/TR/webgpu/#texture-creation textureData.msaaTexture = backend.device.createTexture( msaaTextureDescriptorGPU ); } textureData.initialized = true; textureData.textureDescriptorGPU = textureDescriptorGPU; } /** * Destroys the GPU data for the given texture object. * * @param {Texture} texture - The texture. * @param {boolean} [isDefaultTexture=false] - Whether the texture uses a default GPU texture or not. */ destroyTexture( texture, isDefaultTexture = false ) { const backend = this.backend; const textureData = backend.get( texture ); if ( textureData.texture !== undefined && isDefaultTexture === false && texture.isExternalTexture !== true ) textureData.texture.destroy(); if ( textureData.msaaTexture !== undefined ) textureData.msaaTexture.destroy(); backend.delete( texture ); } /** * Generates mipmaps for the given texture. * * @param {Texture} texture - The texture. * @param {?GPUCommandEncoder} [encoder=null] - An optional command encoder used to generate mipmaps. */ generateMipmaps( texture, encoder = null ) { const textureData = this.backend.get( texture ); this._generateMipmaps( textureData.texture, encoder ); } /** * Returns the color buffer representing the color * attachment of the default framebuffer. * * @return {GPUTexture} The color buffer. */ getColorBuffer() { const backend = this.backend; const canvasTarget = backend.renderer.getCanvasTarget(); const { width, height } = backend.getDrawingBufferSize(); const samples = backend.renderer.currentSamples; const colorTexture = canvasTarget.colorTexture; const colorTextureData = backend.get( colorTexture ); if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) { return colorTextureData.texture; } // recreate let colorBuffer = colorTextureData.texture; if ( colorBuffer ) colorBuffer.destroy(); _textureDescriptor.label = 'colorBuffer'; _textureDescriptor.size.width = width; _textureDescriptor.size.height = height; _textureDescriptor.sampleCount = backend.utils.getSampleCount( backend.renderer.currentSamples ); _textureDescriptor.format = backend.utils.getPreferredCanvasFormat(); _textureDescriptor.usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC; colorBuffer = backend.device.createTexture( _textureDescriptor ); _textureDescriptor.reset(); // colorTexture.source.width = width; colorTexture.source.height = height; colorTexture.samples = samples; colorTextureData.texture = colorBuffer; return colorBuffer; } /** * Returns the depth buffer representing the depth * attachment of the default framebuffer. * * @param {boolean} [depth=true] - Whether depth is enabled or not. * @param {boolean} [stencil=false] - Whether stencil is enabled or not. * @return {GPUTexture} The depth buffer. */ getDepthBuffer( depth = true, stencil = false ) { const backend = this.backend; const canvasTarget = backend.renderer.getCanvasTarget(); const { width, height } = backend.getDrawingBufferSize(); const samples = backend.renderer.currentSamples; const depthTexture = canvasTarget.depthTexture; if ( depthTexture.width === width && depthTexture.height === height && depthTexture.samples === samples && depthTexture.depth === depth && depthTexture.stencil === stencil ) { return backend.get( depthTexture ).texture; } // const depthTextureGPU = backend.get( depthTexture ).texture; let format, type; if ( stencil ) { format = DepthStencilFormat; type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedInt248Type; } else if ( depth ) { format = DepthFormat; type = backend.renderer.reversedDepthBuffer === true ? FloatType : UnsignedIntType; } if ( depthTextureGPU !== undefined ) { if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type && depthTexture.samples === samples ) { return depthTextureGPU; } this.destroyTexture( depthTexture ); } // recreate depthTexture.name = 'depthBuffer'; depthTexture.format = format; depthTexture.type = type; depthTexture.image.width = width; depthTexture.image.height = height; depthTexture.samples = samples; this.createTexture( depthTexture, { width, height } ); return backend.get( depthTexture ).texture; } /** * Uploads the updated texture data to the GPU. * * @param {Texture} texture - The texture. * @param {Object} [options={}] - Optional configuration parameter. */ updateTexture( texture, options ) { const textureData = this.backend.get( texture ); const mipmaps = texture.mipmaps; const { textureDescriptorGPU } = textureData; if ( texture.isRenderTargetTexture || ( textureDescriptorGPU === undefined /* unsupported texture format */ ) ) return; // transfer texture data if ( texture.isDataTexture ) { if ( mipmaps.length > 0 ) { for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { const mipmap = mipmaps[ i ]; this._copyBufferToTexture( mipmap, textureData.texture, textureDescriptorGPU, 0, texture.flipY, 0, i ); } } else { this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY ); } } else if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isData3DTexture ) { if ( texture.layerUpdates && texture.layerUpdates.size > 0 ) { for ( const layerIndex of texture.layerUpdates ) { this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, layerIndex, texture.flipY, layerIndex ); } texture.clearLayerUpdates(); } else { for ( let i = 0; i < options.image.depth; i ++ ) { this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, i, texture.flipY, i ); } } } else if ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) { if ( texture.isCompressedArrayTexture && texture.layerUpdates.size > 0 ) { this._copyCompressedBufferToTexture( texture.mipmaps, textureData.texture, textureDescriptorGPU, texture.layerUpdates ); texture.clearLayerUpdates(); } else { this._copyCompressedBufferToTexture( texture.mipmaps, textureData.texture, textureDescriptorGPU ); } } else if ( texture.isCubeTexture ) { this._copyCubeMapToTexture( texture, textureData.texture, textureDescriptorGPU ); } else if ( texture.isHTMLTexture ) { const device = this.backend.device; const canvas = this.backend.renderer.domElement; const image = texture.image; if ( typeof device.queue.copyElementImageToTexture !== 'function' ) return; // Skip the first frame — the element needs a paint record first. if ( ! textureData.hasPaintCallback ) { textureData.hasPaintCallback = true; canvas.requestPaint(); return; } const width = textureDescriptorGPU.size.width; const height = textureDescriptorGPU.size.height; if ( device.queue.copyElementImageToTexture.length === 2 ) { // Chrome 150+ device.queue.copyElementImageToTexture( { source: image }, { destination: { texture: textureData.texture }, width: width, height: height } ); } else { // Chrome 138 - 149 device.queue.copyElementImageToTexture( image, width, height, { texture: textureData.texture } ); } if ( texture.flipY ) { this._flipY( textureData.texture, textureDescriptorGPU ); } } else { if ( mipmaps.length > 0 ) { for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { const mipmap = mipmaps[ i ]; this._copyImageToTexture( mipmap, textureData.texture, textureDescriptorGPU, 0, texture.flipY, texture.premultiplyAlpha, i ); } } else { this._copyImageToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY, texture.premultiplyAlpha ); } } // textureData.version = texture.version; } /** * Returns texture data as a typed array. * * @async * @param {Texture} texture - The texture to copy. * @param {number} x - The x coordinate of the copy origin. * @param {number} y - The y coordinate of the copy origin. * @param {number} width - The width of the copy. * @param {number} height - The height of the copy. * @param {number} faceIndex - The face index. * @return {Promise<TypedArray>} A Promise that resolves with a typed array when the copy operation has finished. */ async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { const device = this.backend.device; const textureData = this.backend.get( texture ); const textureGPU = textureData.texture; const format = textureData.textureDescriptorGPU.format; const bytesPerTexel = this._getBytesPerTexel( format ); let bytesPerRow = width * bytesPerTexel; bytesPerRow = Math.ceil( bytesPerRow / 256 ) * 256; // Align to 256 bytes _bufferDescriptor.size = ( ( height - 1 ) * bytesPerRow ) + ( width * bytesPerTexel ); // see https://github.com/mrdoob/three.js/issues/31658#issuecomment-3229442010 _bufferDescriptor.usage = GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ; const readBuffer = device.createBuffer( _bufferDescriptor ); _bufferDescriptor.reset(); const encoder = device.createCommandEncoder( _commandEncoderDescriptor ); _texelCopyTextureInfo.texture = textureGPU; _texelCopyTextureInfo.origin.x = x; _texelCopyTextureInfo.origin.y = y; _texelCopyTextureInfo.origin.z = faceIndex; _texelCopyBufferInfo.buffer = readBuffer; _texelCopyBufferInfo.bytesPerRow = bytesPerRow; _extent3D.width = width; _extent3D.height = height; encoder.copyTextureToBuffer( _texelCopyTextureInfo, _texelCopyBufferInfo, _extent3D ); _texelCopyTextureInfo.reset(); _texelCopyBufferInfo.reset(); _extent3D.reset(); const typedArrayType = this._getTypedArrayType( format ); submit( device, encoder.finish() ); await readBuffer.mapAsync( GPUMapMode.READ ); const buffer = readBuffer.getMappedRange().slice(); readBuffer.destroy(); return new typedArrayType( buffer ); } /** * Frees all internal resources. */ dispose() { this._samplerCache.clear(); } /** * Returns the default GPU texture for the given format. * * @private * @param {string} format - The GPU format. * @return {GPUTexture} The GPU texture. */ _getDefaultTextureGPU( format ) { let defaultTexture = this.defaultTexture[ format ]; if ( defaultTexture === undefined ) { const texture = new Texture(); texture.minFilter = NearestFilter; texture.magFilter = NearestFilter; this.createTexture( texture, { width: 1, height: 1, format } ); this.defaultTexture[ format ] = defaultTexture = texture; } return this.backend.get( defaultTexture ).texture; } /** * Returns the default GPU cube texture for the given format. * * @private * @param {string} format - The GPU format. * @return {GPUTexture} The GPU texture. */ _getDefaultCubeTextureGPU( format ) { let defaultCubeTexture = this.defaultCubeTexture[ format ]; if ( defaultCubeTexture === undefined ) { const texture = new CubeTexture(); texture.minFilter = NearestFilter; texture.magFilter = NearestFilter; this.createTexture( texture, { width: 1, height: 1, depth: 6 } ); this.defaultCubeTexture[ format ] = defaultCubeTexture = texture; } return this.backend.get( defaultCubeTexture ).texture; } /** * Uploads cube texture image data to the GPU memory. * * @private * @param {CubeTexture} texture - The cube texture. * @param {GPUTexture} textureGPU - The GPU texture. * @param {Object} textureDescriptorGPU - The GPU texture descriptor. */ _copyCubeMapToTexture( texture, textureGPU, textureDescriptorGPU ) { const images = texture.images; const mipmaps = texture.mipmaps; for ( let i = 0; i < 6; i ++ ) { const image = images[ i ]; const flipIndex = texture.flipY === true ? _flipMap[ i ] : i; if ( image.isDataTexture ) { this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY ); } else { this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY, texture.premultiplyAlpha ); } for ( let j = 0; j < mipmaps.length; j ++ ) { const mipmap = mipmaps[ j ]; const image = mipmap.images[ i ]; if ( image.isDataTexture ) { this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY, 0, j + 1 ); } else { this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY, texture.premultiplyAlpha, j + 1 ); } } } } /** * Uploads texture image data to the GPU memory. * * @private * @param {HTMLImageElement|ImageBitmap|HTMLCanvasElement} image - The image data. * @param {GPUTexture} textureGPU - The GPU texture. * @param {Object} textureDescriptorGPU - The GPU texture descriptor. * @param {number} originDepth - The origin depth. * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not. * @param {boolean} premultiplyAlpha - Whether the texture should have its RGB channels premultiplied by the alpha channel or not. * @param {number} [mipLevel=0] - The mip level where the data should be copied to. */ _copyImageToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY, premultiplyAlpha, mipLevel = 0 ) { const device = this.backend.device; const width = ( mipLevel > 0 ) ? image.width : textureDescriptorGPU.size.width; const height = ( mipLevel > 0 ) ? image.height : textureDescriptorGPU.size.height; _copyExternalImageSourceInfo.source = image; _copyExternalImageSourceInfo.flipY = flipY; _copyExternalImageDestInfo.texture = textureGPU; _copyExternalImageDestInfo.mipLevel = mipLevel; _copyExternalImageDestInfo.origin.z = originDepth; _copyExternalImageDestInfo.premultipliedAlpha = premultiplyAlpha; _extent3D.width = width; _extent3D.height = height; try { device.queue.copyExternalImageToTexture( _copyExternalImageSourceInfo, _copyExternalImageDestInfo, _extent3D ); // try/catch has been added to fix bad video frame data on certain devices, see #32391 } catch ( _ ) { } finally { _copyExternalImageSourceInfo.reset(); _copyExternalImageDestInfo.reset(); _extent3D.reset(); } } /** * Returns the pass utils singleton. * * @private * @return {WebGPUTexturePassUtils} The utils instance. */ _getPassUtils() { let passUtils = this._passUtils; if ( passUtils === null ) { this._passUtils = passUtils = new WebGPUTexturePassUtils( this.backend.device ); } return passUtils; } /** * Generates mipmaps for the given GPU texture. * * @private * @param {GPUTexture} textureGPU - The GPU texture object. * @param {?GPUCommandEncoder} [encoder=null] - An optional command encoder used to generate mipmaps. */ _generateMipmaps( textureGPU, encoder = null ) { this._getPassUtils().generateMipmaps( textureGPU, encoder ); } /** * Flip the contents of the given GPU texture along its vertical axis. * * @private * @param {GPUTexture} textureGPU - The GPU texture object. * @param {Object} textureDescriptorGPU - The texture descriptor. * @param {number} [originDepth=0] - The origin depth. */ _flipY( textureGPU, textureDescriptorGPU, originDepth = 0 ) { this._getPassUtils().flipY( textureGPU, textureDescriptorGPU, originDepth ); } /** * Uploads texture buffer data to the GPU memory. * * @private * @param {Object} image - An object defining the image buffer data. * @param {GPUTexture} textureGPU - The GPU texture. * @param {Object} textureDescriptorGPU - The GPU texture descriptor. * @param {number} originDepth - The origin depth. * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not. * @param {number} [depth=0] - The depth offset when copying array or 3D texture data. * @param {number} [mipLevel=0] - The mip level where the data should be copied to. */ _copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY, depth = 0, mipLevel = 0 ) { // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() // @TODO: Consider to support valid buffer layouts with other formats like RGB const device = this.backend.device; const data = image.data; const bytesPerTexel = this._getBytesPerTexel( textureDescriptorGPU.format ); const bytesPerRow = image.width * bytesPerTexel; _texelCopyTextureInfo.texture = textureGPU; _texelCopyTextureInfo.mipLevel = mipLevel; _texelCopyTextureInfo.origin.z = originDepth; _texelCopyBufferLayout.offset = image.width * image.height * bytesPerTexel * depth; _texelCopyBufferLayout.bytesPerRow = bytesPerRow; _extent3D.width = image.width; _extent3D.height = image.height; device.queue.writeTexture( _texelCopyTextureInfo, data, _texelCopyBufferLayout, _extent3D ); _texelCopyTextureInfo.reset(); _texelCopyBufferLayout.reset(); _extent3D.reset(); if ( flipY === true ) { this._flipY( textureGPU, textureDescriptorGPU, originDepth ); } } /** * Uploads compressed texture data to the GPU memory. * * @private * @param {Array<Object>} mipmaps - An array with mipmap data. * @param {GPUTexture} textureGPU - The GPU texture. * @param {Object} textureDescriptorGPU - The GPU texture descriptor. * @param {?Set<number>} [layerUpdates=null] - The layer indices to update. */ _copyCompressedBufferToTexture( mipmaps, textureGPU, textureDescriptorGPU, layerUpdates = null ) { // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() const device = this.backend.device; const blockData = this._getBlockData( textureDescriptorGPU.format ); const isArrayTexture = textureDescriptorGPU.size.depthOrArrayLayers > 1; const activeLayerUpdates = layerUpdates && layerUpdates.size > 0 ? layerUpdates : null; for ( let i = 0; i < mipmaps.length; i ++ ) { const mipmap = mipmaps[ i ]; const width = mipmap.width; const height = mipmap.height; const depth = isArrayTexture ? textureDescriptorGPU.size.depthOrArrayLayers : 1; const bytesPerRow = Math.ceil( width / blockData.width ) * blockData.byteLength; const rowsPerImage = Math.ceil( height / blockData.height ); const bytesPerImage = bytesPerRow * rowsPerImage; const textureWidth = Math.ceil( width / blockData.width ) * blockData.width; const textureHeight = rowsPerImage * blockData.height; if ( activeLayerUpdates !== null ) { for ( const layerIndex of activeLayerUpdates ) { writeTextureLayer( device, textureGPU, i, layerIndex, mipmap, bytesPerImage, bytesPerRow, rowsPerImage, textureWidth, textureHeight ); } } else { for ( let layerIndex = 0; layerIndex < depth; layerIndex ++ ) { writeTextureLayer( device, textureGPU, i, layerIndex, mipmap, bytesPerImage, bytesPerRow, rowsPerImage, textureWidth, textureHeight ); } } } } /** * This method is only relevant for compressed texture formats. It returns a block * data descriptor for the given GPU compressed texture format. * * @private * @param {string} format - The GPU compressed texture format. * @return {Object} The block data descriptor. */ _getBlockData( format ) { if ( format === GPUTextureFormat.BC1RGBAUnorm || format === GPUTextureFormat.BC1RGBAUnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; // DXT1 if ( format === GPUTextureFormat.BC2RGBAUnorm || format === GPUTextureFormat.BC2RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT3 if ( format === GPUTextureFormat.BC3RGBAUnorm || format === GPUTextureFormat.BC3RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT5 if ( format === GPUTextureFormat.BC4RUnorm || format === GPUTextureFormat.BC4RSnorm ) return { byteLength: 8, width: 4, height: 4 }; // RGTC1 if ( format === GPUTextureFormat.BC5RGUnorm || format === GPUTextureFormat.BC5RGSnorm ) return { byteLength: 16, width: 4, height: 4 }; // RGTC2 if ( format === GPUTextureFormat.BC6HRGBUFloat || format === GPUTextureFormat.BC6HRGBFloat ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (float) if ( format === GPUTextureFormat.BC7RGBAUnorm || format === GPUTextureFormat.BC7RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (unorm) if ( format === GPUTextureFormat.ETC2RGB8Unorm || format === GPUTextureFormat.ETC2RGB8UnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; if ( format === GPUTextureFormat.ETC2RGB8A1Unorm || format === GPUTextureFormat.ETC2RGB8A1UnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; if ( format === GPUTextureFormat.ETC2RGBA8Unorm || format === GPUTextureFormat.ETC2RGBA8UnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; if ( format === GPUTextureFormat.EACR11Unorm ) return { byteLength: 8, width: 4, height: 4 }; if ( format === GPUTextureFormat.EACR11Snorm ) return { byteLength: 8, width: 4, height: 4 }; if ( format === GPUTextureFormat.EACRG11Unorm ) return { byteLength: 16, width: 4, height: 4 }; if ( format === GPUTextureFormat.EACRG11Snorm ) return { byteLength: 16, width: 4, height: 4 }; if ( format === GPUTextureFormat.ASTC4x4Unorm || format === GPUTextureFormat.ASTC4x4UnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; if ( format === GPUTextureFormat.ASTC5x4Unorm || format === GPUTextureFormat.ASTC5x4UnormSRGB ) return { byteLength: 16, width: 5, height: 4 }; if ( format === GPUTextureFormat.ASTC5x5Unorm || format === GPUTextureFormat.ASTC5x5UnormSRGB ) return { byteLength: 16, width: 5, height: 5 }; if ( format === GPUTextureFormat.ASTC6x5Unorm || format === GPUTextureFormat.ASTC6x5UnormSRGB ) return { byteLength: 16, width: 6, height: 5 }; if ( format === GPUTextureFormat.ASTC6x6Unorm || format === GPUTextureFormat.ASTC6x6UnormSRGB ) return { byteLength: 16, width: 6, height: 6 }; if ( format === GPUTextureFormat.ASTC8x5Unorm || format === GPUTextureFormat.ASTC8x5UnormSRGB ) return { byteLength: 16, width: 8, height: 5 }; if ( format === GPUTextureFormat.ASTC8x6Unorm || format === GPUTextureFormat.ASTC8x6UnormSRGB ) return { byteLength: 16, width: 8, height: 6 }; if ( format === GPUTextureFormat.ASTC8x8Unorm || format === GPUTextureFormat.ASTC8x8UnormSRGB ) return { byteLength: 16, width: 8, height: 8 }; if ( format === GPUTextureFormat.ASTC10x5Unorm || format === GPUTextureFormat.ASTC10x5UnormSRGB ) return { byteLength: 16, width: 10, height: 5 }; if ( format === GPUTextureFormat.ASTC10x6Unorm || format === GPUTextureFormat.ASTC10x6UnormSRGB ) return { byteLength: 16, width: 10, height: 6 }; if ( format === GPUTextureFormat.ASTC10x8Unorm || format === GPUTextureFormat.ASTC10x8UnormSRGB ) return { byteLength: 16, width: 10, height: 8 }; if ( format === GPUTextureFormat.ASTC10x10Unorm || format === GPUTextureFormat.ASTC10x10UnormSRGB ) return { byteLength: 16, width: 10, height: 10 }; if ( format === GPUTextureFormat.ASTC12x10Unorm || format === GPUTextureFormat.ASTC12x10UnormSRGB ) return { byteLength: 16, width: 12, height: 10 }; if ( format === GPUTextureFormat.ASTC12x12Unorm || format === GPUTextureFormat.ASTC12x12UnormSRGB ) return { byteLength: 16, width: 12, height: 12 }; } /** * Converts the three.js uv wrapping constants to GPU address mode constants. * * @private * @param {number} value - The three.js constant defining a uv wrapping mode. * @return {string} The GPU address mode. */ _convertAddressMode( value ) { let addressMode = GPUAddressMode.ClampToEdge; if ( value === RepeatWrapping ) { addressMode = GPUAddressMode.Repeat; } else if ( value === MirroredRepeatWrapping ) { addressMode = GPUAddressMode.MirrorRepeat; } return addressMode; } /** * Converts the three.js filter constants to GPU filter constants. * * @private * @param {number} value - The three.js constant defining a filter mode. * @return {string} The GPU filter mode. */ _convertFilterMode( value ) { let filterMode = GPUFilterMode.Linear; if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) { filterMode = GPUFilterMode.Nearest; } return filterMode; } /** * Converts the three.js filter constants to a GPU mipmap filter constant. * Unlike `_convertFilterMode`, this extracts the between-mip-level filtering * axis from the combined three.js constant rather than the within-level axis. * * @private * @param {number} value - The three.js constant defining a filter mode. * @return {string} The GPU mipmap filter mode. */ _convertMipmapFilterMode( value ) { if ( value === NearestMipmapLinearFilter || value === LinearMipmapLinearFilter ) { return GPUFilterMode.Linear; } return GPUFilterMode.Nearest; } /** * Returns the bytes-per-texel value for the given GPU texture format. * * @private * @param {string} format - The GPU texture format. * @return {number} The bytes-per-texel. */ _getBytesPerTexel( format ) { // 8-bit formats if ( format === GPUTextureFormat.R8Unorm || format === GPUTextureFormat.R8Snorm || format === GPUTextureFormat.R8Uint || format === GPUTextureFormat.R8Sint ) return 1; // 16-bit formats if ( format === GPUTextureFormat.R16Uint || format === GPUTextureFormat.R16Sint || format === GPUTextureFormat.R16Float || format === GPUTextureFormat.RG8Unorm || format === GPUTextureFormat.RG8Snorm || format === GPUTextureFormat.RG8Uint || format === GPUTextureFormat.RG8Sint || format === GPUTextureFormat.R16Unorm || format === GPUTextureFormat.R16Snorm ) return 2; // 32-bit formats if ( format === GPUTextureFormat.R32Uint || format === GPUTextureFormat.R32Sint || format === GPUTextureFormat.R32Float || format === GPUTextureFormat.RG16Uint || format === GPUTextureFormat.RG16Sint || format === GPUTextureFormat.RG16Float || format === GPUTextureFormat.RGBA8Unorm || format === GPUTextureFormat.RGBA8UnormSRGB || format === GPUTextureFormat.RGBA8Snorm || format === GPUTextureFormat.RGBA8Uint || 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 || format === GPUTextureFormat.RG11B10UFloat || format === GPUTextureFormat.Depth32Float || format === GPUTextureFormat.Depth24Plus || format === GPUTextureFormat.Depth24PlusStencil8 || format === GPUTextureFormat.Depth32FloatStencil8 ) return 4; // 64-bit formats if ( format === GPUTextureFormat.RG32Uint || format === GPUTextureFormat.RG32Sint || format === GPUTextureFormat.RG32Float || format === GPUTextureFormat.RGBA16Uint || format === GPUTextureFormat.RGBA16Sint || format === GPUTextureFormat.RGBA16Float || format === GPUTextureFormat.RGBA16Unorm || format === GPUTextureFormat.RGBA16Snorm ) return 8; // 128-bit formats if ( format === GPUTextureFormat.RGBA32Uint || format === GPUTextureFormat.RGBA32Sint || format === GPUTextureFormat.RGBA32Float ) return 16; } /** * Returns the corresponding typed array type for the given GPU texture format. * * @private * @param {string} format - The GPU texture format. * @return {TypedArray.constructor} The typed array type. */ _getTypedArrayType( format ) { if ( format === GPUTextureFormat.R8Uint ) return Uint8Array; if ( format === GPUTextureFormat.R8Sint ) return Int8Array; if ( format === GPUTextureFormat.R8Unorm ) return Uint8Array; if ( format === GPUTextureFormat.R8Snorm ) return Int8Array; if ( format === GPUTextureFormat.RG8Uint ) return Uint8Array; if ( format === GPUTextureFormat.RG8Sint ) return Int8Array; if ( format === GPUTextureFormat.RG8Unorm ) return Uint8Array; if ( format === GPUTextureFormat.RG8Snorm ) return Int8Array; if ( format === GPUTextureFormat.RGBA8Uint ) return Uint8Array; if ( format === GPUTextureFormat.RGBA8Sint ) return Int8Array; if ( format === GPUTextureFormat.RGBA8Unorm || format === GPUTextureFormat.RGBA8UnormSRGB ) return Uint8Array; if ( format === GPUTextureFormat.RGBA8Snorm ) return Int8Array; if ( format === GPUTextureFormat.R16Uint ) return Uint16Array; if ( format === GPUTextureFormat.R16Sint ) return Int16Array; if ( format === GPUTextureFormat.RG16Uint ) return Uint16Array; if ( format === GPUTextureFormat.RG16Sint ) return Int16Array; if ( format === GPUTextureFormat.RGBA16Uint ) return Uint16Array; if ( format === GPUTextureFormat.RGBA16Sint ) return Int16Array; if ( format === GPUTextureFormat.R16Float ) return Uint16Array; 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; if ( format === GPUTextureFormat.R32Float ) return Float32Array; if ( format === GPUTextureFormat.RG32Uint ) return Uint32Array; if ( format === GPUTextureFormat.RG32Sint ) return Int32Array; if ( format === GPUTextureFormat.RG32Float ) return Float32Array; if ( format === GPUTextureFormat.RGBA32Uint ) return Uint32Array; if ( format === GPUTextureFormat.RGBA32Sint ) return Int32Array; if ( format === GPUTextureFormat.RGBA32Float ) return Float32Array; if ( format === GPUTextureFormat.BGRA8Unorm || format === GPUTextureFormat.BGRA8UnormSRGB ) return Uint8Array; if ( format === GPUTextureFormat.RGB10A2Unorm ) return Uint32Array; if ( format === GPUTextureFormat.RGB9E5UFloat ) return Uint32Array; if ( format === GPUTextureFormat.RG11B10UFloat ) return Uint32Array; if ( format === GPUTextureFormat.Depth32Float ) return Float32Array; if ( format === GPUTextureFormat.Depth24Plus ) return Uint32Array; if ( format === GPUTextureFormat.Depth24PlusStencil8 ) return Uint32Array; if ( format === GPUTextureFormat.Depth32FloatStencil8 ) return Float32Array; } /** * Returns the GPU dimensions for the given texture. * * @private * @param {Texture} texture - The texture. * @return {string} The GPU dimension. */ _getDimension( texture ) { let dimension; if ( texture.is3DTexture || texture.isData3DTexture ) { dimension = GPUTextureDimension.ThreeD; } else { dimension = GPUTextureDimension.TwoD; } return dimension; } } /** * Returns the GPU format for the given texture. * * @param {Texture} texture - The texture. * @param {GPUDevice} [device] - The GPU device which is used for feature detection. * @return {string} The GPU format. */ export function getFormat( texture, device ) { const format = texture.format; const type = texture.type; const normalized = texture.normalized; const colorSpace = texture.colorSpace; const transfer = ColorManagement.getTransfer( colorSpace ); let formatGPU; let textureFormatsTier1 = false; if ( normalized ) { textureFormatsTier1 = device.features.has( GPUFeatureName.TextureFormatsTier1 ); if ( textureFormatsTier1 === false ) { warn( 'WebGPURenderer: Unable to use normalized textures without texture-formats-tier1 feature.' ); } } if ( texture.isCompressedTexture === true || texture.isCompressedArrayTexture === true ) { switch ( format ) { case RGB_S3TC_DXT1_Format: case RGBA_S3TC_DXT1_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC1RGBAUnormSRGB : GPUTextureFormat.BC1RGBAUnorm; break; case RGBA_S3TC_DXT3_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC2RGBAUnormSRGB : GPUTextureFormat.BC2RGBAUnorm; break; case RGBA_S3TC_DXT5_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC3RGBAUnormSRGB : GPUTextureFormat.BC3RGBAUnorm; break; case RED_RGTC1_Format: formatGPU = GPUTextureFormat.BC4RUnorm; break; case SIGNED_RED_RGTC1_Format: formatGPU = GPUTextureFormat.BC4RSnorm; break; case RED_GREEN_RGTC2_Format: formatGPU = GPUTextureFormat.BC5RGUnorm; break; case SIGNED_RED_GREEN_RGTC2_Format: formatGPU = GPUTextureFormat.BC5RGSnorm; break; case RGBA_BPTC_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC7RGBAUnormSRGB : GPUTextureFormat.BC7RGBAUnorm; break; case RGB_BPTC_SIGNED_Format: formatGPU = GPUTextureFormat.BC6HRGBFloat; break; case RGB_BPTC_UNSIGNED_Format: formatGPU = GPUTextureFormat.BC6HRGBUFloat; break; case RGB_ETC2_Format: case RGB_ETC1_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ETC2RGB8UnormSRGB : GPUTextureFormat.ETC2RGB8Unorm; break; case RGBA_ETC2_EAC_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ETC2RGBA8UnormSRGB : GPUTextureFormat.ETC2RGBA8Unorm; break; case R11_EAC_Format: formatGPU = GPUTextureFormat.EACR11Unorm; break; case SIGNED_R11_EAC_Format: formatGPU = GPUTextureFormat.EACR11Snorm; break; case RG11_EAC_Format: formatGPU = GPUTextureFormat.EACRG11Unorm; break; case SIGNED_RG11_EAC_Format: formatGPU = GPUTextureFormat.EACRG11Snorm; break; case RGBA_ASTC_4x4_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC4x4UnormSRGB : GPUTextureFormat.ASTC4x4Unorm; break; case RGBA_ASTC_5x4_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC5x4UnormSRGB : GPUTextureFormat.ASTC5x4Unorm; break; case RGBA_ASTC_5x5_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC5x5UnormSRGB : GPUTextureFormat.ASTC5x5Unorm; break; case RGBA_ASTC_6x5_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC6x5UnormSRGB : GPUTextureFormat.ASTC6x5Unorm; break; case RGBA_ASTC_6x6_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC6x6UnormSRGB : GPUTextureFormat.ASTC6x6Unorm; break; case RGBA_ASTC_8x5_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x5UnormSRGB : GPUTextureFormat.ASTC8x5Unorm; break; case RGBA_ASTC_8x6_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x6UnormSRGB : GPUTextureFormat.ASTC8x6Unorm; break; case RGBA_ASTC_8x8_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x8UnormSRGB : GPUTextureFormat.ASTC8x8Unorm; break; case RGBA_ASTC_10x5_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x5UnormSRGB : GPUTextureFormat.ASTC10x5Unorm; break; case RGBA_ASTC_10x6_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x6UnormSRGB : GPUTextureFormat.ASTC10x6Unorm; break; case RGBA_ASTC_10x8_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x8UnormSRGB : GPUTextureFormat.ASTC10x8Unorm; break; case RGBA_ASTC_10x10_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x10UnormSRGB : GPUTextureFormat.ASTC10x10Unorm; break; case RGBA_ASTC_12x10_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC12x10UnormSRGB : GPUTextureFormat.ASTC12x10Unorm; break; case RGBA_ASTC_12x12_Format: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC12x12UnormSRGB : GPUTextureFormat.ASTC12x12Unorm; break; case RGBAFormat: formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm; break; default: error( 'WebGPURenderer: Unsupported texture format.', format ); } } else { switch ( format ) { case RGBAFormat: switch ( type ) { case ByteType: formatGPU = GPUTextureFormat.RGBA8Snorm; break; case ShortType: formatGPU = textureFormatsTier1 ? GPUTextureFormat.RGBA16Snorm : GPUTextureFormat.RGBA16Sint; break; case UnsignedShortType: formatGPU = textureFormatsTier1 ? GPUTextureFormat.RGBA16Unorm : GPUTextureFormat.RGBA16Uint; break; case UnsignedIntType: formatGPU = GPUTextureFormat.RGBA32Uint;