فهرست منبع

Add THREE.CompressedArrayTexture (#24745)

* wip

* texture array is working with hardcoded values

* KTX2Loader: Decode additional layers from array textures.

* added compressedarraytexture

* fix merge

* fixed level issues

* fix copyTextureToTexture3D, updated compressedarraytexture arguments, and added doc

* remove build

* fix deepscan

* Update WebGLTextures.js

* Update webgl2_texture2darray_compressed.html

* Update webgl2_texture2darray_compressed.html

* replaced with simple example

* remove build

* Update webgl2_texture2darray_compressed.html

* Update webgl2_texture2darray_compressed.html

* make the example framerate independent

* fix init texture2darray for compressed texture in webglrenderer

* Update webgl2_texture2darray_compressed.html

Co-authored-by: Don McCurdy <[email protected]>
Co-authored-by: Michael Herzog <[email protected]>
Renaud Rohlinger 2 سال پیش
والد
کامیت
7b39286b91

+ 75 - 0
docs/api/en/textures/CompressedArrayTexture.html

@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		[page:CompressedTexture] &rarr;
+
+		<h1>[name]</h1>
+
+		<p class="desc">
+		Creates an texture 2D array based on data in compressed form, for example from a [link:https://en.wikipedia.org/wiki/DirectDraw_Surface DDS] file.<br /><br />
+
+
+		For use with the [page:CompressedTextureLoader CompressedTextureLoader].
+		</p>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [param:Array mipmaps], [param:Number width], [param:Number height], [param:Constant format], [param:Constant type] )</h3>
+		<p>
+		[page:Array mipmaps] -- The mipmaps array should contain objects with data, width and height. The mipmaps should be of the correct format and type.<br />
+
+		[page:Number width] -- The width of the biggest mipmap.<br />
+
+		[page:Number height] -- The height of the biggest mipmap.<br />
+
+		[page:Number depth] -- The number of layers of the 2D array texture.<br />
+
+		[page:Constant format] -- The format used in the mipmaps.
+		See [page:Textures ST3C Compressed Texture Formats],
+		[page:Textures PVRTC Compressed Texture Formats] and
+		[page:Textures ETC Compressed Texture Format] for other choices.<br />
+
+		[page:Constant type] -- Default is [page:Textures THREE.UnsignedByteType].
+		See [page:Textures type constants] for other choices.<br />
+
+		</p>
+
+
+		<h2>Properties</h2>
+
+		See the base [page:CompressedTexture CompressedTexture] class for common properties.
+
+		<h3>[property:number wrapR]</h3>
+		<p>
+		This defines how the texture is wrapped in the depth direction.<br />
+		The default is [page:Textures THREE.ClampToEdgeWrapping], where the edge is clamped to the outer edge texels.
+		The other two choices are [page:Textures THREE.RepeatWrapping] and [page:Textures THREE.MirroredRepeatWrapping].
+		See the [page:Textures texture constants] page for details.
+		</p>
+
+		<h3>[property:Boolean isCompressedArrayTexture]</h3>
+		<p>
+			Read-only flag to check if a given object is of type [name].
+		</p>
+
+		<h2>Methods</h2>
+
+		<p>
+		See the base [page:CompressedTexture CompressedTexture] class for common methods.
+		</p>
+
+		<h2>Source</h2>
+
+		<p>
+			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+		</p>
+	</body>
+</html>

+ 75 - 0
docs/api/zh/textures/CompressedArrayTexture.html

@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		[page:CompressedTexture] &rarr;
+
+		<h1>[name]</h1>
+
+		<p class="desc">
+		Creates an texture 2D array based on data in compressed form, for example from a [link:https://en.wikipedia.org/wiki/DirectDraw_Surface DDS] file.<br /><br />
+
+
+		For use with the [page:CompressedTextureLoader CompressedTextureLoader].
+		</p>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [param:Array mipmaps], [param:Number width], [param:Number height], [param:Constant format], [param:Constant type] )</h3>
+		<p>
+		[page:Array mipmaps] -- The mipmaps array should contain objects with data, width and height. The mipmaps should be of the correct format and type.<br />
+
+		[page:Number width] -- The width of the biggest mipmap.<br />
+
+		[page:Number height] -- The height of the biggest mipmap.<br />
+
+		[page:Number depth] -- The number of layers of the 2D array texture.<br />
+
+		[page:Constant format] -- The format used in the mipmaps.
+		See [page:Textures ST3C Compressed Texture Formats],
+		[page:Textures PVRTC Compressed Texture Formats] and
+		[page:Textures ETC Compressed Texture Format] for other choices.<br />
+
+		[page:Constant type] -- Default is [page:Textures THREE.UnsignedByteType].
+		See [page:Textures type constants] for other choices.<br />
+
+		</p>
+
+
+		<h2>Properties</h2>
+
+		See the base [page:CompressedTexture CompressedTexture] class for common properties.
+
+		<h3>[property:number wrapR]</h3>
+		<p>
+		This defines how the texture is wrapped in the depth direction.<br />
+		The default is [page:Textures THREE.ClampToEdgeWrapping], where the edge is clamped to the outer edge texels.
+		The other two choices are [page:Textures THREE.RepeatWrapping] and [page:Textures THREE.MirroredRepeatWrapping].
+		See the [page:Textures texture constants] page for details.
+		</p>
+
+		<h3>[property:Boolean isCompressedArrayTexture]</h3>
+		<p>
+			Read-only flag to check if a given object is of type [name].
+		</p>
+
+		<h2>Methods</h2>
+
+		<p>
+		See the base [page:CompressedTexture CompressedTexture] class for common methods.
+		</p>
+
+		<h2>Source</h2>
+
+		<p>
+			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+		</p>
+	</body>
+</html>

+ 2 - 0
docs/list.json

@@ -313,6 +313,7 @@
 			"Textures": {
 				"CanvasTexture": "api/en/textures/CanvasTexture",
 				"CompressedTexture": "api/en/textures/CompressedTexture",
+				"CompressedArrayTexture": "api/en/textures/CompressedArrayTexture",
 				"CubeTexture": "api/en/textures/CubeTexture",
 				"Data3DTexture": "api/en/textures/Data3DTexture",
 				"DataArrayTexture": "api/en/textures/DataArrayTexture",
@@ -814,6 +815,7 @@
 			"纹理贴图": {
 				"CanvasTexture": "api/zh/textures/CanvasTexture",
 				"CompressedTexture": "api/zh/textures/CompressedTexture",
+				"CompressedArrayTexture": "api/zh/textures/CompressedArrayTexture",
 				"CubeTexture": "api/zh/textures/CubeTexture",
 				"DataArrayTexture": "api/zh/textures/DataArrayTexture",
 				"Data3DTexture": "api/zh/textures/Data3DTexture",

+ 1 - 0
examples/files.json

@@ -315,6 +315,7 @@
 		"webgl2_multiple_rendertargets",
 		"webgl2_multisampled_renderbuffers",
 		"webgl2_rendertarget_texture2darray",
+		"webgl2_texture2darray_compressed",
 		"webgl2_ubo",
 		"webgl2_volume_cloud",
 		"webgl2_volume_instancing",

+ 67 - 28
examples/jsm/loaders/KTX2Loader.js

@@ -13,6 +13,7 @@
 
 import {
 	CompressedTexture,
+	CompressedArrayTexture,
 	Data3DTexture,
 	DataTexture,
 	FileLoader,
@@ -35,7 +36,7 @@ import {
 	RGBAFormat,
 	RGFormat,
 	sRGBEncoding,
-	UnsignedByteType
+	UnsignedByteType,
 } from 'three';
 import { WorkerPool } from '../utils/WorkerPool.js';
 import {
@@ -236,16 +237,21 @@ class KTX2Loader extends Loader {
 
 	}
 
-	_createTextureFrom( transcodeResult ) {
+	_createTextureFrom( transcodeResult, container ) {
 
 		const { mipmaps, width, height, format, type, error, dfdTransferFn, dfdFlags } = transcodeResult;
 
 		if ( type === 'error' ) return Promise.reject( error );
 
-		const texture = new CompressedTexture( mipmaps, width, height, format, UnsignedByteType );
+		const texture = container.layerCount > 1
+			? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, UnsignedByteType )
+			: new CompressedTexture( mipmaps, width, height, format, UnsignedByteType );
+
+
 		texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter;
 		texture.magFilter = LinearFilter;
 		texture.generateMipmaps = false;
+
 		texture.needsUpdate = true;
 		texture.encoding = dfdTransferFn === KHR_DF_TRANSFER_SRGB ? sRGBEncoding : LinearEncoding;
 		texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED );
@@ -257,7 +263,7 @@ class KTX2Loader extends Loader {
 	/**
 	 * @param {ArrayBuffer} buffer
 	 * @param {object?} config
-	 * @return {Promise<CompressedTexture|DataTexture|Data3DTexture>}
+	 * @return {Promise<CompressedTexture|CompressedArrayTexture|DataTexture|Data3DTexture>}
 	 */
 	async _createTexture( buffer, config = {} ) {
 
@@ -270,13 +276,12 @@ class KTX2Loader extends Loader {
 		}
 
 		//
-
 		const taskConfig = config;
 		const texturePending = this.init().then( () => {
 
 			return this.workerPool.postMessage( { type: 'transcode', buffer, taskConfig: taskConfig }, [ buffer ] );
 
-		} ).then( ( e ) => this._createTextureFrom( e.data ) );
+		} ).then( ( e ) => this._createTextureFrom( e.data, container ) );
 
 		// Cache the task result.
 		_taskCache.set( buffer, { promise: texturePending } );
@@ -437,6 +442,7 @@ KTX2Loader.BasisWorker = function () {
 		const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
 		const width = ktx2File.getWidth();
 		const height = ktx2File.getHeight();
+		const layers = ktx2File.getLayers() || 1;
 		const levels = ktx2File.getLevels();
 		const hasAlpha = ktx2File.getHasAlpha();
 		const dfdTransferFn = ktx2File.getDFDTransferFunc();
@@ -462,30 +468,39 @@ KTX2Loader.BasisWorker = function () {
 
 		for ( let mip = 0; mip < levels; mip ++ ) {
 
-			const levelInfo = ktx2File.getImageLevelInfo( mip, 0, 0 );
-			const mipWidth = levelInfo.origWidth;
-			const mipHeight = levelInfo.origHeight;
-			const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, 0, 0, transcoderFormat ) );
-
-			const status = ktx2File.transcodeImage(
-				dst,
-				mip,
-				0,
-				0,
-				transcoderFormat,
-				0,
-				- 1,
-				- 1,
-			);
+			const layerMips = [];
+
+			let mipWidth, mipHeight;
+
+			for ( let layer = 0; layer < layers; layer ++ ) {
+
+				const levelInfo = ktx2File.getImageLevelInfo( mip, layer, 0 );
+				mipWidth = levelInfo.origWidth;
+				mipHeight = levelInfo.origHeight;
+				const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) );
+				const status = ktx2File.transcodeImage(
+					dst,
+					mip,
+					layer,
+					0,
+					transcoderFormat,
+					0,
+					- 1,
+					- 1,
+				);
+
+				if ( ! status ) {
 
-			if ( ! status ) {
+					cleanup();
+					throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' );
 
-				cleanup();
-				throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' );
+				}
+
+				layerMips.push( dst );
 
 			}
 
-			mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } );
+			mipmaps.push( { data: concat( layerMips ), width: mipWidth, height: mipHeight } );
 
 		}
 
@@ -612,6 +627,33 @@ KTX2Loader.BasisWorker = function () {
 
 	}
 
+	/** Concatenates N byte arrays. */
+	function concat( arrays ) {
+
+		let totalByteLength = 0;
+
+		for ( const array of arrays ) {
+
+			totalByteLength += array.byteLength;
+
+		}
+
+		const result = new Uint8Array( totalByteLength );
+
+		let byteOffset = 0;
+
+		for ( const array of arrays ) {
+
+			result.set( array, byteOffset );
+
+			byteOffset += array.byteLength;
+
+		}
+
+		return result;
+
+	}
+
 };
 
 //
@@ -673,8 +715,6 @@ async function createDataTexture( container ) {
 
 	}
 
-	//
-
 	const level = container.levels[ 0 ];
 
 	let levelData;
@@ -731,7 +771,6 @@ async function createDataTexture( container ) {
 		view = levelData;
 
 	}
-
 	//
 
 	const texture = pixelDepth === 0

BIN
examples/screenshots/webgl2_texture2darray_compressed.jpg


BIN
examples/textures/spiritedaway.ktx2


+ 181 - 0
examples/webgl2_texture2darray_compressed.html

@@ -0,0 +1,181 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - 2D compressed texture array</title>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<script id="vs" type="x-shader/x-vertex">
+	uniform vec2 size;
+	out vec2 vUv;
+
+	void main() {
+
+		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+
+		// Convert position.xy to 1.0-0.0
+
+		vUv.xy = position.xy / size + 0.5;
+		vUv.y = 1.0 - vUv.y; // original data is upside down
+
+	}
+	</script>
+
+	<script id="fs" type="x-shader/x-fragment">
+	precision highp float;
+	precision highp int;
+	precision highp sampler2DArray;
+
+	uniform sampler2DArray diffuse;
+	in vec2 vUv;
+	uniform int depth;
+
+	out vec4 outColor;
+
+	void main() {
+
+		vec4 color = texture( diffuse, vec3( vUv, depth ) );
+
+		// lighten a bit
+		outColor = vec4( color.rgb + .2, 1.0 );
+
+	}
+	</script>
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - 2D Compressed Texture Array<br />
+			Loop from the movie Spirited away
+			by the <a href="https://www.ghibli.jp/" target="_blank" rel="noopener">Studio Ghibli</a><br />
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+			import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
+			if ( WebGL.isWebGL2Available() === false ) {
+
+				document.body.appendChild( WebGL.getWebGL2ErrorMessage() );
+
+			}
+
+			let camera, scene, mesh, renderer, stats, clock;
+
+			const planeWidth = 50;
+			const planeHeight = 25;
+
+			let depthStep = 1;
+
+			init();
+			animate();
+
+			function init() {
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 2000 );
+				camera.position.z = 70;
+
+				scene = new THREE.Scene();
+
+				//
+				clock = new THREE.Clock();
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				//
+
+				const ktx2Loader = new KTX2Loader();
+				ktx2Loader.setTranscoderPath( 'js/libs/basis/' );
+				ktx2Loader.detectSupport( renderer );
+
+				ktx2Loader.load( 'textures/spiritedaway.ktx2', function ( texturearray ) {
+
+					const material = new THREE.ShaderMaterial( {
+						uniforms: {
+							diffuse: { value: texturearray },
+							depth: { value: 55 },
+							size: { value: new THREE.Vector2( planeWidth, planeHeight ) }
+						},
+						vertexShader: document.getElementById( 'vs' ).textContent.trim(),
+						fragmentShader: document.getElementById( 'fs' ).textContent.trim(),
+						glslVersion: THREE.GLSL3
+					} );
+
+					const geometry = new THREE.PlaneGeometry( planeWidth, planeHeight );
+
+					mesh = new THREE.Mesh( geometry, material );
+
+					scene.add( mesh );
+
+				} );
+
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				if ( mesh ) {
+
+					const delta = clock.getDelta() * 10;
+
+					depthStep += delta;
+
+					const value = depthStep % 5;
+
+					mesh.material.uniforms[ 'depth' ].value = value;
+
+				}
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1 - 0
src/Three.js

@@ -33,6 +33,7 @@ export { DataTexture } from './textures/DataTexture.js';
 export { DataArrayTexture } from './textures/DataArrayTexture.js';
 export { Data3DTexture } from './textures/Data3DTexture.js';
 export { CompressedTexture } from './textures/CompressedTexture.js';
+export { CompressedArrayTexture } from './textures/CompressedArrayTexture.js';
 export { CubeTexture } from './textures/CubeTexture.js';
 export { CanvasTexture } from './textures/CanvasTexture.js';
 export { DepthTexture } from './textures/DepthTexture.js';

+ 3 - 3
src/renderers/WebGLRenderer.js

@@ -1967,7 +1967,7 @@ function WebGLRenderer( parameters = {} ) {
 
 			const texture = renderTarget.texture;
 
-			if ( texture.isData3DTexture || texture.isDataArrayTexture ) {
+			if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) {
 
 				isRenderTarget3D = true;
 
@@ -2208,7 +2208,7 @@ function WebGLRenderer( parameters = {} ) {
 
 		} else {
 
-			if ( srcTexture.isCompressedTexture ) {
+			if ( srcTexture.isCompressedArrayTexture ) {
 
 				console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture.' );
 				_gl.compressedTexSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, image.data );
@@ -2244,7 +2244,7 @@ function WebGLRenderer( parameters = {} ) {
 
 			textures.setTexture3D( texture, 0 );
 
-		} else if ( texture.isDataArrayTexture ) {
+		} else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) {
 
 			textures.setTexture2DArray( texture, 0 );
 

+ 30 - 0
src/renderers/webgl/WebGLState.js

@@ -952,6 +952,20 @@ function WebGLState( gl, extensions, capabilities ) {
 
 	}
 
+	function compressedTexImage3D() {
+
+		try {
+
+			gl.compressedTexImage3D.apply( gl, arguments );
+
+		} catch ( error ) {
+
+			console.error( 'THREE.WebGLState:', error );
+
+		}
+
+	}
+
 	function texSubImage2D() {
 
 		try {
@@ -994,6 +1008,20 @@ function WebGLState( gl, extensions, capabilities ) {
 
 	}
 
+	function compressedTexSubImage3D() {
+
+		try {
+
+			gl.compressedTexSubImage3D.apply( gl, arguments );
+
+		} catch ( error ) {
+
+			console.error( 'THREE.WebGLState:', error );
+
+		}
+
+	}
+
 	function texStorage2D() {
 
 		try {
@@ -1239,6 +1267,7 @@ function WebGLState( gl, extensions, capabilities ) {
 		bindTexture: bindTexture,
 		unbindTexture: unbindTexture,
 		compressedTexImage2D: compressedTexImage2D,
+		compressedTexImage3D: compressedTexImage3D,
 		texImage2D: texImage2D,
 		texImage3D: texImage3D,
 
@@ -1250,6 +1279,7 @@ function WebGLState( gl, extensions, capabilities ) {
 		texSubImage2D: texSubImage2D,
 		texSubImage3D: texSubImage3D,
 		compressedTexSubImage2D: compressedTexSubImage2D,
+		compressedTexSubImage3D: compressedTexSubImage3D,
 
 		scissor: scissor,
 		viewport: viewport,

+ 69 - 16
src/renderers/webgl/WebGLTextures.js

@@ -415,6 +415,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		array.push( texture.wrapS );
 		array.push( texture.wrapT );
+		array.push( texture.wrapR || 0 );
 		array.push( texture.magFilter );
 		array.push( texture.minFilter );
 		array.push( texture.anisotropy );
@@ -671,7 +672,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		let textureType = _gl.TEXTURE_2D;
 
-		if ( texture.isDataArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY;
+		if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY;
 		if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D;
 
 		const forceUpload = initTexture( textureProperties, texture );
@@ -853,45 +854,97 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			} else if ( texture.isCompressedTexture ) {
 
-				if ( useTexStorage && allocateMemory ) {
+				if ( texture.isCompressedArrayTexture ) {
 
-					state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height );
+					if ( useTexStorage && allocateMemory ) {
 
-				}
+						state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height, image.depth );
 
-				for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+					}
 
-					mipmap = mipmaps[ i ];
+					for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
 
-					if ( texture.format !== RGBAFormat ) {
+						mipmap = mipmaps[ i ];
 
-						if ( glFormat !== null ) {
+						if ( texture.format !== RGBAFormat ) {
 
-							if ( useTexStorage ) {
+							if ( glFormat !== null ) {
+
+								if ( useTexStorage ) {
+
+									state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data, 0, 0 );
 
-								state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data );
+								} else {
+
+									state.compressedTexImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data, 0, 0 );
+
+								}
 
 							} else {
 
-								state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+								console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' );
 
 							}
 
 						} else {
 
-							console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' );
+							if ( useTexStorage ) {
+
+								state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data );
+
+							} else {
+
+								state.texImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap.data );
+
+							}
 
 						}
 
-					} else {
+					}
 
-						if ( useTexStorage ) {
+				} else {
 
-							state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data );
+					if ( useTexStorage && allocateMemory ) {
+
+						state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height );
+
+					}
+
+					for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+						mipmap = mipmaps[ i ];
+
+						if ( texture.format !== RGBAFormat ) {
+
+							if ( glFormat !== null ) {
+
+								if ( useTexStorage ) {
+
+									state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data );
+
+								} else {
+
+									state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+
+								}
+
+							} else {
+
+								console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' );
+
+							}
 
 						} else {
 
-							state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+							if ( useTexStorage ) {
+
+								state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data );
+
+							} else {
+
+								state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+
+							}
 
 						}
 

+ 18 - 0
src/textures/CompressedArrayTexture.js

@@ -0,0 +1,18 @@
+import { ClampToEdgeWrapping } from '../constants.js';
+import { CompressedTexture } from './CompressedTexture.js';
+
+class CompressedArrayTexture extends CompressedTexture {
+
+	constructor( mipmaps, width, height, depth, format, type ) {
+
+		super( mipmaps, width, height, format, type );
+
+		this.isCompressedArrayTexture = true;
+		this.image.depth = depth;
+		this.wrapR = ClampToEdgeWrapping;
+
+	}
+
+}
+
+export { CompressedArrayTexture };