UNPKG

three

Version:

JavaScript 3D library

1,885 lines 57 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 isComparison = texture.isDepthTexture === true && texture.compareFunction !== null && textureNode.isSampleCompare() && backend.hasCompatibility( Compatibility.TEXTURE_COMPARE );

		const samplerKey = texture.minFilter + '-' + texture.magFilter + '-' +
			texture.wrapS + '-' + texture.wrapT + '-' + ( texture.wrapR || '0' ) + '-' +
			texture.anisotropy + '-' + ( texture.isDepthTexture === true ? 1 : 0 ) + '-' +
			( isComparison ? 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 && isComparison === false ) {

				_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 ( isComparison ) {

				_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 );
		const renderTarget = texture.renderTarget;

		// WebGPU multisampled 2D textures can only have a single array layer.
		const useSeparateMSAATextures = samples > 1 && renderTarget !== null && depth > 1 && dimension === GPUTextureDimension.TwoD;
		const supportsTransientAttachments = GPUTextureUsage.TRANSIENT_ATTACHMENT !== undefined;
		// Layered rendering can resume after a framebuffer copy, so its attachments must support loading.
		const useTransientAttachments = supportsTransientAttachments && useSeparateMSAATextures === false;
		const useTransientDepthAttachment = texture.isDepthTexture === true &&
			useTransientAttachments &&
			renderTarget?.storeMultisampledDepthBuffer === false &&
			( renderTarget.stencilBuffer === false || renderTarget.storeMultisampledStencilBuffer === false );
		const useTransientColorAttachment = texture.isDepthTexture !== true &&
			useTransientAttachments &&
			renderTarget?.storeMultisampledColorBuffer === false;

		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;

		}

		// when the multisampled data are discarded, try to use a transient attachment if possible

		if ( primarySamples > 1 && useTransientDepthAttachment ) {

			usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TRANSIENT_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 || useSeparateMSAATextures ) {

			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

			// when the multisampled data are discarded, try to use a transient attachment if possible

			if ( useTransientDepthAttachment || useTransientColorAttachment ) {

				msaaTextureDescriptorGPU.usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TRANSIENT_ATTACHMENT;

			}

			if ( useSeparateMSAATextures ) {

				msaaTextureDescriptorGPU.size = Object.assign( {}, msaaTextureDescriptorGPU.size, { depthOrArrayLayers: 1 } );

				textureData.msaaTextures = [];

				for ( let i = 0; i < depth; i ++ ) {

					msaaTextureDescriptorGPU.label = textureDescriptorGPU.label + '-msaa-' + i;
					textureData.msaaTextures.push( backend.device.createTexture( msaaTextureDescriptorGPU ) );

				}

			} else {

				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.externalTexture !== true ) textureData.texture.destroy();

		if ( textureData.msaaTexture !== undefined ) textureData.msaaTexture.destroy();

		if ( textureData.msaaTextures !== undefined ) {

			for ( const msaaTexture of textureData.msaaTextures ) 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 cube face, depth slice or array layer 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;
						break;

					case IntType:
						formatGPU = GPUTextureFormat.RGBA32Sint;
						break;

					case UnsignedByteType:
						formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm;
						break;

					case HalfFloatType:
						formatGPU = GPUTextureFormat.RGBA16Float;
						break;

					case FloatType:
						formatGPU = GPUTextureFormat.RGBA32Float;
						break;

					default:
						error( 'WebGPURenderer: Unsupported texture type with RGBAFormat.', type );

				}

				break;

			case RGBFormat:

				switch ( type ) {

					case UnsignedInt5999Type:
						formatGPU = GPUTextureFormat.RGB9E5UFloat;
						break;

					case UnsignedInt101111Type:
						formatGPU = GPUTextureFormat.RG11B10UFloat;
						break;

					default:
						error( 'WebGPURenderer: Unsupported texture type with RGBFormat.', type );

				}

				break;

			case RedFormat:

				switch ( type ) {

					case ByteType:
						formatGPU = GPUTextureFormat.R8Snorm;
						break;

					case ShortType:
						formatGPU = textureFormatsTier1 ? GPUTextureFormat.R16Snorm : GPUTextureFormat.R16Sint;
						break;

					case UnsignedShortType:
						formatGPU = textureFormatsTier1 ? GPUTextureFormat.R16Unorm : GPUTextureFormat.R16Uint;
						break;

					case UnsignedIntType:
						formatGPU = GPUTextureFormat.R32Uint;
						break;

					case IntType:
						formatGPU = GPUTextureFormat.R32Sint;
						break;

					case UnsignedByteType:
						formatGPU = GPUTextureFormat.R8Unorm;
						break;

					case HalfFloatType:
						formatGPU = GPUTextureFormat.R16Float;
						break;

					case FloatType:
						formatGPU = GPUTextureFormat.R32Float;
						break;

					default:
						error( 'WebGPURenderer: Unsupported texture type with RedFormat.', type );

				}

				break;

			case RGFormat:

				switch ( type ) {

					case ByteType:
						formatGPU = GPUTextureFormat.RG8Snorm;
						break;

					case ShortType:
						formatGPU = textureFormatsTier1 ? GPUTextureFormat.RG16Snorm : GPUTextureFormat.RG16Sint;
						break;

					case UnsignedShortType:
						formatGPU = textureFormatsTier1 ? GPUTextureFormat.RG16Unorm : GPUTextureFormat.RG16Uint;
						break;

					case UnsignedIntType:
						formatGPU = GPUTextureFormat.RG32Uint;
						break;

					case IntType:
						formatGPU = GPUTextureFormat.RG32Sint;
						break;

					case UnsignedByteType:
						formatGPU = GPUTextureFormat.RG8Unorm;
						break;

					case HalfFloatType:
						formatGPU = GPUTextureFormat.RG16Float;
						break;

					case FloatType:
						formatGPU = GPUTextureFormat.RG32Float;
						break;

					default:
						error( 'WebGPURenderer: Unsupported texture type with RGFormat.', type );

				}

				break;

			case DepthFormat:

				switch ( type ) {

					case UnsignedShortType:
						formatGPU = GPUTextureFormat.Depth16Unorm;
						break;

					case UnsignedIntType:
						formatGPU = GPUTextureFormat.Depth24Plus;
						break;

					case FloatType:
						formatGPU = GPUTextureFormat.Depth32Float;
						break;

					default:
						error( 'WebGPURenderer: Unsupported texture type with DepthFormat.', type );

				}

				break;

			case DepthStencilFormat:

				switch ( type ) {

					case UnsignedInt248Type:
						formatGPU = GPUTextureFormat.Depth24PlusStencil8;
						break;

					case FloatType:

						if ( device && device.features.has( GPUFeatureName.Depth32FloatStencil8 ) === false ) {

							error( 'WebGPURenderer: Depth textures with DepthStencilFormat + FloatType can only be used with the "depth32float-stencil8" GPU feature.' );

						}

						formatGPU = GPUTextureFormat.Depth32FloatStencil8;

						break;

					default:
						error( 'WebGPURenderer: Unsupported texture type with DepthStencilFormat.', type );

				}

				break;

			case RedIntegerFormat:

				switch ( type ) {

					case IntType:
						formatGPU = GPUTextureFormat.R32Sint;
						break;

					case UnsignedIntType:
						formatGPU = GPUTextureFormat.R32Uint;
						break;

					default:
						error( 'WebGPURenderer: Unsupported texture type with RedIntegerFormat.', type );

				}

				break;

			case RGIntegerFormat:

				switch ( type ) {

					case IntType:
						formatGPU = GPUTextureFormat.RG32Sint;
						break;

					case UnsignedIntType:
						formatGPU = GPUTextureFormat.RG32Uint;
						break;

					default:
						error( 'WebGPURenderer: Unsupported texture type with RGIntegerFormat.', type );

				}

				break;

			case RGBAIntegerFormat:

				switch ( type ) {

					case IntType:
						formatGPU = GPUTextureFormat.RGBA32Sint;
						break;

					case UnsignedIntType:
						formatGPU = GPUTextureFormat.RGBA32Uint;
						break;

					default:
						error( 'WebGPURenderer: Unsupported texture type with RGBAIntegerFormat.', type );

				}

				break;

			default:
				error( 'WebGPURenderer: Unsupported texture format.', format );

		}

	}

	return formatGPU;

}

export default WebGPUTextureUtils;