Skip to content

Commit 4c16f9a

Browse files
mvaligurskyMartin Valigursky
andauthored
Partial cubemap support for WebGPU (playcanvas#4911)
Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
1 parent 61578a9 commit 4c16f9a

File tree

8 files changed

+147
-73
lines changed

8 files changed

+147
-73
lines changed

scripts/utils/cubemap-renderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ CubemapRenderer.prototype.initialize = function () {
4646

4747
// Create cubemap render target with specified resolution and mipmap generation
4848
this.cubeMap = new pc.Texture(this.app.graphicsDevice, {
49+
name: this.entity.name + ':CubemapRenderer-' + resolution,
4950
width: resolution,
5051
height: resolution,
5152
format: pc.PIXELFORMAT_RGBA8,

src/platform/graphics/bind-group-format.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import { TRACEID_BINDGROUPFORMAT_ALLOC } from '../../core/constants.js';
22
import { Debug, DebugHelper } from '../../core/debug.js';
33

4-
import { TEXTUREDIMENSION_2D, SAMPLETYPE_FLOAT, SAMPLETYPE_DEPTH } from './constants.js';
4+
import {
5+
TEXTUREDIMENSION_2D, TEXTUREDIMENSION_CUBE, TEXTUREDIMENSION_3D,
6+
SAMPLETYPE_FLOAT, SAMPLETYPE_DEPTH
7+
} from './constants.js';
58

69
let id = 0;
710

11+
const textureDimensionInfo = {
12+
[TEXTUREDIMENSION_2D]: 'texture2D',
13+
[TEXTUREDIMENSION_CUBE]: 'textureCube',
14+
[TEXTUREDIMENSION_3D]: 'texture3D'
15+
};
16+
817
/**
918
* @ignore
1019
*/
@@ -112,11 +121,13 @@ class BindGroupFormat {
112121
let bindIndex = this.bufferFormats.length;
113122
this.textureFormats.forEach((format) => {
114123

115-
// TODO: suport different types of textures and samplers
116-
Debug.assert(format.textureDimension === TEXTUREDIMENSION_2D);
117-
Debug.assert(format.sampleType !== SAMPLETYPE_DEPTH);
124+
const textureType = textureDimensionInfo[format.textureDimension];
125+
Debug.assert(textureType, "Unsupported texture type");
126+
127+
// TODO: suport depth samplers
128+
Debug.assert(format.sampleType !== SAMPLETYPE_DEPTH, format);
118129

119-
code += `layout(set = ${bindGroup}, binding = ${bindIndex++}) uniform texture2D ${format.name};\n` +
130+
code += `layout(set = ${bindGroup}, binding = ${bindIndex++}) uniform ${textureType} ${format.name};\n` +
120131
`layout(set = ${bindGroup}, binding = ${bindIndex++}) uniform sampler ${format.name}_sampler;\n`;
121132
});
122133

src/platform/graphics/shader-chunks/frag/webgpu.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ layout(location = 0) out highp vec4 pc_fragColor;
99
#define texture2D(res, uv) texture(sampler2D(res, res ## _sampler), uv)
1010
#define texture2DBias(res, uv, bias) texture(sampler2D(res, res ## _sampler), uv, bias)
1111
#define texture2DLodEXT(res, uv, lod) textureLod(sampler2D(res, res ## _sampler), uv, lod)
12+
#define textureCube(res, uv) texture(samplerCube(res, res ## _sampler), uv)
1213
1314
// TODO: implement other texture sampling macros
14-
// #define textureCube texture
1515
// #define texture2DProj textureProj
1616
// #define texture2DProjLodEXT textureProjLod
1717
// #define textureCubeLodEXT textureLod

src/platform/graphics/shader-utils.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class ShaderUtils {
7575
// fragment code
7676
const fragDefines = options.fragmentDefines || getDefines(webgpuFS, gles3FS, gles2FS, false);
7777
const fragCode = (options.fragmentPreamble || '') +
78-
ShaderUtils.versionCode(device) +
78+
ShaderUtils.versionCode(device) +
7979
ShaderUtils.precisionCode(device) + '\n' +
8080
fragDefines +
8181
sharedFS +
@@ -134,27 +134,32 @@ class ShaderUtils {
134134

135135
let code = '';
136136

137-
if (device.deviceType === DEVICETYPE_WEBGL) {
137+
if (forcePrecision && forcePrecision !== 'highp' && forcePrecision !== 'mediump' && forcePrecision !== 'lowp') {
138+
forcePrecision = null;
139+
}
138140

139-
if (forcePrecision && forcePrecision !== 'highp' && forcePrecision !== 'mediump' && forcePrecision !== 'lowp') {
140-
forcePrecision = null;
141+
if (forcePrecision) {
142+
if (forcePrecision === 'highp' && device.maxPrecision !== 'highp') {
143+
forcePrecision = 'mediump';
141144
}
142-
143-
if (forcePrecision) {
144-
if (forcePrecision === 'highp' && device.maxPrecision !== 'highp') {
145-
forcePrecision = 'mediump';
146-
}
147-
if (forcePrecision === 'mediump' && device.maxPrecision === 'lowp') {
148-
forcePrecision = 'lowp';
149-
}
145+
if (forcePrecision === 'mediump' && device.maxPrecision === 'lowp') {
146+
forcePrecision = 'lowp';
150147
}
148+
}
149+
150+
const precision = forcePrecision ? forcePrecision : device.precision;
151+
152+
if (device.deviceType === DEVICETYPE_WEBGL) {
151153

152-
const precision = forcePrecision ? forcePrecision : device.precision;
153154
code = `precision ${precision} float;\n`;
154155

155156
if (device.webgl2) {
156157
code += `precision ${precision} sampler2DShadow;\n`;
157158
}
159+
160+
} else { // WebGPU
161+
162+
code = `precision ${precision} float;\nprecision ${precision} int;\n`;
158163
}
159164

160165
return code;

src/platform/graphics/webgpu/webgpu-graphics-device.js

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,13 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
7373
}
7474

7575
initDeviceCaps() {
76-
this.precision = 'hiphp';
76+
this.precision = 'highp';
77+
this.maxPrecision = 'highp';
7778
this.maxSamples = 4;
7879
this.maxTextures = 16;
80+
this.maxTextureSize = 4096;
81+
this.maxCubeMapSize = 4096;
82+
this.maxVolumeSize = 2048;
7983
this.maxPixelRatio = 1;
8084
this.supportsInstancing = true;
8185
this.supportsUniformBuffers = true;
@@ -88,7 +92,6 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
8892
this.extTextureHalfFloat = true;
8993
this.textureHalfFloatRenderable = true;
9094
this.textureHalfFloatUpdatable = true;
91-
this.maxTextureSize = 4096;
9295
this.boneLimit = 1024;
9396
this.supportsImageBitmap = true;
9497
this.extStandardDerivatives = true;
@@ -236,34 +239,36 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
236239

237240
draw(primitive, numInstances = 1, keepBuffers) {
238241

239-
const passEncoder = this.passEncoder;
240-
241-
// vertex buffers
242-
const vb0 = this.vertexBuffers[0];
243-
const vbSlot = this.submitVertexBuffer(vb0, 0);
244-
const vb1 = this.vertexBuffers[1];
245-
if (vb1) {
246-
this.submitVertexBuffer(vb1, vbSlot);
247-
}
248-
this.vertexBuffers.length = 0;
249-
250-
// render pipeline
251-
const pipeline = this.renderPipeline.get(primitive, vb0.format, vb1?.format, this.shader, this.renderTarget,
252-
this.bindGroupFormats, this.renderState);
253-
Debug.assert(pipeline);
254-
255-
if (this.pipeline !== pipeline) {
256-
this.pipeline = pipeline;
257-
passEncoder.setPipeline(pipeline);
258-
}
259-
260-
// draw
261-
const ib = this.indexBuffer;
262-
if (ib) {
263-
passEncoder.setIndexBuffer(ib.impl.buffer, ib.impl.format);
264-
passEncoder.drawIndexed(ib.numIndices, numInstances, 0, 0, 0);
265-
} else {
266-
passEncoder.draw(vb0.numVertices, numInstances, 0, 0);
242+
if (this.shader.ready) {
243+
const passEncoder = this.passEncoder;
244+
245+
// vertex buffers
246+
const vb0 = this.vertexBuffers[0];
247+
const vbSlot = this.submitVertexBuffer(vb0, 0);
248+
const vb1 = this.vertexBuffers[1];
249+
if (vb1) {
250+
this.submitVertexBuffer(vb1, vbSlot);
251+
}
252+
this.vertexBuffers.length = 0;
253+
254+
// render pipeline
255+
const pipeline = this.renderPipeline.get(primitive, vb0.format, vb1?.format, this.shader, this.renderTarget,
256+
this.bindGroupFormats, this.renderState);
257+
Debug.assert(pipeline);
258+
259+
if (this.pipeline !== pipeline) {
260+
this.pipeline = pipeline;
261+
passEncoder.setPipeline(pipeline);
262+
}
263+
264+
// draw
265+
const ib = this.indexBuffer;
266+
if (ib) {
267+
passEncoder.setIndexBuffer(ib.impl.buffer, ib.impl.format);
268+
passEncoder.drawIndexed(ib.numIndices, numInstances, 0, 0, 0);
269+
} else {
270+
passEncoder.draw(vb0.numVertices, numInstances, 0, 0);
271+
}
267272
}
268273
}
269274

@@ -343,12 +348,14 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
343348

344349
// create a new encoder for each pass to keep the GPU busy with commands
345350
this.commandEncoder = this.wgpu.createCommandEncoder();
351+
DebugHelper.setLabel(this.commandEncoder, renderPass.name);
346352

347353
// clear cached encoder state
348354
this.pipeline = null;
349355

350356
// start the pass
351357
this.passEncoder = this.commandEncoder.beginRenderPass(wrt.renderPassDescriptor);
358+
DebugHelper.setLabel(this.passEncoder, renderPass.name);
352359
}
353360

354361
/**

src/platform/graphics/webgpu/webgpu-render-target.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,19 @@ class WebgpuRenderTarget {
159159
this.renderPassDescriptor.colorAttachments = [colorAttachment];
160160

161161
const colorBuffer = renderTarget.colorBuffer;
162-
const colorView = colorBuffer ? colorBuffer.impl.getView(device) : null;
162+
let colorView = null;
163+
if (colorBuffer) {
164+
colorView = colorBuffer.impl.getView(device);
165+
166+
// cubemap face view - face is a single 2d array layer in order [+X, -X, +Y, -Y, +Z, -Z]
167+
if (colorBuffer.cubemap) {
168+
colorView = colorBuffer.impl.createView({
169+
dimension: '2d',
170+
baseArrayLayer: renderTarget.face,
171+
arrayLayerCount: 1
172+
});
173+
}
174+
}
163175

164176
// multi-sampled color buffer
165177
if (samples > 1) {

src/platform/graphics/webgpu/webgpu-shader.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,27 @@ class WebgpuShader {
4848
}
4949

5050
process() {
51+
const shader = this.shader;
52+
5153
// process the shader source to allow for uniforms
52-
const processed = ShaderProcessor.run(this.shader.device, this.shader.definition, this.shader);
54+
const processed = ShaderProcessor.run(shader.device, shader.definition, shader);
5355

5456
// keep reference to processed shaders in debug mode
5557
Debug.call(() => {
5658
this.processed = processed;
5759
});
5860

59-
this._vertexCode = this.transpile(processed.vshader, 'vertex', this.shader.definition.vshader);
60-
this._fragmentCode = this.transpile(processed.fshader, 'fragment', this.shader.definition.fshader);
61+
this._vertexCode = this.transpile(processed.vshader, 'vertex', shader.definition.vshader);
62+
this._fragmentCode = this.transpile(processed.fshader, 'fragment', shader.definition.fshader);
63+
64+
if (!(this._vertexCode && this._fragmentCode)) {
65+
shader.failed = true;
66+
} else {
67+
shader.ready = true;
68+
}
6169

62-
this.shader.meshUniformBufferFormat = processed.meshUniformBufferFormat;
63-
this.shader.meshBindGroupFormat = processed.meshBindGroupFormat;
70+
shader.meshUniformBufferFormat = processed.meshUniformBufferFormat;
71+
shader.meshBindGroupFormat = processed.meshBindGroupFormat;
6472
}
6573

6674
transpile(src, shaderType, originalSrc) {

src/platform/graphics/webgpu/webgpu-texture.js

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,42 +89,37 @@ class WebgpuTexture {
8989
const wgpu = device.wgpu;
9090

9191
this.descr = {
92-
size: { width: texture.width, height: texture.height },
92+
size: {
93+
width: texture.width,
94+
height: texture.height,
95+
depthOrArrayLayers: texture.cubemap ? 6 : 1
96+
},
9397
format: this.format,
9498
mipLevelCount: 1,
9599
sampleCount: 1,
96-
dimension: '2d',
100+
dimension: texture.volume ? '3d' : '2d',
97101

98102
// TODO: use only required usage flags
99103
// COPY_SRC - probably only needed on render target textures, to support copyRenderTarget (grab pass needs it)
100104
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
101105
};
102106

103107
this.gpuTexture = wgpu.createTexture(this.descr);
104-
DebugHelper.setLabel(this.gpuTexture, texture.name);
108+
DebugHelper.setLabel(this.gpuTexture, `${texture.name}${texture.cubemap ? '[cubemap]' : ''}${texture.volume ? '[3d]' : ''}`);
105109

106110
// default texture view descriptor
107-
// type {GPUTextureViewDescriptor}
108-
const textureDescr = this.descr;
109-
const viewDescr = {
110-
format: textureDescr.format,
111-
dimension: textureDescr.dimension,
112-
aspect: 'all',
113-
baseMipLevel: 0,
114-
mipLevelCount: textureDescr.mipLevelCount,
115-
baseArrayLayer: 0,
116-
arrayLayerCount: 1
117-
};
111+
let viewDescr;
118112

119113
// some format require custom default texture view
120114
if (this.texture.format === PIXELFORMAT_DEPTHSTENCIL) {
121115
// we expose the depth part of the format
122-
viewDescr.format = 'depth24plus';
123-
viewDescr.aspect = 'depth-only';
116+
viewDescr = {
117+
format: 'depth24plus',
118+
aspect: 'depth-only'
119+
};
124120
}
125121

126-
this.view = this.gpuTexture.createView(viewDescr);
127-
DebugHelper.setLabel(this.view, `DefaultView: ${this.texture.name}`);
122+
this.view = this.createView(viewDescr);
128123
}
129124

130125
destroy(device) {
@@ -138,6 +133,36 @@ class WebgpuTexture {
138133
return this.view;
139134
}
140135

136+
createView(viewDescr) {
137+
138+
const options = viewDescr ?? {};
139+
const textureDescr = this.descr;
140+
const texture = this.texture;
141+
142+
// '1d', '2d', '2d-array', 'cube', 'cube-array', '3d'
143+
const defaultViewDimension = () => {
144+
if (texture.cubemap) return 'cube';
145+
if (texture.volume) return '3d';
146+
return '2d';
147+
};
148+
149+
// type {GPUTextureViewDescriptor}
150+
const descr = {
151+
format: options.format ?? textureDescr.format,
152+
dimension: options.dimension ?? defaultViewDimension(),
153+
aspect: options.aspect ?? 'all',
154+
baseMipLevel: options.baseMipLevel ?? 0,
155+
mipLevelCount: options.mipLevelCount ?? textureDescr.mipLevelCount,
156+
baseArrayLayer: options.baseArrayLayer ?? 0,
157+
arrayLayerCount: options.arrayLayerCount ?? textureDescr.depthOrArrayLayers
158+
};
159+
160+
const view = this.gpuTexture.createView(descr);
161+
DebugHelper.setLabel(view, `${viewDescr ? `CustomView${JSON.stringify(viewDescr)}` : 'DefaultView'}:${this.texture.name}`);
162+
163+
return view;
164+
}
165+
141166
// TODO: handle the case where those properties get changed
142167

143168
getSampler(device) {
@@ -197,6 +222,11 @@ class WebgpuTexture {
197222
const texture = this.texture;
198223
const wgpu = device.wgpu;
199224

225+
if (this.texture.cubemap) {
226+
Debug.warn('Cubemap texture data upload is not supported yet', this.texture);
227+
return;
228+
}
229+
200230
// upload texture data if any
201231
const mipLevel = 0;
202232
const mipObject = texture._levels[mipLevel];

0 commit comments

Comments
 (0)