Skip to content

Commit

Permalink
support all texture formats and types in `DataTextureArray.setLayerUp…
Browse files Browse the repository at this point in the history
…date` 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 <renaud.rohlinger@gmail.com>
  • Loading branch information
HunterLarco and RenaudRohlinger committed Jun 20, 2024
1 parent 01dc3d9 commit 9c00c1c
Show file tree
Hide file tree
Showing 7 changed files with 376 additions and 53 deletions.
28 changes: 28 additions & 0 deletions docs/api/en/extras/TextureUtils.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!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>
<h1>[name]</h1>

<p class="desc">A class containing utility functions for textures.</p>

<h2>Methods</h2>

<h3>[method:Number getByteLength]( [param:Number width], [param:Number height], [param:Number format], [param:Number type] )</h3>
<p>
Given the width, height, format, and type of a texture. Determines how
many bytes must be used to represent the texture.
</p>

<h2>Source</h2>

<p>
[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
</p>
</body>
</html>
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@
"webgl_simple_gi",
"webgl_texture2darray",
"webgl_texture2darray_compressed",
"webgl_texture2darray_layerupdate",
"webgl_texture3d",
"webgl_texture3d_partialupdate",
"webgl_ubo",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
191 changes: 191 additions & 0 deletions examples/webgl_texture2darray_layerupdate.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - texture array layer update</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;
attribute uint instancedIndex;
flat out uint diffuseIndex;
out vec2 vUv;

void main() {

vec3 translation = vec3(0, float(instancedIndex) * size.y - size.y, 0);
gl_Position = projectionMatrix * modelViewMatrix * vec4( position + translation, 1.0 );

diffuseIndex = instancedIndex;

// 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;
flat in uint diffuseIndex;

out vec4 outColor;

void main() {

outColor = texture( diffuse, vec3( vUv, diffuseIndex ) );

}
</script>
<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - 2D Compressed Texture Array Layer Updates<br />
Loop from the movie Spirited away
by the <a href="https://www.ghibli.jp/" target="_blank" rel="noopener">Studio Ghibli</a><br />
</div>

<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three';

import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';

let camera, scene, mesh, renderer;

const planeWidth = 20;
const planeHeight = 10;

init();

async 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();

// Configure the renderer.

renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );

// Configure the KTX2 loader.

const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath( 'jsm/libs/basis/' );
ktx2Loader.detectSupport( renderer );

// Load several KTX2 textures which will later be used to modify
// specific texture array layers.

const spiritedaway = await ktx2Loader.loadAsync( 'textures/spiritedaway.ktx2' );

// Create a texture array for rendering.

const layerByteLength = THREE.TextureUtils.getByteLength(
spiritedaway.image.width,
spiritedaway.image.height,
spiritedaway.format,
spiritedaway.type,
);

const textureArray = new THREE.CompressedArrayTexture( [
{
data: new Uint8Array( layerByteLength * 3 ),
width: spiritedaway.image.width,
height: spiritedaway.image.height,
}
], spiritedaway.image.width, spiritedaway.image.height, 3, spiritedaway.format, spiritedaway.type );

// Setup the GUI

const formData = {
srcLayer: 0,
destLayer: 0,
transfer() {
const layerElementLength = layerByteLength / spiritedaway.mipmaps[ 0 ].data.BYTES_PER_ELEMENT;
textureArray.mipmaps[ 0 ].data.set(
spiritedaway.mipmaps[ 0 ].data.subarray(
layerElementLength * ( formData.srcLayer % spiritedaway.image.depth ),
layerElementLength * ( ( formData.srcLayer % spiritedaway.image.depth ) + 1 ),
),
layerByteLength * formData.destLayer,
);
textureArray.addLayerUpdate( formData.destLayer );
textureArray.needsUpdate = true;

renderer.render( scene, camera );
},
};

const gui = new GUI();
gui.add(formData, 'srcLayer', 0, spiritedaway.image.depth - 1, 1);
gui.add(formData, 'destLayer', 0, textureArray.image.depth - 1, 1);
gui.add(formData, 'transfer');

/// Setup the scene.

const material = new THREE.ShaderMaterial( {
uniforms: {
diffuse: { value: textureArray },
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.InstancedBufferGeometry();
geometry.copy( new THREE.PlaneGeometry( planeWidth, planeHeight ) );
geometry.instanceCount = 3;

const instancedIndexAttribute = new THREE.InstancedBufferAttribute(
new Uint16Array( [ 0, 1, 2 ] ), 1, false, 1
);
instancedIndexAttribute.gpuType = THREE.IntType;
geometry.setAttribute( 'instancedIndex', instancedIndexAttribute );

mesh = new THREE.InstancedMesh( geometry, material, 3 );

scene.add( mesh );

window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

renderer.setSize( window.innerWidth, window.innerHeight );

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.render( scene, camera );

}

</script>
</body>
</html>
1 change: 1 addition & 0 deletions src/Three.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export { Curve } from './extras/core/Curve.js';
export { DataUtils } from './extras/DataUtils.js';
export { ImageUtils } from './extras/ImageUtils.js';
export { ShapeUtils } from './extras/ShapeUtils.js';
export { TextureUtils } from './extras/TextureUtils.js';
export { PMREMGenerator } from './extras/PMREMGenerator.js';
export { WebGLUtils } from './renderers/webgl/WebGLUtils.js';
export { createCanvasElement } from './utils.js';
Expand Down
141 changes: 141 additions & 0 deletions src/extras/TextureUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
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';

/**
* Given the width, height, format, and type of a texture. Determines how many
* bytes must be used to represent the texture.
*/
function getByteLength( width, height, format, type ) {

const typeByteLength = getTextureTypeByteLength( type );

switch ( format ) {

// https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml
case AlphaFormat:
return width * height;
case LuminanceFormat:
return width * height;
case LuminanceAlphaFormat:
return width * height * 2;
case RedFormat:
return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength;
case RedIntegerFormat:
return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength;
case RGFormat:
return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength;
case RGIntegerFormat:
return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength;
case RGBFormat:
return ( ( width * height * 3 ) / typeByteLength.components ) * typeByteLength.byteLength;
case RGBAFormat:
return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength;
case RGBAIntegerFormat:
return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength;

// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_s3tc_srgb/
case RGB_S3TC_DXT1_Format:
case RGBA_S3TC_DXT1_Format:
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8;
case RGBA_S3TC_DXT3_Format:
case RGBA_S3TC_DXT5_Format:
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16;

// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_pvrtc/
case RGB_PVRTC_2BPPV1_Format:
case RGBA_PVRTC_2BPPV1_Format:
return ( Math.max( width, 16 ) * Math.max( height, 8 ) ) / 4;
case RGB_PVRTC_4BPPV1_Format:
case RGBA_PVRTC_4BPPV1_Format:
return ( Math.max( width, 8 ) * Math.max( height, 8 ) ) / 2;

// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_etc/
case RGB_ETC1_Format:
case RGB_ETC2_Format:
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8;
case RGBA_ETC2_EAC_Format:
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16;

// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_astc/
case RGBA_ASTC_4x4_Format:
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16;
case RGBA_ASTC_5x4_Format:
return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 3 ) / 4 ) * 16;
case RGBA_ASTC_5x5_Format:
return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 4 ) / 5 ) * 16;
case RGBA_ASTC_6x5_Format:
return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 4 ) / 5 ) * 16;
case RGBA_ASTC_6x6_Format:
return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 5 ) / 6 ) * 16;
case RGBA_ASTC_8x5_Format:
return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 4 ) / 5 ) * 16;
case RGBA_ASTC_8x6_Format:
return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 5 ) / 6 ) * 16;
case RGBA_ASTC_8x8_Format:
return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 7 ) / 8 ) * 16;
case RGBA_ASTC_10x5_Format:
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 4 ) / 5 ) * 16;
case RGBA_ASTC_10x6_Format:
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 5 ) / 6 ) * 16;
case RGBA_ASTC_10x8_Format:
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 7 ) / 8 ) * 16;
case RGBA_ASTC_10x10_Format:
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 9 ) / 10 ) * 16;
case RGBA_ASTC_12x10_Format:
return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 9 ) / 10 ) * 16;
case RGBA_ASTC_12x12_Format:
return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 11 ) / 12 ) * 16;

