Skip to content

Commit 67285f1

Browse files
committed
Add performance test for 2d array upload
1 parent 0643f15 commit 67285f1

File tree

4 files changed

+282
-19
lines changed

4 files changed

+282
-19
lines changed

immersive-vr-session.html

Lines changed: 182 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,30 +47,159 @@
4747
<p>Click 'Enter XR' to see content</p>
4848
</main>
4949
<script type="module">
50+
import {mat4, vec3, quat} from './js/render/math/gl-matrix.js';
51+
5052
import {WebXRButton} from './js/util/webxr-button.js';
53+
import { Node } from './js/render/core/node.js';
54+
import {ArrayTexture, Custom2DTexture} from './js/render/core/texture.js';
55+
5156
import {Scene} from './js/render/scenes/scene.js';
5257
import {Renderer, createWebGLContext} from './js/render/core/renderer.js';
5358
import {Gltf2Node} from './js/render/nodes/gltf2.js';
5459
import {SkyboxNode} from './js/render/nodes/skybox.js';
5560
import {QueryArgs} from './js/util/query-args.js';
61+
import {BoxBuilder} from './js/render/geometry/box-builder.js';
62+
import {PbrMaterial} from './js/render/materials/pbr.js';
5663

5764
// If requested, use the polyfill to provide support for mobile devices
5865
// and devices which only support WebVR.
59-
import WebXRPolyfill from './js/third-party/webxr-polyfill/build/webxr-polyfill.module.js';
60-
if (QueryArgs.getBool('usePolyfill', true)) {
61-
let polyfill = new WebXRPolyfill();
66+
// import WebXRPolyfill from './js/third-party/webxr-polyfill/build/webxr-polyfill.module.js';
67+
// if (QueryArgs.getBool('usePolyfill', true)) {
68+
// // let polyfill = new WebXRPolyfill();
69+
// }
70+
71+
function rand(min, max) {
72+
return Math.floor(Math.random() * (max - min + 1)) + min;
73+
}
74+
75+
function randomColorString() {
76+
const r = rand(50, 255);
77+
const g = rand(50, 255);
78+
const b = rand(50, 255);
79+
return `rgb(${r},${g},${b})`;
6280
}
6381

6482
// XR globals.
6583
let xrButton = null;
6684
let xrRefSpace = null;
6785

86+
/* @test: 2d canvas setup */
87+
88+
const test = {
89+
multipleUpload: true,
90+
multipleTextures: false,
91+
useMultipleCanvases: false,
92+
useTexture2D: false,
93+
canvasSize: [512, 512],
94+
atlasSize: 1024,
95+
layers: 1
96+
};
97+
98+
const textures = new Array(2)
99+
.fill()
100+
.map(_ => {
101+
if(test.useTexture2D) {
102+
return new Custom2DTexture(test.atlasSize, test.atlasSize);
103+
}
104+
return new ArrayTexture(test.atlasSize, test.atlasSize, test.layers);
105+
});
106+
107+
const canvases = new Array(2)
108+
.fill()
109+
.map(_ => {
110+
const canvas = document.createElement('canvas');
111+
canvas.width = test.canvasSize[0];
112+
canvas.height = test.canvasSize[1];
113+
canvas.style.background = '#FF0000';
114+
return canvas;
115+
});
116+
68117
// WebGL scene globals.
69118
let gl = null;
70119
let renderer = null;
71120
let scene = new Scene();
72121
scene.addNode(new Gltf2Node({url: 'media/gltf/space/space.gltf'}));
73122
scene.addNode(new SkyboxNode({url: 'media/textures/milky-way-4k.png'}));
123+
const boxMaterial = new PbrMaterial();
124+
boxMaterial.baseColorFactor.value = [1.0, 1.0, 1.0, 1.0];
125+
boxMaterial.baseColor.texture = textures[0];
126+
boxMaterial.arrayLayer.value = 5.0;
127+
128+
function uploadTextureLayer(texture, layer, img, offsets = [0, 0]) {
129+
const textureGL = renderer._getRenderTexture(texture);
130+
if(test.useTexture2D) {
131+
gl.bindTexture(gl.TEXTURE_2D, textureGL._texture);
132+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
133+
gl.texSubImage2D(
134+
gl.TEXTURE_2D,
135+
0,
136+
offsets[0],
137+
offsets[1],
138+
img.width,
139+
img.height,
140+
gl.RGBA,
141+
gl.UNSIGNED_BYTE,
142+
img
143+
);
144+
} else {
145+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, textureGL._texture);
146+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
147+
gl.texSubImage3D(
148+
gl.TEXTURE_2D_ARRAY,
149+
0,
150+
offsets[0],
151+
offsets[1],
152+
layer,
153+
img.width,
154+
img.height,
155+
1,
156+
gl.RGBA,
157+
gl.UNSIGNED_BYTE,
158+
img
159+
);
160+
}
161+
}
162+
163+
function updateCanvas(canvas) {
164+
const ctx = canvas.getContext('2d');
165+
const numSquares = 30;
166+
ctx.clearRect(0, 0, canvas.width, canvas.height);
167+
for (let i = 0; i < numSquares; i++) {
168+
const size = rand(30, 150);
169+
const x = rand(0, canvas.width - size);
170+
const y = rand(0, canvas.height - size);
171+
ctx.fillStyle = randomColorString();
172+
ctx.fillRect(x, y, size, size);
173+
}
174+
}
175+
176+
function initCanvasTexture() {
177+
const boxBuilder = new BoxBuilder();
178+
boxBuilder.pushCube([0, 0, 0], 5.0);
179+
const boxPrimitive = boxBuilder.finishPrimitive(renderer);
180+
181+
const boxRenderPrimitive = renderer.createRenderPrimitive(boxPrimitive, boxMaterial);
182+
const node = new Node();
183+
node.addRenderPrimitive(boxRenderPrimitive);
184+
mat4.translate(node.matrix, node.matrix, [-10, 0, -10]);
185+
186+
scene.addNode(node);
187+
188+
const canvas = canvases[0];
189+
const ctx = canvas.getContext('2d');
190+
for(let layer = 0; layer < test.layers; ++layer) {
191+
const r = Math.floor(layer*25);
192+
ctx.fillStyle = `rgb(${r},${0},${0})`;
193+
ctx.fillRect(0, 0, canvas.width, canvas.height);
194+
uploadTextureLayer(textures[0], layer, canvas);
195+
196+
if(test.multipleTextures) {
197+
ctx.fillStyle = `rgb(${0},${r},${0})`;
198+
ctx.fillRect(0, 0, canvas.width, canvas.height);
199+
uploadTextureLayer(textures[1], layer, canvas);
200+
}
201+
}
202+
}
74203

