Skip to content

Commit 22a285a

Browse files
WebGPURenderer: Add copyTextureToTexture support (#27888)
* webgpu: add copytexturetotexture support * fix dst texture not update on the gpu
1 parent 73762af commit 22a285a

File tree

7 files changed

+241
-1
lines changed

7 files changed

+241
-1
lines changed

examples/files.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@
361361
"webgpu_portal",
362362
"webgpu_reflection",
363363
"webgpu_rtt",
364+
"webgpu_materials_texture_partialupdate",
364365
"webgpu_sandbox",
365366
"webgpu_shadertoy",
366367
"webgpu_shadowmap",

examples/jsm/renderers/common/Renderer.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,16 @@ class Renderer {
979979

980980
}
981981

982+
copyTextureToTexture( position, srcTexture, dstTexture, level = 0 ) {
983+
984+
this._textures.updateTexture( srcTexture );
985+
this._textures.updateTexture( dstTexture );
986+
987+
this.backend.copyTextureToTexture( position, srcTexture, dstTexture, level );
988+
989+
}
990+
991+
982992
readRenderTargetPixelsAsync( renderTarget, x, y, width, height ) {
983993

984994
return this.backend.copyTextureToBuffer( renderTarget.texture, x, y, width, height );

examples/jsm/renderers/webgl/WebGLBackend.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,12 @@ class WebGLBackend extends Backend {
11091109

11101110
}
11111111

1112+
copyTextureToTexture( position, srcTexture, dstTexture, level ) {
1113+
1114+
this.textureUtils.copyTextureToTexture( position, srcTexture, dstTexture, level );
1115+
1116+
}
1117+
11121118
copyFramebufferToTexture( texture, renderContext ) {
11131119

11141120
this.textureUtils.copyFramebufferToTexture( texture, renderContext );

examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,48 @@ class WebGLTextureUtils {
523523

524524
}
525525

526+
copyTextureToTexture( position, srcTexture, dstTexture, level = 0 ) {
527+
528+
const { gl, backend } = this;
529+
const { state } = this.backend;
530+
531+
const width = srcTexture.image.width;
532+
const height = srcTexture.image.height;
533+
const { textureGPU: dstTextureGPU, glTextureType, glType, glFormat } = backend.get( dstTexture );
534+
535+
state.bindTexture( glTextureType, dstTextureGPU );
536+
537+
// As another texture upload may have changed pixelStorei
538+
// parameters, make sure they are correct for the dstTexture
539+
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY );
540+
gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha );
541+
gl.pixelStorei( gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment );
542+
543+
if ( srcTexture.isDataTexture ) {
544+
545+
gl.texSubImage2D( gl.TEXTURE_2D, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data );
546+
547+
} else {
548+
549+
if ( srcTexture.isCompressedTexture ) {
550+
551+
gl.compressedTexSubImage2D( gl.TEXTURE_2D, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data );
552+
553+
} else {
554+
555+
gl.texSubImage2D( gl.TEXTURE_2D, level, position.x, position.y, glFormat, glType, srcTexture.image );
556+
557+
}
558+
559+
}
560+
561+
// Generate mipmaps only when copying level 0
562+
if ( level === 0 && dstTexture.generateMipmaps ) gl.generateMipmap( gl.TEXTURE_2D );
563+
564+
state.unbindTexture();
565+
566+
}
567+
526568
copyFramebufferToTexture( texture, renderContext ) {
527569

528570
const { gl } = this;

examples/jsm/renderers/webgpu/WebGPUBackend.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,7 @@ class WebGPUBackend extends Backend {
12361236

12371237
if ( ! this.adapter ) {
12381238

1239-
console.warn( 'WebGPUBackend: WebGPU adapter has not been initialized yet. Please use detectSupportAsync instead' );
1239+
console.warn( 'WebGPUBackend: WebGPU adapter has not been initialized yet. Please use hasFeatureAsync instead' );
12401240

12411241
return false;
12421242

@@ -1246,6 +1246,37 @@ class WebGPUBackend extends Backend {
12461246

12471247
}
12481248

1249+
copyTextureToTexture( position, srcTexture, dstTexture, level = 0 ) {
1250+
1251+
const encoder = this.device.createCommandEncoder( { label: 'copyTextureToTexture_' + srcTexture.id + '_' + dstTexture.id } );
1252+
1253+
const sourceGPU = this.get( srcTexture ).texture;
1254+
const destinationGPU = this.get( dstTexture ).texture;
1255+
1256+
encoder.copyTextureToTexture(
1257+
{
1258+
texture: sourceGPU,
1259+
mipLevel: level,
1260+
origin: { x: 0, y: 0, z: 0 }
1261+
},
1262+
{
1263+
texture: destinationGPU,
1264+
mipLevel: level,
1265+
origin: { x: position.x, y: position.y, z: position.z }
1266+
},
1267+
[
1268+
srcTexture.image.width,
1269+
srcTexture.image.height
1270+
]
1271+
);
1272+
1273+
this.device.queue.submit( [ encoder.finish() ] );
1274+
1275+
}
1276+
1277+
1278+
1279+
12491280
copyFramebufferToTexture( texture, renderContext ) {
12501281

12511282
const renderContextData = this.get( renderContext );
8.6 KB
Loading
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>three.js webgl - texture - webgpu partial 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+
<body>
10+
11+
<div id="info">
12+
<a href="https://threejs.org" target="_blank" rel="noopener noreferrer">three.js</a> - partial webgpu texture update <br/>
13+
replace parts of an existing texture with all data of another texture
14+
</div>
15+
16+
<script type="importmap">
17+
{
18+
"imports": {
19+
"three": "../build/three.module.js",
20+
"three/addons/": "./jsm/",
21+
"three/nodes": "./jsm/nodes/Nodes.js"
22+
}
23+
}
24+
</script>
25+
26+
<script type="module">
27+
28+
import * as THREE from 'three';
29+
import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
30+
31+
let camera, scene, renderer, clock, dataTexture, diffuseMap;
32+
33+
let last = 0;
34+
const position = new THREE.Vector2();
35+
const color = new THREE.Color();
36+
37+
init();
38+
39+
function init() {
40+
41+
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
42+
camera.position.z = 2;
43+
44+
scene = new THREE.Scene();
45+
46+
clock = new THREE.Clock();
47+
48+
const loader = new THREE.TextureLoader();
49+
diffuseMap = loader.load( 'textures/carbon/Carbon.png', animate );
50+
diffuseMap.colorSpace = THREE.SRGBColorSpace;
51+
diffuseMap.minFilter = THREE.LinearFilter;
52+
diffuseMap.generateMipmaps = false;
53+
54+
const geometry = new THREE.PlaneGeometry( 2, 2 );
55+
const material = new THREE.MeshBasicMaterial( { map: diffuseMap } );
56+
57+
const mesh = new THREE.Mesh( geometry, material );
58+
scene.add( mesh );
59+
60+
//
61+
62+
const width = 32;
63+
const height = 32;
64+
65+
const data = new Uint8Array( width * height * 4 );
66+
dataTexture = new THREE.DataTexture( data, width, height );
67+
68+
//
69+
70+
renderer = new WebGPURenderer( { antialias: true, forceWebGL: false } );
71+
renderer.setPixelRatio( window.devicePixelRatio );
72+
renderer.setSize( window.innerWidth, window.innerHeight );
73+
74+
document.body.appendChild( renderer.domElement );
75+
76+
//
77+
78+
window.addEventListener( 'resize', onWindowResize );
79+
80+
}
81+
82+
function onWindowResize() {
83+
84+
camera.aspect = window.innerWidth / window.innerHeight;
85+
camera.updateProjectionMatrix();
86+
87+
renderer.setSize( window.innerWidth, window.innerHeight );
88+
89+
}
90+
91+
async function animate() {
92+
93+
requestAnimationFrame( animate );
94+
95+
const elapsedTime = clock.getElapsedTime();
96+
97+
98+
await renderer.renderAsync( scene, camera );
99+
100+
101+
if ( elapsedTime - last > 0.1 ) {
102+
103+
last = elapsedTime;
104+
105+
position.x = ( 32 * THREE.MathUtils.randInt( 1, 16 ) ) - 32;
106+
position.y = ( 32 * THREE.MathUtils.randInt( 1, 16 ) ) - 32;
107+
108+
// generate new color data
109+
updateDataTexture( dataTexture );
110+
111+
// perform copy from src to dest texture to a random position
112+
113+
renderer.copyTextureToTexture( position, dataTexture, diffuseMap );
114+
115+
}
116+
117+
}
118+
119+
function updateDataTexture( texture ) {
120+
121+
const size = texture.image.width * texture.image.height;
122+
const data = texture.image.data;
123+
124+
// generate a random color and update texture data
125+
126+
color.setHex( Math.random() * 0xffffff );
127+
128+
const r = Math.floor( color.r * 255 );
129+
const g = Math.floor( color.g * 255 );
130+
const b = Math.floor( color.b * 255 );
131+
132+
for ( let i = 0; i < size; i ++ ) {
133+
134+
const stride = i * 4;
135+
136+
data[ stride ] = r;
137+
data[ stride + 1 ] = g;
138+
data[ stride + 2 ] = b;
139+
data[ stride + 3 ] = 1;
140+
141+
}
142+
143+
texture.needsUpdate = true;
144+
145+
}
146+
147+
</script>
148+
149+
</body>
150+
</html>

0 commit comments

Comments
 (0)