Skip to content

Commit 9c00c1c

Browse files
support all texture formats and types in DataTextureArray.setLayerUpdate and CompressedTextureArray.setLayerUpdate (#28654)
* create TextureUtils * docs * use tabs instead of spaces * fix lint * use a byte view * remove typo * ensure the unit8array adheres to the byteLength of the input data source * add example * update screenshot * fix indentation * convert more spaces to tabs * generate screenshot * generate screenshot * test increase debounce and render in interval instead of loop * cleanup example * preserve parity between type and typedarray constructor * remove dependency on setInterval * update screenshot --------- Co-authored-by: Renaud Rohlinger <[email protected]>
1 parent 01dc3d9 commit 9c00c1c

File tree

7 files changed

+376
-53
lines changed

7 files changed

+376
-53
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<base href="../../../" />
6+
<script src="page.js"></script>
7+
<link type="text/css" rel="stylesheet" href="page.css" />
8+
</head>
9+
<body>
10+
<h1>[name]</h1>
11+
12+
<p class="desc">A class containing utility functions for textures.</p>
13+
14+
<h2>Methods</h2>
15+
16+
<h3>[method:Number getByteLength]( [param:Number width], [param:Number height], [param:Number format], [param:Number type] )</h3>
17+
<p>
18+
Given the width, height, format, and type of a texture. Determines how
19+
many bytes must be used to represent the texture.
20+
</p>
21+
22+
<h2>Source</h2>
23+
24+
<p>
25+
[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
26+
</p>
27+
</body>
28+
</html>

examples/files.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@
293293
"webgl_simple_gi",
294294
"webgl_texture2darray",
295295
"webgl_texture2darray_compressed",
296+
"webgl_texture2darray_layerupdate",
296297
"webgl_texture3d",
297298
"webgl_texture3d_partialupdate",
298299
"webgl_ubo",
3.33 KB
Loading
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>three.js webgl - texture array layer update</title>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
7+
<link type="text/css" rel="stylesheet" href="main.css">
8+
</head>
9+
<script id="vs" type="x-shader/x-vertex">
10+
uniform vec2 size;
11+
attribute uint instancedIndex;
12+
flat out uint diffuseIndex;
13+
out vec2 vUv;
14+
15+
void main() {
16+
17+
vec3 translation = vec3(0, float(instancedIndex) * size.y - size.y, 0);
18+
gl_Position = projectionMatrix * modelViewMatrix * vec4( position + translation, 1.0 );
19+
20+
diffuseIndex = instancedIndex;
21+
22+
// Convert position.xy to 1.0-0.0
23+
24+
vUv.xy = position.xy / size + 0.5;
25+
vUv.y = 1.0 - vUv.y; // original data is upside down
26+
27+
}
28+
</script>
29+
30+
<script id="fs" type="x-shader/x-fragment">
31+
precision highp float;
32+
precision highp int;
33+
precision highp sampler2DArray;
34+
35+
uniform sampler2DArray diffuse;
36+
in vec2 vUv;
37+
flat in uint diffuseIndex;
38+
39+
out vec4 outColor;
40+
41+
void main() {
42+
43+
outColor = texture( diffuse, vec3( vUv, diffuseIndex ) );
44+
45+
}
46+
</script>
47+
<body>
48+
<div id="info">
49+
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - 2D Compressed Texture Array Layer Updates<br />
50+
Loop from the movie Spirited away
51+
by the <a href="https://www.ghibli.jp/" target="_blank" rel="noopener">Studio Ghibli</a><br />
52+
</div>
53+
54+
<script type="importmap">
55+
{
56+
"imports": {
57+
"three": "../build/three.module.js",
58+
"three/addons/": "./jsm/"
59+
}
60+
}
61+
</script>
62+
63+
<script type="module">
64+
65+
import * as THREE from 'three';
66+
67+
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
68+
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
69+
70+
let camera, scene, mesh, renderer;
71+
72+
const planeWidth = 20;
73+
const planeHeight = 10;
74+
75+
init();
76+
77+
async function init() {
78+
79+
const container = document.createElement( 'div' );
80+
document.body.appendChild( container );
81+
82+
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 2000 );
83+
camera.position.z = 70;
84+
85+
scene = new THREE.Scene();
86+
87+
// Configure the renderer.
88+
89+
renderer = new THREE.WebGLRenderer( { antialias: true } );
90+
renderer.setPixelRatio( window.devicePixelRatio );
91+
renderer.setSize( window.innerWidth, window.innerHeight );
92+
container.appendChild( renderer.domElement );
93+
94+
// Configure the KTX2 loader.
95+
96+
const ktx2Loader = new KTX2Loader();
97+
ktx2Loader.setTranscoderPath( 'jsm/libs/basis/' );
98+
ktx2Loader.detectSupport( renderer );
99+
100+
// Load several KTX2 textures which will later be used to modify
101+
// specific texture array layers.
102+
103+
const spiritedaway = await ktx2Loader.loadAsync( 'textures/spiritedaway.ktx2' );
104+
105+
// Create a texture array for rendering.
106+
107+
const layerByteLength = THREE.TextureUtils.getByteLength(
108+
spiritedaway.image.width,
109+
spiritedaway.image.height,
110+
spiritedaway.format,
111+
spiritedaway.type,
112+
);
113+
114+
const textureArray = new THREE.CompressedArrayTexture( [
115+
{
116+
data: new Uint8Array( layerByteLength * 3 ),
117+
width: spiritedaway.image.width,
118+
height: spiritedaway.image.height,
119+
}
120+
], spiritedaway.image.width, spiritedaway.image.height, 3, spiritedaway.format, spiritedaway.type );
121+
122+
// Setup the GUI
123+
124+
const formData = {
125+
srcLayer: 0,
126+
destLayer: 0,
127+
transfer() {
128+
const layerElementLength = layerByteLength / spiritedaway.mipmaps[ 0 ].data.BYTES_PER_ELEMENT;
129+
textureArray.mipmaps[ 0 ].data.set(
130+
spiritedaway.mipmaps[ 0 ].data.subarray(
131+
layerElementLength * ( formData.srcLayer % spiritedaway.image.depth ),
132+
layerElementLength * ( ( formData.srcLayer % spiritedaway.image.depth ) + 1 ),
133+
),
134+
layerByteLength * formData.destLayer,
135+
);
136+
textureArray.addLayerUpdate( formData.destLayer );
137+
textureArray.needsUpdate = true;
138+
139+
renderer.render( scene, camera );
140+
},
141+
};
142+
143+
const gui = new GUI();
144+
gui.add(formData, 'srcLayer', 0, spiritedaway.image.depth - 1, 1);
145+
gui.add(formData, 'destLayer', 0, textureArray.image.depth - 1, 1);
146+
gui.add(formData, 'transfer');
147+
148+
/// Setup the scene.
149+
150+
const material = new THREE.ShaderMaterial( {
151+
uniforms: {
152+
diffuse: { value: textureArray },
153+
size: { value: new THREE.Vector2( planeWidth, planeHeight ) }
154+
},
155+
vertexShader: document.getElementById( 'vs' ).textContent.trim(),
156+
fragmentShader: document.getElementById( 'fs' ).textContent.trim(),
157+
glslVersion: THREE.GLSL3
158+
} );
159+
160+
const geometry = new THREE.InstancedBufferGeometry();
161+
geometry.copy( new THREE.PlaneGeometry( planeWidth, planeHeight ) );
162+
geometry.instanceCount = 3;
163+
164+
const instancedIndexAttribute = new THREE.InstancedBufferAttribute(
165+
new Uint16Array( [ 0, 1, 2 ] ), 1, false, 1
166+
);
167+
instancedIndexAttribute.gpuType = THREE.IntType;
168+
geometry.setAttribute( 'instancedIndex', instancedIndexAttribute );
169+
170+
mesh = new THREE.InstancedMesh( geometry, material, 3 );
171+
172+
scene.add( mesh );
173+
174+
window.addEventListener( 'resize', onWindowResize );
175+
176+
}
177+
178+
function onWindowResize() {
179+
180+
renderer.setSize( window.innerWidth, window.innerHeight );
181+
182+
camera.aspect = window.innerWidth / window.innerHeight;
183+
camera.updateProjectionMatrix();
184+
185+
renderer.render( scene, camera );
186+
187+
}
188+
189+
</script>
190+
</body>
191+
</html>

src/Three.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export { Curve } from './extras/core/Curve.js';
153153
export { DataUtils } from './extras/DataUtils.js';
154154
export { ImageUtils } from './extras/ImageUtils.js';
155155
export { ShapeUtils } from './extras/ShapeUtils.js';
156+
export { TextureUtils } from './extras/TextureUtils.js';
156157
export { PMREMGenerator } from './extras/PMREMGenerator.js';
157158
export { WebGLUtils } from './renderers/webgl/WebGLUtils.js';
158159
export { createCanvasElement } from './utils.js';

src/extras/TextureUtils.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { AlphaFormat, LuminanceFormat, LuminanceAlphaFormat, RedFormat, RedIntegerFormat, RGFormat, RGIntegerFormat, RGBFormat, RGBAFormat, RGBAIntegerFormat, RGB_S3TC_DXT1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGB_PVRTC_2BPPV1_Format, RGBA_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, 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, RGBA_BPTC_Format, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RED_RGTC1_Format, SIGNED_RED_RGTC1_Format, RED_GREEN_RGTC2_Format, SIGNED_RED_GREEN_RGTC2_Format, UnsignedByteType, ByteType, UnsignedShortType, ShortType, HalfFloatType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedIntType, IntType, FloatType, UnsignedInt5999Type } from '../constants.js';
2+
3+
/**
4+
* Given the width, height, format, and type of a texture. Determines how many
5+
* bytes must be used to represent the texture.
6+
*/
7+
function getByteLength( width, height, format, type ) {
8+
9+
const typeByteLength = getTextureTypeByteLength( type );
10+
11+
switch ( format ) {
12+
13+
// https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml
14+
case AlphaFormat:
15+
return width * height;
16+
case LuminanceFormat:
17+
return width * height;
18+
case LuminanceAlphaFormat:
19+
return width * height * 2;
20+
case RedFormat:
21+
return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength;
22+
case RedIntegerFormat:
23+
return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength;
24+
case RGFormat:
25+
return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength;
26+
case RGIntegerFormat:
27+
return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength;
28+
case RGBFormat:
29+
return ( ( width * height * 3 ) / typeByteLength.components ) * typeByteLength.byteLength;
30+
case RGBAFormat:
31+
return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength;
32+
case RGBAIntegerFormat:
33+
return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength;
34+
35+
// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_s3tc_srgb/
36+
case RGB_S3TC_DXT1_Format:
37+
case RGBA_S3TC_DXT1_Format:
38+
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8;
39+
case RGBA_S3TC_DXT3_Format:
40+
case RGBA_S3TC_DXT5_Format:
41+
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16;
42+
43+
// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_pvrtc/
44+
case RGB_PVRTC_2BPPV1_Format:
45+
case RGBA_PVRTC_2BPPV1_Format:
46+
return ( Math.max( width, 16 ) * Math.max( height, 8 ) ) / 4;
47+
case RGB_PVRTC_4BPPV1_Format:
48+
case RGBA_PVRTC_4BPPV1_Format:
49+
return ( Math.max( width, 8 ) * Math.max( height, 8 ) ) / 2;
50+
51+
// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_etc/
52+
case RGB_ETC1_Format:
53+
case RGB_ETC2_Format:
54+
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8;
55+
case RGBA_ETC2_EAC_Format:
56+
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16;
57+
58+
// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_astc/
59+
case RGBA_ASTC_4x4_Format:
60+
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16;
61+
case RGBA_ASTC_5x4_Format:
62+
return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 3 ) / 4 ) * 16;
63+
case RGBA_ASTC_5x5_Format:
64+
return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 4 ) / 5 ) * 16;
65+
case RGBA_ASTC_6x5_Format:
66+
return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 4 ) / 5 ) * 16;
67+
case RGBA_ASTC_6x6_Format:
68+
return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 5 ) / 6 ) * 16;
69+
case RGBA_ASTC_8x5_Format:
70+
return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 4 ) / 5 ) * 16;
71+
case RGBA_ASTC_8x6_Format:
72+
return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 5 ) / 6 ) * 16;
73+
case RGBA_ASTC_8x8_Format:
74+
return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 7 ) / 8 ) * 16;
75+
case RGBA_ASTC_10x5_Format:
76+
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 4 ) / 5 ) * 16;
77+
case RGBA_ASTC_10x6_Format:
78+
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 5 ) / 6 ) * 16;
79+
case RGBA_ASTC_10x8_Format:
80+
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 7 ) / 8 ) * 16;
81+
case RGBA_ASTC_10x10_Format:
82+
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 9 ) / 10 ) * 16;
83+
case RGBA_ASTC_12x10_Format:
84+
return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 9 ) / 10 ) * 16;
85+
case RGBA_ASTC_12x12_Format:
86+
return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 11 ) / 12 ) * 16;
87+
88+
// https://registry.khronos.org/webgl/extensions/EXT_texture_compression_bptc/
89+
case RGBA_BPTC_Format:
90+
case RGB_BPTC_SIGNED_Format:
91+
case RGB_BPTC_UNSIGNED_Format:
92+
return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16;
93+
94+
// https://registry.khronos.org/webgl/extensions/EXT_texture_compression_rgtc/
95+
case RED_RGTC1_Format:
96+
case SIGNED_RED_RGTC1_Format:
97+
return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 8;
98+
case RED_GREEN_RGTC2_Format:
99+
case SIGNED_RED_GREEN_RGTC2_Format:
100+
return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16;
101+
102+
}
103+
104+
throw new Error(
105+
`Unable to determine texture byte length for ${format} format.`,
106+
);
107+
108+
}
109+
110+
function getTextureTypeByteLength( type ) {
111+
112+
switch ( type ) {
113+
114+
case UnsignedByteType:
115+
case ByteType:
116+
return { byteLength: 1, components: 1 };
117+
case UnsignedShortType:
118+
case ShortType:
119+
case HalfFloatType:
120+
return { byteLength: 2, components: 1 };
121+
case UnsignedShort4444Type:
122+
case UnsignedShort5551Type:
123+
return { byteLength: 2, components: 4 };
124+
case UnsignedIntType:
125+
case IntType:
126+
case FloatType:
127+
return { byteLength: 4, components: 1 };
128+
case UnsignedInt5999Type:
129+
return { byteLength: 4, components: 3 };
130+
131+
}
132+
133+
throw new Error( `Unknown texture type ${type}.` );
134+
135+
}
136+
137+
const TextureUtils = {
138+
getByteLength,
139+
};
140+
141+
export { getByteLength, TextureUtils };

0 commit comments

Comments
 (0)