75204
// Checks to see if WebXR is available and, if so, queries a list of
76205
// XRDevices that are connected to the system.
@@ -101,7 +230,7 @@
101230

102231
// Called when we've successfully acquired a XRSession. In response we
103232
// will set up the necessary session state and kick off the frame loop.
104-
function onSessionStarted(session) {
233+
async function onSessionStarted(session) {
105234
// This informs the 'Enter XR' button that the session has started and
106235
// that it should display 'Exit XR' instead.
107236
xrButton.setSession(session);
@@ -113,8 +242,22 @@
113242
// Create a WebGL context to render with, initialized to be compatible
114243
// with the XRDisplay we're presenting to.
115244
gl = createWebGLContext({
116-
xrCompatible: true
245+
"alpha": true,
246+
"depth": true,
247+
"stencil": true,
248+
"antialias": true,
249+
"premultipliedAlpha": false,
250+
"preserveDrawingBuffer": false,
251+
"powerPreference": "default",
252+
"failIfMajorPerformanceCaveat": false,
253+
"majorVersion": 2,
254+
"minorVersion": 0,
255+
"enableExtensionsByDefault": 1,
256+
"explicitSwapControl": 0,
257+
"proxyContextToMainThread": 0,
258+
"renderViaOffscreenBackBuffer": 0
117259
});
260+
await gl.makeXRCompatible();
118261

119262
// Create a renderer with that GL context (this is just for the samples
120263
// framework and has nothing to do with WebXR specifically.)
@@ -137,6 +280,9 @@
137280
// Inform the session that we're ready to begin drawing.
138281
session.requestAnimationFrame(onXRFrame);
139282
});
283+
284+
/* @test */
285+
initCanvasTexture(renderer);
140286
}
141287

142288
// Called when the user clicks the 'Exit XR' button. In response we end
@@ -157,13 +303,44 @@
157303
renderer = null;
158304
}
159305

