Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGPURenderer: Support Compressed Texture Array #29231

Merged
merged 3 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,8 @@
"webgpu_volume_cloud",
"webgpu_volume_perlin",
"webgpu_water",
"webgpu_performance"
"webgpu_performance",
"webgpu_texture2darray_compressed"
],
"webaudio": [
"webaudio_orientation",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
129 changes: 129 additions & 0 deletions examples/webgpu_texture2darray_compressed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<!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>

<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - WebGPU - 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>

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

<script type="module">

import * as THREE from 'three';

import { texture, uniform, uv } from 'three/tsl';

import Stats from 'three/addons/libs/stats.module.js';
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';

let camera, scene, mesh, renderer, stats, clock;

const depth = uniform( 0 );

const planeWidth = 50;
const planeHeight = 25;

let depthStep = 1;

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

//
clock = new THREE.Clock();

renderer = new THREE.WebGPURenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
container.appendChild( renderer.domElement );

//

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

ktx2Loader.load( 'textures/spiritedaway.ktx2', function ( texturearray ) {

const material = new THREE.NodeMaterial();

material.colorNode = texture( texturearray, uv().flipY() ).depth( depth );
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() {

if ( mesh ) {

const delta = clock.getDelta() * 10;

depthStep += delta;

const value = depthStep % 5;

depth.value = value;

}

render();
stats.update();

}

function render() {

renderer.render( scene, camera );

}

</script>
</body>
</html>
2 changes: 1 addition & 1 deletion src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ ${ flowData.code }

snippet = `sampler2DShadow ${ uniform.name };`;

} else if ( texture.isDataArrayTexture === true ) {
} else if ( texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) {

snippet = `${typePrefix}sampler2DArray ${ uniform.name };`;

Expand Down
9 changes: 5 additions & 4 deletions src/renderers/webgl-fallback/utils/WebGLTextureUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ class WebGLTextureUtils {

glTextureType = gl.TEXTURE_CUBE_MAP;

} else if ( texture.isDataArrayTexture === true ) {
} else if ( texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) {

glTextureType = gl.TEXTURE_2D_ARRAY;

} else if ( texture.isData3DTexture === true ) {
} else if ( texture.isData3DTexture === true ) { // TODO: isCompressed3DTexture, wait for #26642

glTextureType = gl.TEXTURE_3D;

Expand Down Expand Up @@ -340,7 +340,7 @@ class WebGLTextureUtils {

this.setTextureParameters( glTextureType, texture );

if ( texture.isDataArrayTexture ) {
if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) {

gl.texStorage3D( gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, width, height, depth );

Expand Down Expand Up @@ -429,14 +429,14 @@ class WebGLTextureUtils {
if ( texture.isCompressedTexture ) {

const mipmaps = texture.mipmaps;
const image = options.image;

for ( let i = 0; i < mipmaps.length; i ++ ) {

const mipmap = mipmaps[ i ];

if ( texture.isCompressedArrayTexture ) {

const image = options.image;

if ( texture.format !== gl.RGBA ) {

Expand Down Expand Up @@ -473,6 +473,7 @@ class WebGLTextureUtils {

}


} else if ( texture.isCubeTexture ) {

const images = options.images;
Expand Down
2 changes: 1 addition & 1 deletion src/renderers/webgpu/nodes/WGSLNodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ ${ flowData.code }

textureType = 'texture_cube<f32>';

} else if ( texture.isDataArrayTexture === true ) {
} else if ( texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) {

textureType = 'texture_2d_array<f32>';

Expand Down
4 changes: 2 additions & 2 deletions src/renderers/webgpu/utils/WebGPUBindingUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class WebGPUBindingUtils {

texture.viewDimension = GPUTextureViewDimension.Cube;

} else if ( binding.texture.isDataArrayTexture ) {
} else if ( binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) {

texture.viewDimension = GPUTextureViewDimension.TwoDArray;

Expand Down Expand Up @@ -244,7 +244,7 @@ class WebGPUBindingUtils {

dimensionViewGPU = GPUTextureViewDimension.ThreeD;

} else if ( binding.texture.isDataArrayTexture ) {
} else if ( binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) {

dimensionViewGPU = GPUTextureViewDimension.TwoDArray;

Expand Down
47 changes: 28 additions & 19 deletions src/renderers/webgpu/utils/WebGPUTextureUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class WebGPUTextureUtils {

}

if ( texture.isCompressedTexture !== true ) {
if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true ) {

usage |= GPUTextureUsage.RENDER_ATTACHMENT;

Expand Down Expand Up @@ -341,7 +341,7 @@ class WebGPUTextureUtils {

}

} else if ( texture.isCompressedTexture ) {
} else if ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) {

this._copyCompressedBufferToTexture( texture.mipmaps, textureData.texture, textureDescriptorGPU );

Expand Down Expand Up @@ -605,32 +605,41 @@ class WebGPUTextureUtils {
const device = this.backend.device;

const blockData = this._getBlockData( textureDescriptorGPU.format );
const isTextureArray = textureDescriptorGPU.size.depthOrArrayLayers > 1;

for ( let i = 0; i < mipmaps.length; i ++ ) {

const mipmap = mipmaps[ i ];

const width = mipmap.width;
const height = mipmap.height;
const depth = isTextureArray ? textureDescriptorGPU.size.depthOrArrayLayers : 1;

const bytesPerRow = Math.ceil( width / blockData.width ) * blockData.byteLength;
const bytesPerImage = bytesPerRow * Math.ceil( height / blockData.height );

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

device.queue.writeTexture(
{
texture: textureGPU,
mipLevel: i,
origin: { x: 0, y: 0, z: j }
},
mipmap.data,
{
offset: j * bytesPerImage,
bytesPerRow,
rowsPerImage: Math.ceil( height / blockData.height )
},
{
width: Math.ceil( width / blockData.width ) * blockData.width,
height: Math.ceil( height / blockData.height ) * blockData.height,
depthOrArrayLayers: 1
}
);

device.queue.writeTexture(
{
texture: textureGPU,
mipLevel: i
},
mipmap.data,
{
offset: 0,
bytesPerRow
},
{
width: Math.ceil( width / blockData.width ) * blockData.width,
height: Math.ceil( height / blockData.width ) * blockData.width,
depthOrArrayLayers: 1
}
);
}

}

Expand Down Expand Up @@ -843,7 +852,7 @@ export function getFormat( texture, device = null ) {

formatGPU = GPUTextureFormat.BGRA8Unorm;

} else if ( texture.isCompressedTexture === true ) {
} else if ( texture.isCompressedTexture === true || texture.isCompressedArrayTexture === true ) {

switch ( format ) {

Expand Down