// https://registry.khronos.org/webgl/extensions/EXT_texture_compression_bptc/
case RGBA_BPTC_Format:
case RGB_BPTC_SIGNED_Format:
case RGB_BPTC_UNSIGNED_Format:
return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16;

// https://registry.khronos.org/webgl/extensions/EXT_texture_compression_rgtc/
case RED_RGTC1_Format:
case SIGNED_RED_RGTC1_Format:
return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 8;
case RED_GREEN_RGTC2_Format:
case SIGNED_RED_GREEN_RGTC2_Format:
return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16;

}

throw new Error(
`Unable to determine texture byte length for ${format} format.`,
);

}

function getTextureTypeByteLength( type ) {

switch ( type ) {

case UnsignedByteType:
case ByteType:
return { byteLength: 1, components: 1 };
case UnsignedShortType:
case ShortType:
case HalfFloatType:
return { byteLength: 2, components: 1 };
case UnsignedShort4444Type:
case UnsignedShort5551Type:
return { byteLength: 2, components: 4 };
case UnsignedIntType:
case IntType:
case FloatType:
return { byteLength: 4, components: 1 };
case UnsignedInt5999Type:
return { byteLength: 4, components: 3 };

}

throw new Error( `Unknown texture type ${type}.` );

}

const TextureUtils = {
getByteLength,
};

export { getByteLength, TextureUtils };
Loading

0 comments on commit 9c00c1c

Please sign in to comment.