306+
let currentLayerToUpdate = 0;
307+
160308
// Called every time the XRSession requests that a new frame be drawn.
161309
function onXRFrame(t, frame) {
162310
let session = frame.session;
163311

164312
// Per-frame scene setup. Nothing WebXR specific here.
165313
scene.startFrame();
166314

315+
/* Upload canvas to texture */
316+
317+
updateCanvas(canvases[0]);
318+
if(test.useMultipleCanvases) {
319+
updateCanvas(canvases[1]);
320+
}
321+
322+
let currentTexture = textures[0];
323+
let nextTexture = textures[0];
324+
if(test.multipleTextures) {
325+
nextTexture = textures[1];
326+
if (boxMaterial.baseColor.texture === textures[0]) {
327+
currentTexture = textures[1];
328+
nextTexture = textures[0];
329+
}
330+
}
331+
boxMaterial.baseColor.texture = currentTexture;
332+
333+
// console.log(`Drawing to image ${nextTexture.textureKey}, displaying image ${boxMaterial.baseColor.texture.textureKey}`);
334+
335+
uploadTextureLayer(nextTexture, currentLayerToUpdate++, canvases[0]);
336+
currentLayerToUpdate = currentLayerToUpdate%test.layers;
337+
if(test.multipleUpload) {
338+
const canvas = test.useMultipleCanvases ? canvases[1] : canvases[0];
339+
const offsets = [canvas.width*0.5, canvas.height*0.5];
340+
uploadTextureLayer(nextTexture, currentLayerToUpdate, canvas, offsets);
341+
currentLayerToUpdate = currentLayerToUpdate%test.layers;
342+
}
343+
167344
// Inform the session that we're ready for the next frame.
168345
session.requestAnimationFrame(onXRFrame);
169346

js/render/core/renderer.js

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import {CAP, MAT_STATE, RENDER_ORDER, stateToBlendFunc} from './material.js';
2222
import {Node} from './node.js';
2323
import {Program} from './program.js';
24-
import {DataTexture, ExternalTexture, VideoTexture} from './texture.js';
24+
import {ArrayTexture, Custom2DTexture, DataTexture, ExternalTexture, VideoTexture} from './texture.js';
2525
import {mat4, vec3} from '../math/gl-matrix.js';
2626

2727
export const ATTRIB = {
@@ -1042,19 +1042,31 @@ export class Renderer {
10421042
let renderTexture = new RenderTexture(textureHandle);
10431043
this._textureCache[key] = renderTexture;
10441044

1045+
const target = texture instanceof ArrayTexture ? gl.TEXTURE_2D_ARRAY : gl.TEXTURE_2D;
1046+
10451047
if (texture instanceof ExternalTexture) {
10461048
renderTexture._isExternalTexture = true;
1049+
} else if (texture instanceof ArrayTexture) {
1050+
gl.bindTexture(target, textureHandle);
1051+
gl.texStorage3D(target, 1, gl.RGBA8, texture.width, texture.height, texture.depth);
1052+
this._setSamplerParameters(texture, target);
1053+
renderTexture._complete = true;
1054+
} else if (texture instanceof Custom2DTexture) {
1055+
gl.bindTexture(target, textureHandle);
1056+
gl.texStorage2D(target, 1, gl.RGBA8, texture.width, texture.height);
1057+
this._setSamplerParameters(texture, target);
1058+
renderTexture._complete = true;
10471059
} else if (texture instanceof DataTexture) {
10481060
gl.bindTexture(gl.TEXTURE_2D, textureHandle);
10491061
gl.texImage2D(gl.TEXTURE_2D, 0, texture.format, texture.width, texture.height,
10501062
0, texture.format, texture._type, texture._data);
1051-
this._setSamplerParameters(texture);
1063+
this._setSamplerParameters(texture, target);
10521064
renderTexture._complete = true;
10531065
} else {
10541066
texture.waitForComplete().then(() => {
10551067
gl.bindTexture(gl.TEXTURE_2D, textureHandle);
10561068
gl.texImage2D(gl.TEXTURE_2D, 0, texture.format, texture.format, gl.UNSIGNED_BYTE, texture.source);
1057-
this._setSamplerParameters(texture);
1069+
this._setSamplerParameters(texture, target);
10581070
renderTexture._complete = true;
10591071

10601072
if (texture instanceof VideoTexture) {
@@ -1076,24 +1088,24 @@ export class Renderer {
10761088
}
10771089
}
10781090

1079-
_setSamplerParameters(texture) {
1091+
_setSamplerParameters(texture, target) {
10801092
let gl = this._gl;
10811093

10821094
let sampler = texture.sampler;
10831095
let powerOfTwo = isPowerOfTwo(texture.width) && isPowerOfTwo(texture.height);
10841096
let mipmap = powerOfTwo && texture.mipmap;
10851097
if (mipmap) {
1086-
gl.generateMipmap(gl.TEXTURE_2D);
1098+
gl.generateMipmap(target);
10871099
}
10881100

10891101
let minFilter = sampler.minFilter || (mipmap ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
10901102
let wrapS = sampler.wrapS || (powerOfTwo ? gl.REPEAT : gl.CLAMP_TO_EDGE);
10911103
let wrapT = sampler.wrapT || (powerOfTwo ? gl.REPEAT : gl.CLAMP_TO_EDGE);
10921104

1093-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, sampler.magFilter || gl.LINEAR);
1094-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
1095-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapS);
1096-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapT);
1105+
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, sampler.magFilter || gl.LINEAR);
1106+
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minFilter);
1107+
gl.texParameteri(target, gl.TEXTURE_WRAP_S, wrapS);
1108+
gl.texParameteri(target, gl.TEXTURE_WRAP_T, wrapT);
10971109
}
10981110

10991111
_getProgramKey(name, defines) {

0 commit comments

Comments
 (0)