From 793a8c238f604395ade4c9a67944936c95bd7cc4 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Wed, 18 Jan 2023 16:36:41 +0000 Subject: [PATCH 1/2] [Breaking] Quad render class, drawQuadWithShader refactor, Camera render events moved. --- examples/webgpu-temp/one.html | 56 +++++-- src/deprecated/deprecated.js | 8 +- src/framework/lightmapper/lightmapper.js | 2 +- src/index.js | 3 +- src/platform/graphics/constants.js | 5 +- src/platform/graphics/graphics-device.js | 35 ++++- src/platform/graphics/render-pass.js | 22 ++- .../graphics/shader-processor-options.js | 24 ++- src/platform/graphics/shader-processor.js | 4 +- src/platform/graphics/simple-post-effect.js | 148 ------------------ .../graphics/webgl/webgl-graphics-device.js | 63 ++++++-- .../graphics/webgpu/webgpu-graphics-device.js | 50 +++++- .../graphics/webgpu/webgpu-render-state.js | 2 + src/platform/graphics/webgpu/webgpu-shader.js | 2 +- src/scene/graphics/post-effect.js | 33 +--- src/scene/graphics/prefilter-cubemap.js | 2 +- src/scene/graphics/quad-render-utils.js | 107 +++++++++++++ src/scene/graphics/quad-render.js | 141 +++++++++++++++++ src/scene/graphics/reproject-texture.js | 2 +- src/scene/materials/material.js | 32 +--- src/scene/morph-instance.js | 9 +- src/scene/particle-system/gpu-updater.js | 2 +- src/scene/particle-system/particle-emitter.js | 4 +- src/scene/renderer/cookie-renderer.js | 2 +- src/scene/renderer/forward-renderer.js | 30 ++-- .../renderer/shadow-renderer-directional.js | 7 +- src/scene/renderer/shadow-renderer.js | 2 +- src/scene/shader-lib/program-library.js | 6 +- src/scene/shader-lib/utils.js | 47 +++++- 29 files changed, 571 insertions(+), 279 deletions(-) delete mode 100644 src/platform/graphics/simple-post-effect.js create mode 100644 src/scene/graphics/quad-render-utils.js create mode 100644 src/scene/graphics/quad-render.js diff --git a/examples/webgpu-temp/one.html b/examples/webgpu-temp/one.html index 3a9ec43fbeb..9ffffb69f46 100644 --- a/examples/webgpu-temp/one.html +++ b/examples/webgpu-temp/one.html @@ -27,6 +27,7 @@ import { Shader } from '../../src/platform/graphics/shader.js'; import { Texture } from '../../src/platform/graphics/texture.js'; import { RenderTarget } from '../../src/platform/graphics/render-target.js'; + import { drawQuadWithShader } from '../../src/scene/graphics/quad-render-utils.js'; import { DEVICETYPE_WEBGL, DEVICETYPE_WEBGPU, SEMANTIC_TEXCOORD0, SEMANTIC_POSITION, CULLFACE_NONE, @@ -43,6 +44,7 @@ import { StandardMaterial } from "../../src/scene/materials/standard-material.js"; import { BLEND_ADDITIVE, BLEND_SUBTRACTIVE, BLEND_SCREEN, BLEND_NORMAL, BLEND_NONE } from "../../src/scene/constants.js"; + import { createShaderFromCode } from '../../src/scene/shader-lib/utils.js'; import { Material } from "../../src/scene/materials/material.js"; import { BasicMaterial } from "../../src/scene/materials/basic-material.js"; import { RenderComponentSystem } from '../../src/framework/components/render/system.js'; @@ -167,6 +169,7 @@ // Create the shader definition and shader from the vertex and fragment shaders const shaderDefinition = { + name: 'OneExampleTestShader', attributes: { vertex_position: SEMANTIC_POSITION, vertex_texCoord0: SEMANTIC_TEXCOORD0 @@ -236,21 +239,21 @@ const worldLayer = app.scene.layers.getLayerByName("World"); const skyboxLayer = app.scene.layers.getLayerByName("Skybox"); - const textureCamera = new Entity("TextureCamera"); - textureCamera.addComponent("camera", { - clearColor: new Color(0.2, 0.2, 0.4), + // const textureCamera = new Entity("TextureCamera"); + // textureCamera.addComponent("camera", { + // clearColor: new Color(0.2, 0.2, 0.4), - layers: [worldLayer.id, skyboxLayer.id], + // layers: [worldLayer.id, skyboxLayer.id], - // set the priority of textureCamera to lower number than the priority of the main camera (which is at default 0) - // to make it rendered first each frame - priority: -1, + // // set the priority of textureCamera to lower number than the priority of the main camera (which is at default 0) + // // to make it rendered first each frame + // priority: -1, - // this camera renders into texture target - renderTarget: renderTarget - }); - app.root.addChild(textureCamera); - textureCamera.setLocalPosition(0, 0, 12); + // // this camera renders into texture target + // renderTarget: renderTarget + // }); + // app.root.addChild(textureCamera); + // textureCamera.setLocalPosition(0, 0, 12); /////////////// skinning - commented out as it is still not functional @@ -301,6 +304,21 @@ app.root.addChild(lightDir); + const textureBlitVertexShader = ` + attribute vec2 vertex_position; + void main(void) { + gl_Position = vec4(vertex_position, 0.5, 1.0); + }`; + + const textureBlitFragmentShader = ` + void main(void) { + gl_FragColor = vec4(1, 0, 0, 1); + }`; + + + + // console.log("create shadewr !!!!!!!!"); + const blitShader = createShaderFromCode(app.graphicsDevice, textureBlitVertexShader, textureBlitFragmentShader, `blitShader`); let time = 0; @@ -328,8 +346,18 @@ camera.setLocalPosition(20 * Math.cos(time * 0.2), 2, 20 * Math.sin(time * 0.2)); camera.lookAt(Vec3.ZERO); - textureCamera.setLocalPosition(20 * Math.cos(time * 0.2), 2, 20 * Math.sin(time * 0.2)); - textureCamera.lookAt(Vec3.ZERO); + // textureCamera.setLocalPosition(20 * Math.cos(time * 0.2), 2, 20 * Math.sin(time * 0.2)); + // textureCamera.lookAt(Vec3.ZERO); + + + + + drawQuadWithShader(app.graphicsDevice, renderTarget, blitShader); + + + + + app.drawTexture(0.6, -0.7, 0.6, 0.3, assets.rocks.resource); app.drawTexture(-0.6, -0.7, 0.6, 0.5, renderTexture); diff --git a/src/deprecated/deprecated.js b/src/deprecated/deprecated.js index c03b3ee40dc..93f88224c0a 100644 --- a/src/deprecated/deprecated.js +++ b/src/deprecated/deprecated.js @@ -35,11 +35,11 @@ import { TYPE_INT8, TYPE_UINT8, TYPE_INT16, TYPE_UINT16, TYPE_INT32, TYPE_UINT32, TYPE_FLOAT32 } from '../platform/graphics/constants.js'; import { begin, end, fogCode, gammaCode, skinCode, tonemapCode } from '../scene/shader-lib/programs/common.js'; -import { drawQuadWithShader } from '../platform/graphics/simple-post-effect.js'; +import { drawQuadWithShader } from '../scene/graphics/quad-render-utils.js'; import { shaderChunks } from '../scene/shader-lib/chunks/chunks.js'; import { GraphicsDevice } from '../platform/graphics/graphics-device.js'; import { IndexBuffer } from '../platform/graphics/index-buffer.js'; -import { createFullscreenQuad, drawFullscreenQuad, PostEffect } from '../scene/graphics/post-effect.js'; +import { drawFullscreenQuad, PostEffect } from '../scene/graphics/post-effect.js'; import { PostEffectQueue } from '../framework/components/camera/post-effect-queue.js'; import { ProgramLibrary } from '../scene/shader-lib/program-library.js'; import { getProgramLibrary, setProgramLibrary } from '../scene/shader-lib/get-program-library.js'; @@ -436,7 +436,9 @@ export const gfx = { }; export const posteffect = { - createFullscreenQuad: createFullscreenQuad, + createFullscreenQuad: (device) => { + return device.quadVertexBuffer; + }, drawFullscreenQuad: drawFullscreenQuad, PostEffect: PostEffect, PostEffectQueue: PostEffectQueue diff --git a/src/framework/lightmapper/lightmapper.js b/src/framework/lightmapper/lightmapper.js index 66b6b5870eb..cd4a5bebb7a 100644 --- a/src/framework/lightmapper/lightmapper.js +++ b/src/framework/lightmapper/lightmapper.js @@ -16,7 +16,7 @@ import { } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; -import { drawQuadWithShader } from '../../platform/graphics/simple-post-effect.js'; +import { drawQuadWithShader } from '../../scene/graphics/quad-render-utils.js'; import { Texture } from '../../platform/graphics/texture.js'; import { MeshInstance } from '../../scene/mesh-instance.js'; diff --git a/src/index.js b/src/index.js index 59fdac01769..b91302f84a1 100644 --- a/src/index.js +++ b/src/index.js @@ -58,7 +58,6 @@ export * from './platform/audio/constants.js'; // PLATFORM / GRAPHICS export * from './platform/graphics/constants.js'; export { createGraphicsDevice } from './platform/graphics/graphics-device-create.js'; -export { drawQuadWithShader, drawTexture } from './platform/graphics/simple-post-effect.js'; export { GraphicsDevice } from './platform/graphics/graphics-device.js'; export { IndexBuffer } from './platform/graphics/index-buffer.js'; export { RenderTarget } from './platform/graphics/render-target.js'; @@ -102,6 +101,7 @@ export { SoundInstance3d } from './platform/sound/instance3d.js'; // SCENE export * from './scene/constants.js'; export { calculateNormals, calculateTangents, createBox, createCapsule, createCone, createCylinder, createMesh, createPlane, createSphere, createTorus } from './scene/procedural.js'; +export { drawQuadWithShader, drawTexture } from './scene/graphics/quad-render-utils.js'; export { BasicMaterial } from './scene/materials/basic-material.js'; export { Batch } from './scene/batching/batch.js'; export { BatchGroup } from './scene/batching/batch-group.js'; @@ -124,6 +124,7 @@ export { Morph } from './scene/morph.js'; export { MorphInstance } from './scene/morph-instance.js'; export { MorphTarget } from './scene/morph-target.js'; export { ParticleEmitter } from './scene/particle-system/particle-emitter.js'; +export { QuadRender } from './scene/graphics/quad-render.js'; export { Scene } from './scene/scene.js'; export { Skin } from './scene/skin.js'; export { SkinInstance } from './scene/skin-instance.js'; diff --git a/src/platform/graphics/constants.js b/src/platform/graphics/constants.js index 065a940ae55..1cdd58ebf65 100644 --- a/src/platform/graphics/constants.js +++ b/src/platform/graphics/constants.js @@ -1177,8 +1177,9 @@ export const SHADERSTAGE_FRAGMENT = 2; export const SHADERSTAGE_COMPUTE = 4; // indices of commonly used bind groups -export const BINDGROUP_VIEW = 0; -export const BINDGROUP_MESH = 1; +// sorted in a way that any trailing bind groups can be unused in any render pass +export const BINDGROUP_MESH = 0; +export const BINDGROUP_VIEW = 1; // name of the default uniform buffer slot in a bind group export const UNIFORM_BUFFER_DEFAULT_SLOT_NAME = 'default'; diff --git a/src/platform/graphics/graphics-device.js b/src/platform/graphics/graphics-device.js index 99c2e04314e..d3f2ae7ee7c 100644 --- a/src/platform/graphics/graphics-device.js +++ b/src/platform/graphics/graphics-device.js @@ -4,9 +4,12 @@ import { platform } from '../../core/platform.js'; import { now } from '../../core/time.js'; import { - PRIMITIVE_POINTS, PRIMITIVE_TRIFAN + BUFFER_STATIC, + PRIMITIVE_POINTS, PRIMITIVE_TRIFAN, SEMANTIC_POSITION, TYPE_FLOAT32 } from './constants.js'; import { ScopeSpace } from './scope-space.js'; +import { VertexBuffer } from './vertex-buffer.js'; +import { VertexFormat } from './vertex-format.js'; const EVENT_RESIZE = 'resizecanvas'; @@ -124,6 +127,14 @@ class GraphicsDevice extends EventHandler { */ textureHalfFloatRenderable; + /** + * A vertex buffer representing a quad. + * + * @type {VertexBuffer} + * @ignore + */ + quadVertexBuffer; + constructor(canvas) { super(); @@ -188,6 +199,19 @@ class GraphicsDevice extends EventHandler { this.textureBias.setValue(0.0); } + /** + * Function that executes after the device has been created. + */ + postInit() { + + // create quad vertex buffer + const vertexFormat = new VertexFormat(this, [ + { semantic: SEMANTIC_POSITION, components: 2, type: TYPE_FLOAT32 } + ]); + const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]); + this.quadVertexBuffer = new VertexBuffer(this, vertexFormat, 4, BUFFER_STATIC, positions); + } + /** * Fired when the canvas is resized. * @@ -203,6 +227,9 @@ class GraphicsDevice extends EventHandler { // fire the destroy event. // textures and other device resources may destroy themselves in response. this.fire('destroy'); + + this.quadVertexBuffer?.destroy(); + this.quadVertexBuffer = null; } onDestroyShader(shader) { @@ -232,6 +259,12 @@ class GraphicsDevice extends EventHandler { this.renderTarget = null; } + initializeRenderState() { + // Cached viewport and scissor dimensions + this.vx = this.vy = this.vw = this.vh = 0; + this.sx = this.sy = this.sw = this.sh = 0; + } + /** * Sets the specified render target on the device. If null is passed as a parameter, the back * buffer becomes the current target for all rendering operations. diff --git a/src/platform/graphics/render-pass.js b/src/platform/graphics/render-pass.js index cd51dd58b1a..5c9ae979757 100644 --- a/src/platform/graphics/render-pass.js +++ b/src/platform/graphics/render-pass.js @@ -123,6 +123,20 @@ class RenderPass { */ fullSizeClearRect = true; + /** + * Custom function that is called before the pass has started. + * + * @type {Function} + */ + before; + + /** + * Custom function that is called after the pass has fnished. + * + * @type {Function} + */ + after; + /** * Creates an instance of the RenderPass. * @@ -130,16 +144,12 @@ class RenderPass { * graphics device. * @param {Function} execute - Custom function that is called when the pass needs to be * rendered. - * @param {Function} [after] - Custom function that is called after the pass has fnished. */ - constructor(graphicsDevice, execute, after = null) { + constructor(graphicsDevice, execute) { this.device = graphicsDevice; /** @type {Function} */ this.execute = execute; - - /** @type {Function} */ - this.after = after; } /** @@ -211,6 +221,8 @@ class RenderPass { const realPass = this.renderTarget !== undefined; DebugGraphics.pushGpuMarker(device, `Pass:${this.name}`); + this.before?.(); + if (realPass) { device.startPass(this); } diff --git a/src/platform/graphics/shader-processor-options.js b/src/platform/graphics/shader-processor-options.js index 648b5954728..65d692582f8 100644 --- a/src/platform/graphics/shader-processor-options.js +++ b/src/platform/graphics/shader-processor-options.js @@ -1,5 +1,10 @@ import { BINDGROUP_VIEW } from "./constants.js"; +/** + * Options to drive shader processing to add support for bind groups and uniform buffers. + * + * @ignore + */ class ShaderProcessorOptions { /** @type {import('./uniform-buffer-format.js').UniformBufferFormat[]} */ uniformFormats = []; @@ -10,9 +15,9 @@ class ShaderProcessorOptions { /** * Constructs shader processing options, used to process the shader for uniform buffer support. * - * @param {import('./uniform-buffer-format.js').UniformBufferFormat} viewUniformFormat - Format + * @param {import('./uniform-buffer-format.js').UniformBufferFormat} [viewUniformFormat] - Format * of the uniform buffer. - * @param {import('./bind-group-format.js').BindGroupFormat} viewBindGroupFormat - Format of + * @param {import('./bind-group-format.js').BindGroupFormat} [viewBindGroupFormat] - Format of * the bind group. */ constructor(viewUniformFormat, viewBindGroupFormat) { @@ -32,7 +37,7 @@ class ShaderProcessorOptions { for (let i = 0; i < this.uniformFormats.length; i++) { const uniformFormat = this.uniformFormats[i]; - if (uniformFormat.get(name)) { + if (uniformFormat?.get(name)) { return true; } } @@ -50,13 +55,24 @@ class ShaderProcessorOptions { for (let i = 0; i < this.bindGroupFormats.length; i++) { const groupFormat = this.bindGroupFormats[i]; - if (groupFormat.getTexture(name)) { + if (groupFormat?.getTexture(name)) { return true; } } return false; } + + /** + * Generate unique key represending the processing options. + * + * @returns {string} - Returns the key. + */ + generateKey() { + // TODO: Optimize. Uniform and BindGroup formats should have their keys evaluated in their + // constructors, and here we should simply concatenate those. + return JSON.stringify(this); + } } export { ShaderProcessorOptions }; diff --git a/src/platform/graphics/shader-processor.js b/src/platform/graphics/shader-processor.js index ed13eba4fc6..48245b64e43 100644 --- a/src/platform/graphics/shader-processor.js +++ b/src/platform/graphics/shader-processor.js @@ -297,7 +297,9 @@ class ShaderProcessor { }); // and also for generated mesh format, which is at the slot 0 of the bind group - code += meshUniformBufferFormat.getShaderDeclaration(BINDGROUP_MESH, 0); + if (meshUniformBufferFormat) { + code += meshUniformBufferFormat.getShaderDeclaration(BINDGROUP_MESH, 0); + } // generate code for textures processingOptions.bindGroupFormats.forEach((format, bindGroupIndex) => { diff --git a/src/platform/graphics/simple-post-effect.js b/src/platform/graphics/simple-post-effect.js deleted file mode 100644 index c92b5ce1127..00000000000 --- a/src/platform/graphics/simple-post-effect.js +++ /dev/null @@ -1,148 +0,0 @@ -import { BUFFER_STATIC, CULLFACE_NONE, PRIMITIVE_TRISTRIP, SEMANTIC_POSITION, TYPE_FLOAT32 } from './constants.js'; -import { VertexBuffer } from './vertex-buffer.js'; -import { VertexFormat } from './vertex-format.js'; -import { DebugGraphics } from './debug-graphics.js'; -import { DeviceCache } from './device-cache.js'; - -// Draws shaded full-screen quad in a single call -const _postEffectQuadDraw = { - type: PRIMITIVE_TRISTRIP, - base: 0, - count: 4, - indexed: false -}; - -// Device cache storing a quad vertex buffer -const postEffectDeviceCache = new DeviceCache(); - -function getPostEffectQuadVB(device) { - return postEffectDeviceCache.get(device, () => { - const vertexFormat = new VertexFormat(device, [{ - semantic: SEMANTIC_POSITION, - components: 2, - type: TYPE_FLOAT32 - }]); - const positions = new Float32Array(8); - positions.set([-1, -1, 1, -1, -1, 1, 1, 1]); - return new VertexBuffer(device, vertexFormat, 4, BUFFER_STATIC, positions); - }); -} - -/** - * Draws a screen-space quad using a specific shader. Mostly used by post-effects. - * - * @param {import('./graphics-device.js').GraphicsDevice} device - The graphics device used to draw - * the quad. - * @param {import('./render-target.js').RenderTarget|undefined} target - The destination render - * target. If undefined, target is the frame buffer. - * @param {import('./shader.js').Shader} shader - The shader used for rendering the quad. Vertex - * shader should contain `attribute vec2 vertex_position`. - * @param {import('../../core/math/vec4.js').Vec4} [rect] - The viewport rectangle of the quad, in - * pixels. Defaults to fullscreen (`0, 0, target.width, target.height`). - * @param {import('../../core/math/vec4.js').Vec4} [scissorRect] - The scissor rectangle of the - * quad, in pixels. Defaults to fullscreen (`0, 0, target.width, target.height`). - * @param {boolean} [useBlend] - True to enable blending. Defaults to false, disabling blending. - */ -function drawQuadWithShader(device, target, shader, rect, scissorRect, useBlend = false) { - - DebugGraphics.pushGpuMarker(device, "drawQuadWithShader"); - - const oldRt = device.renderTarget; - device.setRenderTarget(target); - device.updateBegin(); - - let x, y, w, h; - let sx, sy, sw, sh; - if (!rect) { - w = target ? target.width : device.width; - h = target ? target.height : device.height; - x = 0; - y = 0; - } else { - x = rect.x; - y = rect.y; - w = rect.z; - h = rect.w; - } - - if (!scissorRect) { - sx = x; - sy = y; - sw = w; - sh = h; - } else { - sx = scissorRect.x; - sy = scissorRect.y; - sw = scissorRect.z; - sh = scissorRect.w; - } - - const oldVx = device.vx; - const oldVy = device.vy; - const oldVw = device.vw; - const oldVh = device.vh; - device.setViewport(x, y, w, h); - const oldSx = device.sx; - const oldSy = device.sy; - const oldSw = device.sw; - const oldSh = device.sh; - device.setScissor(sx, sy, sw, sh); - - const oldDepthTest = device.getDepthTest(); - const oldDepthWrite = device.getDepthWrite(); - const oldCullMode = device.getCullMode(); - const oldWR = device.writeRed; - const oldWG = device.writeGreen; - const oldWB = device.writeBlue; - const oldWA = device.writeAlpha; - device.setDepthTest(false); - device.setDepthWrite(false); - device.setCullMode(CULLFACE_NONE); - device.setColorWrite(true, true, true, true); - if (!useBlend) device.setBlending(false); - - device.setVertexBuffer(getPostEffectQuadVB(device), 0); - device.setShader(shader); - - device.draw(_postEffectQuadDraw); - - device.setDepthTest(oldDepthTest); - device.setDepthWrite(oldDepthWrite); - device.setCullMode(oldCullMode); - device.setColorWrite(oldWR, oldWG, oldWB, oldWA); - - device.updateEnd(); - - device.setRenderTarget(oldRt); - device.updateBegin(); - - device.setViewport(oldVx, oldVy, oldVw, oldVh); - device.setScissor(oldSx, oldSy, oldSw, oldSh); - - DebugGraphics.popGpuMarker(device); -} - -/** - * Draws a texture in screen-space. Mostly used by post-effects. - * - * @param {import('./graphics-device.js').GraphicsDevice} device - The graphics device used to draw - * the texture. - * @param {import('./texture.js').Texture} texture - The source texture to be drawn. Accessible as - * `uniform sampler2D * source` in shader. - * @param {import('./render-target.js').RenderTarget} [target] - The destination render target. - * Defaults to the frame buffer. - * @param {import('./shader.js').Shader} [shader] - The shader used for rendering the texture. - * Defaults to {@link GraphicsDevice#getCopyShader}. - * @param {import('../../core/math/vec4.js').Vec4} [rect] - The viewport rectangle to use for the - * texture, in pixels. Defaults to fullscreen (`0, 0, target.width, target.height`). - * @param {import('../../core/math/vec4.js').Vec4} [scissorRect] - The scissor rectangle to use for - * the texture, in pixels. Defaults to fullscreen (`0, 0, target.width, target.height`). - * @param {boolean} [useBlend] - True to enable blending. Defaults to false, disabling blending. - */ -function drawTexture(device, texture, target, shader, rect, scissorRect, useBlend = false) { - shader = shader || device.getCopyShader(); - device.constantTexSource.setValue(texture); - drawQuadWithShader(device, target, shader, rect, scissorRect, useBlend); -} - -export { drawQuadWithShader, drawTexture }; diff --git a/src/platform/graphics/webgl/webgl-graphics-device.js b/src/platform/graphics/webgl/webgl-graphics-device.js index a36c219316a..ac01400f49a 100644 --- a/src/platform/graphics/webgl/webgl-graphics-device.js +++ b/src/platform/graphics/webgl/webgl-graphics-device.js @@ -20,11 +20,11 @@ import { UNIFORMTYPE_BVEC3, UNIFORMTYPE_BVEC4, UNIFORMTYPE_MAT2, UNIFORMTYPE_MAT3, UNIFORMTYPE_MAT4, UNIFORMTYPE_TEXTURE2D, UNIFORMTYPE_TEXTURECUBE, UNIFORMTYPE_FLOATARRAY, UNIFORMTYPE_TEXTURE2D_SHADOW, UNIFORMTYPE_TEXTURECUBE_SHADOW, UNIFORMTYPE_TEXTURE3D, UNIFORMTYPE_VEC2ARRAY, UNIFORMTYPE_VEC3ARRAY, UNIFORMTYPE_VEC4ARRAY, - semanticToLocation + semanticToLocation, + PRIMITIVE_TRISTRIP } from '../constants.js'; import { GraphicsDevice } from '../graphics-device.js'; -import { drawQuadWithShader } from '../simple-post-effect.js'; import { RenderTarget } from '../render-target.js'; import { Texture } from '../texture.js'; import { DebugGraphics } from '../debug-graphics.js'; @@ -79,6 +79,49 @@ void main(void) { } `; +function quadWithShader(device, target, shader) { + + DebugGraphics.pushGpuMarker(device, "QuadWithShader"); + + const oldRt = device.renderTarget; + device.setRenderTarget(target); + device.updateBegin(); + + const oldDepthTest = device.getDepthTest(); + const oldDepthWrite = device.getDepthWrite(); + const oldCullMode = device.getCullMode(); + const oldWR = device.writeRed; + const oldWG = device.writeGreen; + const oldWB = device.writeBlue; + const oldWA = device.writeAlpha; + device.setDepthTest(false); + device.setDepthWrite(false); + device.setCullMode(CULLFACE_NONE); + device.setColorWrite(true, true, true, true); + + device.setVertexBuffer(device.quadVertexBuffer, 0); + device.setShader(shader); + + device.draw({ + type: PRIMITIVE_TRISTRIP, + base: 0, + count: 4, + indexed: false + }); + + device.setDepthTest(oldDepthTest); + device.setDepthWrite(oldDepthWrite); + device.setCullMode(oldCullMode); + device.setColorWrite(oldWR, oldWG, oldWB, oldWA); + + device.updateEnd(); + + device.setRenderTarget(oldRt); + device.updateBegin(); + + DebugGraphics.popGpuMarker(device); +} + function testRenderable(gl, pixelFormat) { let result = true; @@ -170,7 +213,7 @@ function testTextureFloatHighPrecision(device) { colorBuffer: tex1, depth: false }); - drawQuadWithShader(device, targ1, shader1); + quadWithShader(device, targ1, shader1); textureOptions.format = PIXELFORMAT_RGBA8; const tex2 = new Texture(device, textureOptions); @@ -179,7 +222,7 @@ function testTextureFloatHighPrecision(device) { depth: false }); device.constantTexSource.setValue(tex1); - drawQuadWithShader(device, targ2, shader2); + quadWithShader(device, targ2, shader2); const prevFramebuffer = device.activeFramebuffer; device.setFramebuffer(targ2.impl._glFrameBuffer); @@ -725,6 +768,8 @@ class WebglGraphicsDevice extends GraphicsDevice { } else if (this.extTextureFloat && this.extTextureFloatLinear) { this.areaLightLutFormat = PIXELFORMAT_RGBA32F; } + + this.postInit(); } /** @@ -987,6 +1032,8 @@ class WebglGraphicsDevice extends GraphicsDevice { * @ignore */ initializeRenderState() { + super.initializeRenderState(); + const gl = this.gl; // Initialize render state to a known start state @@ -1061,10 +1108,6 @@ class WebglGraphicsDevice extends GraphicsDevice { this.clearStencil = 0; gl.clearStencil(0); - // Cached viewport and scissor dimensions - this.vx = this.vy = this.vw = this.vh = 0; - this.sx = this.sy = this.sw = this.sh = 0; - if (this.webgl2) { gl.hint(gl.FRAGMENT_SHADER_DERIVATIVE_HINT, gl.NICEST); } else { @@ -1281,7 +1324,7 @@ class WebglGraphicsDevice extends GraphicsDevice { } else { const shader = this.getCopyShader(); this.constantTexSource.setValue(source._colorBuffer); - drawQuadWithShader(this, dest, shader); + quadWithShader(this, dest, shader); } DebugGraphics.popGpuMarker(this); @@ -1356,7 +1399,7 @@ class WebglGraphicsDevice extends GraphicsDevice { this.clear(clearOptions); } - Debug.assert(!this.insideRenderPass); + Debug.assert(!this.insideRenderPass, 'RenderPass cannot be started while inside another render pass.'); this.insideRenderPass = true; DebugGraphics.popGpuMarker(this); diff --git a/src/platform/graphics/webgpu/webgpu-graphics-device.js b/src/platform/graphics/webgpu/webgpu-graphics-device.js index ecae702928e..3d07dea07ff 100644 --- a/src/platform/graphics/webgpu/webgpu-graphics-device.js +++ b/src/platform/graphics/webgpu/webgpu-graphics-device.js @@ -1,7 +1,7 @@ import { Debug, DebugHelper } from '../../../core/debug.js'; import { - DEVICETYPE_WEBGPU, PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA8, PIXELFORMAT_BGRA8 + DEVICETYPE_WEBGPU, PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA8, PIXELFORMAT_BGRA8, CULLFACE_BACK } from '../constants.js'; import { GraphicsDevice } from '../graphics-device.js'; import { RenderTarget } from '../render-target.js'; @@ -69,9 +69,22 @@ class WebgpuGraphicsDevice extends GraphicsDevice { super(canvas); this.deviceType = DEVICETYPE_WEBGPU; + // TODO: refactor as needed + this.writeRed = true; + this.writeGreen = true; + this.writeBlue = true; + this.writeAlpha = true; + this.initDeviceCaps(); } + /** + * Destroy the graphics device. + */ + destroy() { + super.destroy(); + } + initDeviceCaps() { this.precision = 'highp'; this.maxPrecision = 'highp'; @@ -164,6 +177,8 @@ class WebgpuGraphicsDevice extends GraphicsDevice { this.createFramebuffer(); + this.postInit(); + return this; } @@ -242,6 +257,7 @@ class WebgpuGraphicsDevice extends GraphicsDevice { if (this.shader.ready) { const passEncoder = this.passEncoder; + Debug.assert(passEncoder); // vertex buffers const vb0 = this.vertexBuffers[0]; @@ -303,9 +319,17 @@ class WebgpuGraphicsDevice extends GraphicsDevice { setDepthTest(depthTest) { } + getDepthTest() { + return true; + } + setCullMode(cullMode) { } + getCullMode() { + return CULLFACE_BACK; + } + setAlphaToCoverage(state) { } @@ -315,6 +339,10 @@ class WebgpuGraphicsDevice extends GraphicsDevice { setDepthWrite(writeDepth) { } + getDepthWrite() { + return true; + } + initializeContextCaches() { super.initializeContextCaches(); } @@ -365,6 +393,9 @@ class WebgpuGraphicsDevice extends GraphicsDevice { // start the pass this.passEncoder = this.commandEncoder.beginRenderPass(wrt.renderPassDescriptor); DebugHelper.setLabel(this.passEncoder, renderPass.name); + + Debug.assert(!this.insideRenderPass, 'RenderPass cannot be started while inside another render pass.'); + this.insideRenderPass = true; } /** @@ -380,6 +411,11 @@ class WebgpuGraphicsDevice extends GraphicsDevice { this.wgpu.queue.submit([this.commandEncoder.finish()]); this.commandEncoder = null; + + // each render pass can use different number of bind groups + this.bindGroupFormats.length = 0; + + this.insideRenderPass = false; } clear(options) { @@ -410,6 +446,12 @@ class WebgpuGraphicsDevice extends GraphicsDevice { // so we can skip this if fullscreen // TODO: this condition should be removed, it's here to handle fake grab pass, which should be refactored instead if (this.passEncoder) { + + this.vx = x; + this.vy = y; + this.vw = w; + this.vh = h; + this.passEncoder.setViewport(x, this.renderTarget.height - y - h, w, h, 0, 1); } } @@ -419,6 +461,12 @@ class WebgpuGraphicsDevice extends GraphicsDevice { // so we can skip this if fullscreen // TODO: this condition should be removed, it's here to handle fake grab pass, which should be refactored instead if (this.passEncoder) { + + this.sx = x; + this.sy = y; + this.sw = w; + this.sh = h; + this.passEncoder.setScissorRect(x, this.renderTarget.height - y - h, w, h); } } diff --git a/src/platform/graphics/webgpu/webgpu-render-state.js b/src/platform/graphics/webgpu/webgpu-render-state.js index 34a72485a28..3d77e176012 100644 --- a/src/platform/graphics/webgpu/webgpu-render-state.js +++ b/src/platform/graphics/webgpu/webgpu-render-state.js @@ -8,6 +8,8 @@ class WebgpuRenderState { this.reset(); } + // TODO: When setDepthTest is implemented, a webgpu only workaround should be removed in drawQuadWithShader function. + reset() { // blend state diff --git a/src/platform/graphics/webgpu/webgpu-shader.js b/src/platform/graphics/webgpu/webgpu-shader.js index f1d65e4af08..779b1ac0e48 100644 --- a/src/platform/graphics/webgpu/webgpu-shader.js +++ b/src/platform/graphics/webgpu/webgpu-shader.js @@ -75,7 +75,7 @@ class WebgpuShader { try { return this.shader.device.glslang.compileGLSL(src, shaderType); } catch (err) { - console.error(`Failed to transpile webgl ${shaderType} shader with id ${this.shader.id} to WebGPU: [${err.message}]`, { + console.error(`Failed to transpile webgl ${shaderType} shader [${this.shader.label}] to WebGPU: [${err.message}]`, { processed: src, original: originalSrc, shader: this.shader diff --git a/src/scene/graphics/post-effect.js b/src/scene/graphics/post-effect.js index d1cacd159f2..4f2de3aa1be 100644 --- a/src/scene/graphics/post-effect.js +++ b/src/scene/graphics/post-effect.js @@ -1,6 +1,4 @@ -import { CULLFACE_NONE, PRIMITIVE_TRISTRIP, SEMANTIC_POSITION, TYPE_FLOAT32, BUFFER_STATIC } from '../../platform/graphics/constants.js'; -import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js'; -import { VertexFormat } from '../../platform/graphics/vertex-format.js'; +import { CULLFACE_NONE, PRIMITIVE_TRISTRIP } from '../../platform/graphics/constants.js'; // Primitive for drawFullscreenQuad const primitive = { @@ -41,9 +39,9 @@ class PostEffect { /** * The vertex buffer for the fullscreen quad. Used when calling {@link drawFullscreenQuad}. * - * @type {VertexBuffer} + * @type {import('../../platform/graphics/vertex-buffer.js').VertexBuffer} */ - this.vertexBuffer = createFullscreenQuad(graphicsDevice); + this.vertexBuffer = graphicsDevice.quadVertexBuffer; /** * The property that should to be set to `true` (by the custom post effect) if a depth map @@ -52,8 +50,6 @@ class PostEffect { * @type {boolean} */ this.needsDepthBuffer = false; - - this.depthMap = null; } /** @@ -70,25 +66,6 @@ class PostEffect { } } -/** - * Create a vertex buffer with 4 vertices representing a fullscreen quad. - * - * @param {import('../../platform//graphics/graphics-device.js').GraphicsDevice} device - The - * graphics device. - * @returns {VertexBuffer} - The fullscreen quad vertex buffer. - * @ignore - */ -function createFullscreenQuad(device) { - // Create the vertex format - const vertexFormat = new VertexFormat(device, [ - { semantic: SEMANTIC_POSITION, components: 2, type: TYPE_FLOAT32 } - ]); - - // Create a vertex buffer - const data = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]); - return new VertexBuffer(device, vertexFormat, 4, BUFFER_STATIC, data.buffer); -} - /** * Draw a screen-space rectangle in a render target. Primarily meant to be used in custom post * effects based on {@link PostEffect}. @@ -97,7 +74,7 @@ function createFullscreenQuad(device) { * graphics device of the application. * @param {import('../../platform/graphics/render-target.js').RenderTarget} target - The output * render target. - * @param {VertexBuffer} vertexBuffer - The vertex buffer for the rectangle mesh. When calling from + * @param {import('../../platform/graphics/vertex-buffer.js').VertexBuffer} vertexBuffer - The vertex buffer for the rectangle mesh. When calling from * a custom post effect, pass the field {@link PostEffect#vertexBuffer}. * @param {import('../../platform/graphics/shader.js').Shader} shader - The shader to be used for * drawing the rectangle. When calling from a custom post effect, pass the field @@ -167,4 +144,4 @@ function drawFullscreenQuad(device, target, vertexBuffer, shader, rect) { device.setScissor(oldSx, oldSy, oldSw, oldSh); } -export { createFullscreenQuad, drawFullscreenQuad, PostEffect }; +export { drawFullscreenQuad, PostEffect }; diff --git a/src/scene/graphics/prefilter-cubemap.js b/src/scene/graphics/prefilter-cubemap.js index 9818b459608..abd4134cd45 100644 --- a/src/scene/graphics/prefilter-cubemap.js +++ b/src/scene/graphics/prefilter-cubemap.js @@ -5,7 +5,7 @@ import { PIXELFORMAT_RGBA8, TEXTURETYPE_DEFAULT, TEXTURETYPE_RGBM } from '../../platform/graphics/constants.js'; import { createShaderFromCode } from '../shader-lib/utils.js'; -import { drawQuadWithShader } from '../../platform/graphics/simple-post-effect.js'; +import { drawQuadWithShader } from './quad-render-utils.js'; import { shaderChunks } from '../shader-lib/chunks/chunks.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; import { Texture } from '../../platform/graphics/texture.js'; diff --git a/src/scene/graphics/quad-render-utils.js b/src/scene/graphics/quad-render-utils.js new file mode 100644 index 00000000000..d398924505d --- /dev/null +++ b/src/scene/graphics/quad-render-utils.js @@ -0,0 +1,107 @@ +import { Debug } from '../../core/debug.js'; +import { Vec4 } from '../../core/math/vec4.js'; + +import { CULLFACE_NONE, DEVICETYPE_WEBGPU } from '../../platform/graphics/constants.js'; +import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; +import { RenderPass } from '../../platform/graphics/render-pass.js'; +import { QuadRender } from './quad-render.js'; + +const _tempRect = new Vec4(); + +/** + * Draws a screen-space quad using a specific shader. + * + * @param {import('../../platform/graphics/graphics-device.js').GraphicsDevice} device - The graphics device used to draw + * the quad. + * @param {import('../../platform/graphics/render-target.js').RenderTarget|undefined} target - The destination render + * target. If undefined, target is the frame buffer. + * @param {import('../../platform/graphics/shader.js').Shader} shader - The shader used for rendering the quad. Vertex + * shader should contain `attribute vec2 vertex_position`. + * @param {import('../../core/math/vec4.js').Vec4} [rect] - The viewport rectangle of the quad, in + * pixels. Defaults to fullscreen (`0, 0, target.width, target.height`). + * @param {import('../../core/math/vec4.js').Vec4} [scissorRect] - The scissor rectangle of the + * quad, in pixels. Defaults to fullscreen (`0, 0, target.width, target.height`). + * @param {boolean} [useBlend] - True to enable blending. Defaults to false, disabling blending. + */ +function drawQuadWithShader(device, target, shader, rect, scissorRect, useBlend = false) { + + // a valid target or a null target (framebuffer) are supported + Debug.assert(target !== undefined); + + DebugGraphics.pushGpuMarker(device, "drawQuadWithShader"); + + const oldDepthTest = device.getDepthTest(); + const oldDepthWrite = device.getDepthWrite(); + const oldCullMode = device.getCullMode(); + const oldWR = device.writeRed; + const oldWG = device.writeGreen; + const oldWB = device.writeBlue; + const oldWA = device.writeAlpha; + + device.setDepthTest(false); + device.setDepthWrite(false); + device.setCullMode(CULLFACE_NONE); + device.setColorWrite(true, true, true, true); + if (!useBlend) device.setBlending(false); + + // prepare the quad for rendering with the shader + const quad = new QuadRender(shader); + + // by default render to the whole render target + if (!rect) { + rect = _tempRect; + rect.x = 0; + rect.y = 0; + rect.z = target ? target.width : device.width; + rect.w = target ? target.height : device.height; + } + + // prepare a render pass to render the quad to the render target + const renderPass = new RenderPass(device, () => { + quad.render(rect, scissorRect); + }); + renderPass.init(target); + renderPass.colorOps.clear = false; + renderPass.depthStencilOps.clearDepth = false; + + // TODO: this is temporary, till the webgpu supports setDepthTest + if (device.deviceType === DEVICETYPE_WEBGPU) { + renderPass.depthStencilOps.clearDepth = true; + } + + renderPass.render(); + quad.destroy(); + + device.setDepthTest(oldDepthTest); + device.setDepthWrite(oldDepthWrite); + device.setCullMode(oldCullMode); + device.setColorWrite(oldWR, oldWG, oldWB, oldWA); + + DebugGraphics.popGpuMarker(device); +} + +/** + * Draws a texture in screen-space. Mostly used by post-effects. + * + * @param {import('../../platform/graphics/graphics-device.js').GraphicsDevice} device - The graphics device used to draw + * the texture. + * @param {import('../../platform/graphics/texture.js').Texture} texture - The source texture to be drawn. Accessible as + * `uniform sampler2D * source` in shader. + * @param {import('../../platform/graphics/render-target.js').RenderTarget} [target] - The destination render target. + * Defaults to the frame buffer. + * @param {import('../../platform/graphics/shader.js').Shader} [shader] - The shader used for rendering the texture. + * Defaults to {@link GraphicsDevice#getCopyShader}. + * @param {import('../../core/math/vec4.js').Vec4} [rect] - The viewport rectangle to use for the + * texture, in pixels. Defaults to fullscreen (`0, 0, target.width, target.height`). + * @param {import('../../core/math/vec4.js').Vec4} [scissorRect] - The scissor rectangle to use for + * the texture, in pixels. Defaults to fullscreen (`0, 0, target.width, target.height`). + * @param {boolean} [useBlend] - True to enable blending. Defaults to false, disabling blending. + */ +function drawTexture(device, texture, target, shader, rect, scissorRect, useBlend = false) { + Debug.assert(device.deviceType !== DEVICETYPE_WEBGPU, 'pc.drawTexture is not currently supported on WebGPU platform.'); + shader = shader || device.getCopyShader(); + device.constantTexSource.setValue(texture); + drawQuadWithShader(device, target, shader, rect, scissorRect, useBlend); +} + +export { drawQuadWithShader, drawTexture }; diff --git a/src/scene/graphics/quad-render.js b/src/scene/graphics/quad-render.js new file mode 100644 index 00000000000..3ee1f524568 --- /dev/null +++ b/src/scene/graphics/quad-render.js @@ -0,0 +1,141 @@ +import { Debug, DebugHelper } from "../../core/debug.js"; +import { Vec4 } from "../../core/math/vec4.js"; +import { BindGroup } from "../../platform/graphics/bind-group.js"; +import { BINDGROUP_MESH, PRIMITIVE_TRISTRIP } from "../../platform/graphics/constants.js"; +import { DebugGraphics } from "../../platform/graphics/debug-graphics.js"; +import { ShaderProcessorOptions } from "../../platform/graphics/shader-processor-options.js"; +import { UniformBuffer } from "../../platform/graphics/uniform-buffer.js"; +import { processShader } from "../shader-lib/utils.js"; + +const _quadPrimitive = { + type: PRIMITIVE_TRISTRIP, + base: 0, + count: 4, + indexed: false +}; + +const _tempViewport = new Vec4(); +const _tempScissor = new Vec4(); + +/** + * An object that renders a quad using a {@link Shader}. + * + * Example: + * + * ```javascript + * var = pc.createShaderFromCode(app.graphicsDevice, vertexShader, fragmentShader, `MyShader`); + * var quad = new QuadRender(shader); + * quad.render(); + * quad.destroy(); + * ``` + */ +class QuadRender { + /** + * @type {UniformBuffer} + * @ignore + */ + uniformBuffer; + + /** + * @type {BindGroup} + * @ignore + */ + bindGroup; + + /** + * Create a new QuadRender instance. + * + * @param {import('../../platform/graphics/shader.js').Shader} shader - The shader to be used to render the quad. + */ + constructor(shader) { + + const device = shader.device; + this.shader = shader; + Debug.assert(shader); + + if (device.supportsUniformBuffers) { + + // add uniform buffer support to shader + const processingOptions = new ShaderProcessorOptions(); + this.shader = processShader(shader, processingOptions); + + // uniform buffer + const ubFormat = this.shader.meshUniformBufferFormat; + if (ubFormat) { + this.uniformBuffer = new UniformBuffer(device, ubFormat); + } + + // bind group + const bingGroupFormat = this.shader.meshBindGroupFormat; + Debug.assert(bingGroupFormat); + this.bindGroup = new BindGroup(device, bingGroupFormat, this.uniformBuffer); + DebugHelper.setName(this.bindGroup, `QuadRender-MeshBindGroup_${this.bindGroup.id}`); + } + } + + /** + * Destroyes the resources associated with this instance. + */ + destroy() { + this.uniformBuffer?.destroy(); + this.uniformBuffer = null; + + this.bindGroup?.destroy(); + this.bindGroup = null; + } + + /** + * Renders the quad. If the viewport is provided, the original viewport and scissor is restored + * after the rendering. + * + * @param {import('../../core/math/vec4.js').Vec4} [viewport] - The viewport rectangle of the + * quad, in pixels. The viewport is not changed if not provided. + * @param {import('../../core/math/vec4.js').Vec4} [scissor] - The scissor rectangle of the + * quad, in pixels. Used only if the viewport is provided. + */ + render(viewport, scissor) { + + const device = this.shader.device; + DebugGraphics.pushGpuMarker(device, "QuadRender"); + + // only modify viewport or scissor if viewport supplied + if (viewport) { + + // backup current settings + _tempViewport.set(device.vx, device.vy, device.vw, device.vh); + _tempScissor.set(device.sx, device.sy, device.sw, device.sh); + + // set new values + scissor = scissor ?? viewport; + device.setViewport(viewport.x, viewport.y, viewport.z, viewport.w); + device.setScissor(scissor.x, scissor.y, scissor.z, scissor.w); + } + + device.setVertexBuffer(device.quadVertexBuffer, 0); + + const shader = this.shader; + device.setShader(shader); + + if (device.supportsUniformBuffers) { + + const bindGroup = this.bindGroup; + if (bindGroup.defaultUniformBuffer) { + bindGroup.defaultUniformBuffer.update(); + } + bindGroup.update(); + device.setBindGroup(BINDGROUP_MESH, bindGroup); + } + + device.draw(_quadPrimitive); + + // restore if changed + if (viewport) { + device.setViewport(_tempViewport.x, _tempViewport.y, _tempViewport.z, _tempViewport.w); + device.setScissor(_tempScissor.x, _tempScissor.y, _tempScissor.z, _tempScissor.w); + } + + DebugGraphics.popGpuMarker(device); + } +} + +export { QuadRender }; diff --git a/src/scene/graphics/reproject-texture.js b/src/scene/graphics/reproject-texture.js index 69d6ad4bf96..75ea5cd6710 100644 --- a/src/scene/graphics/reproject-texture.js +++ b/src/scene/graphics/reproject-texture.js @@ -10,7 +10,7 @@ import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { DeviceCache } from '../../platform/graphics/device-cache.js'; import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; -import { drawQuadWithShader } from '../../platform/graphics/simple-post-effect.js'; +import { drawQuadWithShader } from './quad-render-utils.js'; import { Texture } from '../../platform/graphics/texture.js'; import { ChunkUtils } from '../shader-lib/chunk-utils.js'; diff --git a/src/scene/materials/material.js b/src/scene/materials/material.js index af97a6e6588..559811e5b2f 100644 --- a/src/scene/materials/material.js +++ b/src/scene/materials/material.js @@ -16,7 +16,7 @@ import { BLEND_MULTIPLICATIVE, BLEND_ADDITIVEALPHA, BLEND_MULTIPLICATIVE2X, BLEND_SCREEN, BLEND_MIN, BLEND_MAX } from '../constants.js'; -import { getProgramLibrary } from '../shader-lib/get-program-library.js'; +import { processShader } from '../shader-lib/utils.js'; import { getDefaultMaterial } from './default-material.js'; let id = 0; @@ -381,37 +381,9 @@ class Material { getShaderVariant(device, scene, objDefs, staticLightList, pass, sortedLights, viewUniformFormat, viewBindGroupFormat) { - Debug.assert(this._shader, 'Material does not have a shader set', this); - - // default generator for a material - simply return existing shader definition. Use generator and getProgram - // to allow for shader processing to be cached - const key = `shader-id-${this._shader.id}`; - const shaderDefinition = this._shader.definition; - const materialGenerator = { - generateKey: function (options) { - // unique name based of the shader id - return key; - }, - - createShaderDefinition: function (device, options) { - return shaderDefinition; - } - }; - - // temporarily register the program generator - const libraryModuleName = 'shader'; - const library = getProgramLibrary(device); - Debug.assert(!library.isRegistered(libraryModuleName)); - library.register(libraryModuleName, materialGenerator); - // generate shader variant - its the same shader, but with different processing options const processingOptions = new ShaderProcessorOptions(viewUniformFormat, viewBindGroupFormat); - const variant = library.getProgram(libraryModuleName, {}, processingOptions); - - // unregister it again - library.unregister(libraryModuleName); - - return variant; + return processShader(this._shader, processingOptions); } /** diff --git a/src/scene/morph-instance.js b/src/scene/morph-instance.js index a8a83e14f8f..f76c42f9236 100644 --- a/src/scene/morph-instance.js +++ b/src/scene/morph-instance.js @@ -1,7 +1,7 @@ import { Debug } from '../core/debug.js'; import { BLENDEQUATION_ADD, BLENDMODE_ONE, PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA16F } from '../platform/graphics/constants.js'; -import { drawQuadWithShader } from '../platform/graphics/simple-post-effect.js'; +import { drawQuadWithShader } from './graphics/quad-render-utils.js'; import { RenderTarget } from '../platform/graphics/render-target.js'; import { DebugGraphics } from '../platform/graphics/debug-graphics.js'; @@ -319,8 +319,11 @@ class MorphInstance { if (this._activeTargets.length > 0 || !this.zeroTextures) { // blend morph targets into render targets - this._updateTextureRenderTarget(this.rtPositions, 'texturePositions'); - this._updateTextureRenderTarget(this.rtNormals, 'textureNormals'); + if (this.rtPositions) + this._updateTextureRenderTarget(this.rtPositions, 'texturePositions'); + + if (this.rtNormals) + this._updateTextureRenderTarget(this.rtNormals, 'textureNormals'); // textures were cleared if no active targets this.zeroTextures = this._activeTargets.length === 0; diff --git a/src/scene/particle-system/gpu-updater.js b/src/scene/particle-system/gpu-updater.js index e3c5ff05737..d2e406e88c0 100644 --- a/src/scene/particle-system/gpu-updater.js +++ b/src/scene/particle-system/gpu-updater.js @@ -5,7 +5,7 @@ import { Vec3 } from '../../core/math/vec3.js'; import { CULLFACE_NONE } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; -import { drawQuadWithShader } from '../../platform/graphics/simple-post-effect.js'; +import { drawQuadWithShader } from '../graphics/quad-render-utils.js'; import { EMITTERSHAPE_BOX } from '../constants.js'; diff --git a/src/scene/particle-system/particle-emitter.js b/src/scene/particle-system/particle-emitter.js index e014fc596a1..3c1696f1ba8 100644 --- a/src/scene/particle-system/particle-emitter.js +++ b/src/scene/particle-system/particle-emitter.js @@ -25,6 +25,7 @@ import { RenderTarget } from '../../platform/graphics/render-target.js'; import { Texture } from '../../platform/graphics/texture.js'; import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js'; import { VertexFormat } from '../../platform/graphics/vertex-format.js'; +import { ShaderProcessorOptions } from '../../platform/graphics/shader-processor-options.js'; import { BLEND_NORMAL, @@ -854,6 +855,7 @@ class ParticleEmitter { // set by Editor if running inside editor const inTools = this.emitter.inTools; + const processingOptions = new ShaderProcessorOptions(viewUniformFormat, viewBindGroupFormat); const shader = programLib.getProgram('particle', { useCpu: this.emitter.useCpu, @@ -877,7 +879,7 @@ class ParticleEmitter { animTexLoop: this.emitter.animLoop, pack8: this.emitter.pack8, customFace: this.emitter.orientation !== PARTICLEORIENTATION_SCREEN - }); + }, processingOptions); return shader; }; diff --git a/src/scene/renderer/cookie-renderer.js b/src/scene/renderer/cookie-renderer.js index a22eac9303f..53af410c2bd 100644 --- a/src/scene/renderer/cookie-renderer.js +++ b/src/scene/renderer/cookie-renderer.js @@ -3,7 +3,7 @@ import { Mat4 } from '../../core/math/mat4.js'; import { ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; -import { drawQuadWithShader } from '../../platform/graphics/simple-post-effect.js'; +import { drawQuadWithShader } from '../graphics/quad-render-utils.js'; import { Texture } from '../../platform/graphics/texture.js'; import { LIGHTTYPE_OMNI } from '../constants.js'; diff --git a/src/scene/renderer/forward-renderer.js b/src/scene/renderer/forward-renderer.js index ab32656dc80..0f8c46a90ab 100644 --- a/src/scene/renderer/forward-renderer.js +++ b/src/scene/renderer/forward-renderer.js @@ -940,9 +940,27 @@ class ForwardRenderer extends Renderer { const renderActions = layerComposition._renderActions; const startRenderAction = renderActions[startIndex]; + const endRenderAction = renderActions[endIndex]; const startLayer = layerComposition.layerList[startRenderAction.layerIndex]; const camera = startLayer.cameras[startRenderAction.cameraIndex]; + if (camera) { + + // callback on the camera component before rendering with this camera for the first time + if (startRenderAction.firstCameraUse && camera.onPreRender) { + renderPass.before = () => { + camera.onPreRender(); + }; + } + + // callback on the camera component when we're done rendering with this camera + if (endRenderAction.lastCameraUse && camera.onPostRender) { + renderPass.after = () => { + camera.onPostRender(); + }; + } + } + // depth grab pass on webgl1 is normal render pass (scene gets re-rendered) const grabPassRequired = isGrabPass && SceneGrab.requiresRenderPass(this.device, camera); const isRealPass = !isGrabPass || grabPassRequired; @@ -1065,13 +1083,6 @@ class ForwardRenderer extends Renderer { const drawTime = now(); // #endif - if (camera) { - // callback on the camera component before rendering with this camera for the first time during the frame - if (renderAction.firstCameraUse && camera.onPreRender) { - camera.onPreRender(); - } - } - // Call prerender callback if there's one if (!transparent && layer.onPreRenderOpaque) { layer.onPreRenderOpaque(cameraPass); @@ -1155,11 +1166,6 @@ class ForwardRenderer extends Renderer { device.setStencilTest(false); // don't leak stencil state device.setAlphaToCoverage(false); // don't leak a2c state device.setDepthBias(false); - - // callback on the camera component when we're done rendering all layers with this camera - if (renderAction.lastCameraUse && camera.onPostRender) { - camera.onPostRender(); - } } // Call layer's postrender callback if there's one diff --git a/src/scene/renderer/shadow-renderer-directional.js b/src/scene/renderer/shadow-renderer-directional.js index b3c8af7bcac..e814d8989a8 100644 --- a/src/scene/renderer/shadow-renderer-directional.js +++ b/src/scene/renderer/shadow-renderer-directional.js @@ -217,13 +217,12 @@ class ShadowRendererDirectional { shadowUpdateOverrides[face] = SHADOWUPDATE_NONE; } } + }); - }, () => { - + renderPass.after = () => { // after the pass is done, apply VSM blur if needed this.shadowRenderer.renderVms(light, camera); - - }); + }; // setup render pass using any of the cameras, they all have the same pass related properties this.shadowRenderer.setupRenderPass(renderPass, shadowCamera, allCascadesRendering); diff --git a/src/scene/renderer/shadow-renderer.js b/src/scene/renderer/shadow-renderer.js index 23d681581b7..ae98293f1d1 100644 --- a/src/scene/renderer/shadow-renderer.js +++ b/src/scene/renderer/shadow-renderer.js @@ -7,7 +7,7 @@ import { Vec4 } from '../../core/math/vec4.js'; import { DEVICETYPE_WEBGPU, FUNC_LESSEQUAL, SHADERSTAGE_FRAGMENT, SHADERSTAGE_VERTEX, UNIFORMTYPE_MAT4, UNIFORM_BUFFER_DEFAULT_SLOT_NAME } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; -import { drawQuadWithShader } from '../../platform/graphics/simple-post-effect.js'; +import { drawQuadWithShader } from '../graphics/quad-render-utils.js'; import { BLUR_GAUSSIAN, diff --git a/src/scene/shader-lib/program-library.js b/src/scene/shader-lib/program-library.js index cb852eef235..06223f1e013 100644 --- a/src/scene/shader-lib/program-library.js +++ b/src/scene/shader-lib/program-library.js @@ -96,7 +96,7 @@ class ProgramLibrary { const device = this._device; def = generator.createShaderDefinition(device, options); - def.name = `${name}-pass:${options.pass}`; + def.name = def.name ?? (options.pass ? `${name}-pass:${options.pass}` : name); this.definitionsCache.set(key, def); } return def; @@ -120,7 +120,7 @@ class ProgramLibrary { // we have a key for shader source code generation, a key for its further processing to work with // uniform buffers, and a final key to get the processed shader from the cache const generationKey = generator.generateKey(options); - const processingKey = JSON.stringify(processingOptions); + const processingKey = processingOptions.generateKey(); const totalKey = `${generationKey}#${processingKey}`; // do we have final processed shader @@ -133,7 +133,7 @@ class ProgramLibrary { // create a shader definition for the shader that will include the processingOptions const shaderDefinition = { - name: name, + name: `${generatedShaderDef.name}-processed`, attributes: generatedShaderDef.attributes, vshader: generatedShaderDef.vshader, fshader: generatedShaderDef.fshader, diff --git a/src/scene/shader-lib/utils.js b/src/scene/shader-lib/utils.js index f566c7a580b..878b22f9179 100644 --- a/src/scene/shader-lib/utils.js +++ b/src/scene/shader-lib/utils.js @@ -57,7 +57,52 @@ function createShaderFromCode(device, vsCode, fsCode, uniqueName, attributes, us return shader; } +/** + * Process shader using shader processing options, utilizing cache of the ProgramLibrary + * + * @param {Shader} shader - The shader to be processed. + * @param {import('../../platform/graphics/shader-processor-options.js').ShaderProcessorOptions} processingOptions - + * The shader processing options. + * @returns {Shader} The processed shader. + * @ignore + */ +function processShader(shader, processingOptions) { + + Debug.assert(shader); + const shaderDefinition = shader.definition; + + // 'shader' generator for a material - simply return existing shader definition. Use generator and getProgram + // to allow for shader processing to be cached + const name = shaderDefinition.name ?? 'shader'; + const key = `${name}-id-${shader.id}`; + const materialGenerator = { + generateKey: function (options) { + // unique name based of the shader id + return key; + }, + + createShaderDefinition: function (device, options) { + return shaderDefinition; + } + }; + + // temporarily register the program generator + const libraryModuleName = 'shader'; + const library = getProgramLibrary(shader.device); + Debug.assert(!library.isRegistered(libraryModuleName)); + library.register(libraryModuleName, materialGenerator); + + // generate shader variant - its the same shader, but with different processing options + const variant = library.getProgram(libraryModuleName, {}, processingOptions); + + // unregister it again + library.unregister(libraryModuleName); + + return variant; +} + + shaderChunks.createShader = createShader; shaderChunks.createShaderFromCode = createShaderFromCode; -export { createShader, createShaderFromCode }; +export { createShader, createShaderFromCode, processShader }; From 50991c833eabdc79ca9f8fd3e9693596eb784123 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Thu, 19 Jan 2023 16:24:28 +0000 Subject: [PATCH 2/2] add debug name to the render pass --- src/scene/graphics/quad-render-utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scene/graphics/quad-render-utils.js b/src/scene/graphics/quad-render-utils.js index d398924505d..b68b7c6d6c2 100644 --- a/src/scene/graphics/quad-render-utils.js +++ b/src/scene/graphics/quad-render-utils.js @@ -1,4 +1,4 @@ -import { Debug } from '../../core/debug.js'; +import { Debug, DebugHelper } from '../../core/debug.js'; import { Vec4 } from '../../core/math/vec4.js'; import { CULLFACE_NONE, DEVICETYPE_WEBGPU } from '../../platform/graphics/constants.js'; @@ -60,6 +60,7 @@ function drawQuadWithShader(device, target, shader, rect, scissorRect, useBlend const renderPass = new RenderPass(device, () => { quad.render(rect, scissorRect); }); + DebugHelper.setName(renderPass, `RenderPass-drawQuadWithShader${target ? `-${target.name}` : ''}`); renderPass.init(target); renderPass.colorOps.clear = false; renderPass.depthStencilOps.clearDepth = false;