diff --git a/examples/src/examples/graphics/integer-textures.example.mjs b/examples/src/examples/graphics/integer-textures.example.mjs index 35ffbe587fd..d833675f33e 100644 --- a/examples/src/examples/graphics/integer-textures.example.mjs +++ b/examples/src/examples/graphics/integer-textures.example.mjs @@ -127,31 +127,29 @@ const outputRenderTarget = createPixelRenderTarget(2, outputTexture); // This is shader runs the sand simulation // It uses integer textures to store the state of each pixel -const sandShader = pc.createShaderFromCode( - device, - pc.RenderPassShaderQuad.quadVertexShader, - files['sandSimulation.frag'], - 'SandShader', - { aPosition: pc.SEMANTIC_POSITION }, +const sandShader = pc.ShaderUtils.createShader(device, { + uniqueName: 'SandShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.RenderPassShaderQuad.quadVertexShader, + fragmentGLSL: files['sandSimulation.frag'], // Note that we are changing the shader output type to 'uint' // This means we only have to return a single integer value from the shader, // whereas the default is to return a vec4. This option allows you to pass // an array of types to specify the output type for each color attachment. // Unspecified types are assumed to be 'vec4'. - { fragmentOutputTypes: ['uint'] } -); + fragmentOutputTypes: ['uint'] +}); // This shader reads the integer textures // and renders a visual representation of the simulation -const outputShader = pc.createShaderFromCode( - device, - pc.RenderPassShaderQuad.quadVertexShader, - files['renderOutput.frag'], - 'RenderOutputShader', - { aPosition: pc.SEMANTIC_POSITION } +const outputShader = pc.ShaderUtils.createShader(device, { + uniqueName: 'RenderOutputShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.RenderPassShaderQuad.quadVertexShader, + fragmentGLSL: files['renderOutput.frag'] // For the output shader, we don't need to specify the output type, // as we are returning a vec4 by default. -); +}); // Write the initial simulation state to the integer texture const resetData = () => { diff --git a/scripts/posteffects/posteffect-blend.js b/scripts/posteffects/posteffect-blend.js index ba262fe0ef9..ce063880705 100644 --- a/scripts/posteffects/posteffect-blend.js +++ b/scripts/posteffects/posteffect-blend.js @@ -27,8 +27,11 @@ function BlendEffect(graphicsDevice) { '}' ].join('\n'); - this.shader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'BlendShader', { - aPosition: pc.SEMANTIC_POSITION + this.shader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'BlendShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fshader }); // Uniforms diff --git a/scripts/posteffects/posteffect-bloom.js b/scripts/posteffects/posteffect-bloom.js index 001ed24c17b..7797f7f728e 100644 --- a/scripts/posteffects/posteffect-bloom.js +++ b/scripts/posteffects/posteffect-bloom.js @@ -137,9 +137,26 @@ function BloomEffect(graphicsDevice) { '}' ].join('\n'); - this.extractShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, extractFrag, 'BloomExtractShader', attributes); - this.blurShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, gaussianBlurFrag, 'BloomBlurShader', attributes); - this.combineShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, combineFrag, 'BloomCombineShader', attributes); + this.extractShader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'BloomExtractShader', + attributes: attributes, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: extractFrag + }); + + this.blurShader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'BloomBlurShader', + attributes: attributes, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: gaussianBlurFrag + }); + + this.combineShader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'BloomCombineShader', + attributes: attributes, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: combineFrag + }); this.targets = []; diff --git a/scripts/posteffects/posteffect-bokeh.js b/scripts/posteffects/posteffect-bokeh.js index b74b4e871fe..c96d4cba9bb 100644 --- a/scripts/posteffects/posteffect-bokeh.js +++ b/scripts/posteffects/posteffect-bokeh.js @@ -96,8 +96,11 @@ function BokehEffect(graphicsDevice) { '}' ].join('\n'); - this.shader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'BokehShader', { - aPosition: pc.SEMANTIC_POSITION + this.shader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'BokehShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fshader }); // Uniforms diff --git a/scripts/posteffects/posteffect-brightnesscontrast.js b/scripts/posteffects/posteffect-brightnesscontrast.js index dd8b82d400f..55c977c9f40 100644 --- a/scripts/posteffects/posteffect-brightnesscontrast.js +++ b/scripts/posteffects/posteffect-brightnesscontrast.js @@ -32,8 +32,11 @@ function BrightnessContrastEffect(graphicsDevice) { '}' ].join('\n'); - this.shader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'BrightnessContrastShader', { - aPosition: pc.SEMANTIC_POSITION + this.shader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'BrightnessContrastShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fshader }); // Uniforms diff --git a/scripts/posteffects/posteffect-edgedetect.js b/scripts/posteffects/posteffect-edgedetect.js index e651d31e81c..c6083fbf9c5 100644 --- a/scripts/posteffects/posteffect-edgedetect.js +++ b/scripts/posteffects/posteffect-edgedetect.js @@ -52,8 +52,11 @@ function EdgeDetectEffect(graphicsDevice) { '}' ].join('\n'); - this.shader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'EdgeDetectShader', { - aPosition: pc.SEMANTIC_POSITION + this.shader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'EdgeDetectShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fshader }); // Uniforms diff --git a/scripts/posteffects/posteffect-fxaa.js b/scripts/posteffects/posteffect-fxaa.js index 5c0cc2f6f5d..a9486f88306 100644 --- a/scripts/posteffects/posteffect-fxaa.js +++ b/scripts/posteffects/posteffect-fxaa.js @@ -70,8 +70,11 @@ function FxaaEffect(graphicsDevice) { '}' ].join('\n'); - this.fxaaShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fxaaFrag, 'FxaaShader', { - aPosition: pc.SEMANTIC_POSITION + this.fxaaShader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'FxaaShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fxaaFrag }); // Uniforms diff --git a/scripts/posteffects/posteffect-horizontaltiltshift.js b/scripts/posteffects/posteffect-horizontaltiltshift.js index 6583d693056..530e52120db 100644 --- a/scripts/posteffects/posteffect-horizontaltiltshift.js +++ b/scripts/posteffects/posteffect-horizontaltiltshift.js @@ -37,8 +37,11 @@ function HorizontalTiltShiftEffect(graphicsDevice) { '}' ].join('\n'); - this.shader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'HorizontalTiltShiftShader', { - aPosition: pc.SEMANTIC_POSITION + this.shader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'HorizontalTiltShiftShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fshader }); // uniforms diff --git a/scripts/posteffects/posteffect-huesaturation.js b/scripts/posteffects/posteffect-huesaturation.js index 350b37daa18..4c7b5df8029 100644 --- a/scripts/posteffects/posteffect-huesaturation.js +++ b/scripts/posteffects/posteffect-huesaturation.js @@ -44,8 +44,11 @@ function HueSaturationEffect(graphicsDevice) { '}' ].join('\n'); - this.shader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'HueSaturationShader', { - aPosition: pc.SEMANTIC_POSITION + this.shader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'HueSaturationShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fshader }); // uniforms diff --git a/scripts/posteffects/posteffect-luminosity.js b/scripts/posteffects/posteffect-luminosity.js index 65877b39a7a..27b9266f7c1 100644 --- a/scripts/posteffects/posteffect-luminosity.js +++ b/scripts/posteffects/posteffect-luminosity.js @@ -23,8 +23,11 @@ function LuminosityEffect(graphicsDevice) { '}' ].join('\n'); - this.shader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'LuminosityShader', { - aPosition: pc.SEMANTIC_POSITION + this.shader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'LuminosityShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fshader }); } diff --git a/scripts/posteffects/posteffect-outline.js b/scripts/posteffects/posteffect-outline.js index 3020a83a60b..44aa1b528e4 100644 --- a/scripts/posteffects/posteffect-outline.js +++ b/scripts/posteffects/posteffect-outline.js @@ -46,8 +46,11 @@ function OutlineEffect(graphicsDevice, thickness) { '}' ].join('\n'); - this.shader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'OutlineShader', { - aPosition: pc.SEMANTIC_POSITION + this.shader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'OutlineShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fshader }); // Uniforms diff --git a/scripts/posteffects/posteffect-sepia.js b/scripts/posteffects/posteffect-sepia.js index 2e05b6e4bec..eb60f46486a 100644 --- a/scripts/posteffects/posteffect-sepia.js +++ b/scripts/posteffects/posteffect-sepia.js @@ -29,8 +29,11 @@ function SepiaEffect(graphicsDevice) { '}' ].join('\n'); - this.shader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'SepiaShader', { - aPosition: pc.SEMANTIC_POSITION + this.shader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'SepiaShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fshader }); // Uniforms diff --git a/scripts/posteffects/posteffect-ssao.js b/scripts/posteffects/posteffect-ssao.js index 2b6f20038dc..e14a737b736 100644 --- a/scripts/posteffects/posteffect-ssao.js +++ b/scripts/posteffects/posteffect-ssao.js @@ -316,9 +316,27 @@ function SSAOEffect(graphicsDevice, ssaoScript) { aPosition: pc.SEMANTIC_POSITION }; - this.ssaoShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fSsao, 'SsaoShader', attributes); - this.blurShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fblur, 'SsaoBlurShader', attributes); - this.outputShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, foutput, 'SsaoOutputShader', attributes); + this.ssaoShader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'SsaoShader', + attributes: attributes, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fSsao + }); + + this.blurShader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'SsaoBlurShader', + attributes: attributes, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fblur + }); + + this.outputShader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'SsaoOutputShader', + attributes: attributes, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: foutput + }); + // Uniforms this.radius = 4; diff --git a/scripts/posteffects/posteffect-verticaltiltshift.js b/scripts/posteffects/posteffect-verticaltiltshift.js index 203fec6d4a4..0d44835699b 100644 --- a/scripts/posteffects/posteffect-verticaltiltshift.js +++ b/scripts/posteffects/posteffect-verticaltiltshift.js @@ -37,8 +37,11 @@ function VerticalTiltShiftEffect(graphicsDevice) { '}' ].join('\n'); - this.shader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, fshader, 'VerticalTiltShiftShader', { - aPosition: pc.SEMANTIC_POSITION + this.shader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'VerticalTiltShiftShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: fshader }); // uniforms diff --git a/scripts/posteffects/posteffect-vignette.js b/scripts/posteffects/posteffect-vignette.js index e4e8863f5e4..144cfc3bcce 100644 --- a/scripts/posteffects/posteffect-vignette.js +++ b/scripts/posteffects/posteffect-vignette.js @@ -27,8 +27,11 @@ function VignetteEffect(graphicsDevice) { '}' ].join('\n'); - this.vignetteShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, luminosityFrag, 'VignetteShader', { - aPosition: pc.SEMANTIC_POSITION + this.vignetteShader = pc.ShaderUtils.createShader(graphicsDevice, { + uniqueName: 'VignetteShader', + attributes: { aPosition: pc.SEMANTIC_POSITION }, + vertexGLSL: pc.PostEffect.quadVertexShader, + fragmentGLSL: luminosityFrag }); this.offset = 1; diff --git a/src/extras/exporters/core-exporter.js b/src/extras/exporters/core-exporter.js index 47ad70e15e7..1a7c4b79cfa 100644 --- a/src/extras/exporters/core-exporter.js +++ b/src/extras/exporters/core-exporter.js @@ -1,11 +1,11 @@ -import { createShaderFromCode } from '../../scene/shader-lib/utils.js'; +import { ShaderUtils } from '../../scene/shader-lib/shader-utils.js'; import { Texture } from '../../platform/graphics/texture.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { drawQuadWithShader } from '../../scene/graphics/quad-render-utils.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; import { FILTER_LINEAR, ADDRESS_CLAMP_TO_EDGE, isCompressedPixelFormat, PIXELFORMAT_RGBA8, - SEMANTIC_POSITION, SHADERLANGUAGE_WGSL, SHADERLANGUAGE_GLSL + SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js'; import { shaderChunksWGSL } from '../../scene/shader-lib/chunks-wgsl/chunks-wgsl.js'; @@ -102,14 +102,15 @@ class CoreExporter { depth: false }); - // render to a render target using a blit shader - const shader = device.isWebGPU ? - createShaderFromCode(device, shaderChunksWGSL.fullscreenQuadVS, shaderChunksWGSL.outputTex2DPS, 'ShaderCoreExporterBlit', - { vertex_position: SEMANTIC_POSITION }, { shaderLanguage: SHADERLANGUAGE_WGSL } - ) : - createShaderFromCode(device, shaderChunks.fullscreenQuadVS, shaderChunks.outputTex2DPS, 'ShaderCoreExporterBlit', - { vertex_position: SEMANTIC_POSITION }, { shaderLanguage: SHADERLANGUAGE_GLSL } - ); + const shader = ShaderUtils.createShader(device, { + uniqueName: 'ShaderCoreExporterBlit', + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.fullscreenQuadVS, + fragmentGLSL: shaderChunks.outputTex2DPS, + vertexWGSL: shaderChunksWGSL.fullscreenQuadVS, + fragmentWGSL: shaderChunksWGSL.outputTex2DPS + }); + device.scope.resolve('source').setValue(texture); device.setBlendState(BlendState.NOBLEND); drawQuadWithShader(device, renderTarget, shader); diff --git a/src/extras/renderers/outline-renderer.js b/src/extras/renderers/outline-renderer.js index 43f57759711..e064d3a5ebc 100644 --- a/src/extras/renderers/outline-renderer.js +++ b/src/extras/renderers/outline-renderer.js @@ -5,9 +5,7 @@ import { ADDRESS_CLAMP_TO_EDGE, BLENDEQUATION_ADD, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDMODE_SRC_ALPHA, CULLFACE_NONE, FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, PIXELFORMAT_SRGBA8, - SEMANTIC_POSITION, - SHADERLANGUAGE_GLSL, - SHADERLANGUAGE_WGSL + SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; @@ -18,7 +16,7 @@ import { StandardMaterialOptions } from '../../scene/materials/standard-material import { StandardMaterial } from '../../scene/materials/standard-material.js'; import { shaderChunksWGSL } from '../../scene/shader-lib/chunks-wgsl/chunks-wgsl.js'; import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js'; -import { createShaderFromCode } from '../../scene/shader-lib/utils.js'; +import { ShaderUtils } from '../../scene/shader-lib/shader-utils.js'; /** * @import { AppBase } from '../../framework/app-base.js' @@ -117,13 +115,22 @@ class OutlineRenderer { this.blendState = new BlendState(true, BLENDEQUATION_ADD, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA); const device = this.app.graphicsDevice; - this.shaderExtend = createShaderFromCode(device, shaderChunks.fullscreenQuadVS, shaderOutlineExtendPS, 'OutlineExtendShader'); - this.shaderBlend = device.isWebGPU ? - createShaderFromCode(device, shaderChunksWGSL.fullscreenQuadVS, shaderChunksWGSL.outputTex2DPS, 'OutlineBlendShader', - { vertex_position: SEMANTIC_POSITION }, { shaderLanguage: SHADERLANGUAGE_WGSL }) : - createShaderFromCode(device, shaderChunks.fullscreenQuadVS, shaderChunks.outputTex2DPS, 'OutlineBlendShader', - { vertex_position: SEMANTIC_POSITION }, { shaderLanguage: SHADERLANGUAGE_GLSL }); + this.shaderExtend = ShaderUtils.createShader(device, { + uniqueName: 'OutlineExtendShader', + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.fullscreenQuadVS, + fragmentGLSL: shaderOutlineExtendPS + }); + + this.shaderBlend = ShaderUtils.createShader(device, { + uniqueName: 'OutlineBlendShader', + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.fullscreenQuadVS, + fragmentGLSL: shaderChunks.outputTex2DPS, + vertexWGSL: shaderChunksWGSL.fullscreenQuadVS, + fragmentWGSL: shaderChunksWGSL.outputTex2DPS + }); this.quadRenderer = new QuadRender(this.shaderBlend); diff --git a/src/framework/lightmapper/lightmap-filters.js b/src/framework/lightmapper/lightmap-filters.js index 6fbdc34c21a..dea2f2317f2 100644 --- a/src/framework/lightmapper/lightmap-filters.js +++ b/src/framework/lightmapper/lightmap-filters.js @@ -1,7 +1,7 @@ -import { createShaderFromCode } from '../../scene/shader-lib/utils.js'; +import { ShaderUtils } from '../../scene/shader-lib/shader-utils.js'; import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js'; import { shaderChunksWGSL } from '../../scene/shader-lib/chunks-wgsl/chunks-wgsl.js'; -import { SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from '../../platform/graphics/constants.js'; +import { SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; // size of the kernel - needs to match the constant in the shader const DENOISE_FILTER_SIZE = 15; @@ -41,18 +41,21 @@ class LightmapFilters { const index = bakeHDR ? 0 : 1; if (!this.shaderDenoise[index]) { - const wgsl = this.device.isWebGPU; - const chunks = wgsl ? shaderChunksWGSL : shaderChunks; - const name = `lmBilateralDeNoise-${bakeHDR ? 'hdr' : 'rgbm'}`; const defines = new Map(); defines.set('{MSIZE}', 15); if (bakeHDR) defines.set('HDR', ''); - this.shaderDenoise[index] = createShaderFromCode(this.device, chunks.fullscreenQuadVS, chunks.bilateralDeNoisePS, name, { vertex_position: SEMANTIC_POSITION }, undefined, { - shaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL, + this.shaderDenoise[index] = ShaderUtils.createShader(this.device, { + uniqueName: `lmBilateralDeNoise-${bakeHDR ? 'hdr' : 'rgbm'}`, + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.fullscreenQuadVS, + vertexWGSL: shaderChunksWGSL.fullscreenQuadVS, + fragmentGLSL: shaderChunks.bilateralDeNoisePS, + fragmentWGSL: shaderChunksWGSL.bilateralDeNoisePS, fragmentDefines: defines }); + this.sigmas = new Float32Array(2); this.constantSigmas = this.device.scope.resolve('sigmas'); this.constantKernel = this.device.scope.resolve('kernel[0]'); @@ -74,12 +77,14 @@ class LightmapFilters { getDilate(device, bakeHDR) { const index = bakeHDR ? 0 : 1; if (!this.shaderDilate[index]) { - const wgsl = this.device.isWebGPU; - const chunks = wgsl ? shaderChunksWGSL : shaderChunks; - const name = `lmDilate-${bakeHDR ? 'hdr' : 'rgbm'}`; const define = bakeHDR ? '#define HDR\n' : ''; - this.shaderDilate[index] = createShaderFromCode(device, chunks.fullscreenQuadVS, define + chunks.dilatePS, name, { vertex_position: SEMANTIC_POSITION }, undefined, { - shaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL + this.shaderDilate[index] = ShaderUtils.createShader(device, { + uniqueName: `lmDilate-${bakeHDR ? 'hdr' : 'rgbm'}`, + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.fullscreenQuadVS, + vertexWGSL: shaderChunksWGSL.fullscreenQuadVS, + fragmentGLSL: define + shaderChunks.dilatePS, + fragmentWGSL: define + shaderChunksWGSL.dilatePS }); } return this.shaderDilate[index]; diff --git a/src/index.js b/src/index.js index 236cb540b60..8ada0499845 100644 --- a/src/index.js +++ b/src/index.js @@ -223,7 +223,7 @@ export { FogParams } from './scene/fog-params.js'; export { RenderPassForward } from './scene/renderer/render-pass-forward.js'; // SCENE / SHADER-LIB -export { createShader, createShaderFromCode } from './scene/shader-lib/utils.js'; +export { ShaderUtils, createShader, createShaderFromCode } from './scene/shader-lib/shader-utils.js'; export { LitShaderOptions } from './scene/shader-lib/programs/lit-shader-options.js'; export { ProgramLibrary } from './scene/shader-lib/program-library.js'; export { shaderChunks } from './scene/shader-lib/chunks/chunks.js'; diff --git a/src/platform/graphics/shader.js b/src/platform/graphics/shader.js index 46161c49b47..a1df8e5eac0 100644 --- a/src/platform/graphics/shader.js +++ b/src/platform/graphics/shader.js @@ -53,7 +53,7 @@ class Shader { /** * Creates a new Shader instance. * - * Consider {@link createShaderFromCode} as a simpler and more powerful way to create + * Consider {@link ShaderUtils#createShader} as a simpler and more powerful way to create * a shader. * * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this shader. diff --git a/src/scene/graphics/quad-render.js b/src/scene/graphics/quad-render.js index cfd78623b1b..0ffe0d43560 100644 --- a/src/scene/graphics/quad-render.js +++ b/src/scene/graphics/quad-render.js @@ -5,7 +5,7 @@ import { BINDGROUP_MESH, BINDGROUP_MESH_UB, BINDGROUP_VIEW, PRIMITIVE_TRISTRIP } 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'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; /** * @import { Shader } from '../../platform/graphics/shader.js' @@ -28,7 +28,12 @@ const _dynamicBindGroup = new DynamicBindGroup(); * Example: * * ```javascript - * const shader = pc.createShaderFromCode(app.graphicsDevice, vertexShader, fragmentShader, `MyShader`); + * const shader = pc.ShaderUtils.createShader(app.graphicsDevice, { + * uniqueName: 'MyShader', + * attributes: { aPosition: SEMANTIC_POSITION }, + * vertexGLSL: '// vertex shader code', + * fragmentGLSL: '// fragment shader code' + * }); * const quad = new QuadRender(shader); * quad.render(); * quad.destroy(); @@ -64,7 +69,7 @@ class QuadRender { // add uniform buffer support to shader const processingOptions = new ShaderProcessorOptions(); - this.shader = processShader(shader, processingOptions); + this.shader = ShaderUtils.processShader(shader, processingOptions); // uniform buffer const ubFormat = this.shader.meshUniformBufferFormat; diff --git a/src/scene/graphics/render-pass-shader-quad.js b/src/scene/graphics/render-pass-shader-quad.js index 47385930a99..c414c9bbdd2 100644 --- a/src/scene/graphics/render-pass-shader-quad.js +++ b/src/scene/graphics/render-pass-shader-quad.js @@ -3,7 +3,7 @@ import { BlendState } from '../../platform/graphics/blend-state.js'; import { CULLFACE_NONE, SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; -import { createShaderFromCode } from '../shader-lib/utils.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; /** * @import { Shader } from '../../platform/graphics/shader.js' @@ -109,14 +109,18 @@ class RenderPassShaderQuad extends RenderPass { * @returns {object} Returns the created shader. */ createQuadShader(name, fs, shaderDefinitionOptions = {}) { - return createShaderFromCode( - this.device, - RenderPassShaderQuad.quadVertexShader, - fs, - name, - { aPosition: SEMANTIC_POSITION }, - shaderDefinitionOptions - ); + return ShaderUtils.createShader(this.device, { + uniqueName: name, + attributes: { aPosition: SEMANTIC_POSITION }, + useTransformFeedback: shaderDefinitionOptions.useTransformFeedback, + vertexGLSL: RenderPassShaderQuad.quadVertexShader, + vertexIncludes: shaderDefinitionOptions.vertexIncludes, + vertexDefines: shaderDefinitionOptions.vertexDefines, + fragmentGLSL: fs, + fragmentIncludes: shaderDefinitionOptions.fragmentIncludes, + fragmentDefines: shaderDefinitionOptions.fragmentDefines, + fragmentOutputTypes: shaderDefinitionOptions.fragmentOutputTypes + }); } execute() { diff --git a/src/scene/graphics/reproject-texture.js b/src/scene/graphics/reproject-texture.js index f44ce082cb8..3b3e94b6f84 100644 --- a/src/scene/graphics/reproject-texture.js +++ b/src/scene/graphics/reproject-texture.js @@ -4,8 +4,6 @@ import { Vec3 } from '../../core/math/vec3.js'; import { FILTER_NEAREST, TEXTUREPROJECTION_OCTAHEDRAL, TEXTUREPROJECTION_CUBE, - SHADERLANGUAGE_WGSL, - SHADERLANGUAGE_GLSL, SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; @@ -15,7 +13,7 @@ import { Texture } from '../../platform/graphics/texture.js'; import { ChunkUtils } from '../shader-lib/chunk-utils.js'; import { shaderChunks } from '../shader-lib/chunks/chunks.js'; import { getProgramLibrary } from '../shader-lib/get-program-library.js'; -import { createShaderFromCode } from '../shader-lib/utils.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { drawQuadWithShader } from './quad-render-utils.js'; import { shaderChunksWGSL } from '../shader-lib/chunks-wgsl/chunks-wgsl.js'; @@ -425,23 +423,22 @@ function reprojectTexture(source, target, options = {}) { const numSamples = options.hasOwnProperty('numSamples') ? options.numSamples : 1024; // generate unique shader key - const shaderKey = `${processFunc}_${decodeFunc}_${encodeFunc}_${sourceFunc}_${targetFunc}_${numSamples}`; + const shaderKey = `ReprojectShader:${processFunc}_${decodeFunc}_${encodeFunc}_${sourceFunc}_${targetFunc}_${numSamples}`; const device = source.device; let shader = getProgramLibrary(device).getCachedShader(shaderKey); if (!shader) { - const defines = ` - ${prefilterSamples ? '#define USE_SAMPLES_TEX' : ''} - ${source.cubemap ? '#define CUBEMAP_SOURCE' : ''} - #define {PROCESS_FUNC} ${processFunc} - #define {DECODE_FUNC} ${decodeFunc} - #define {ENCODE_FUNC} ${encodeFunc} - #define {SOURCE_FUNC} ${sourceFunc} - #define {TARGET_FUNC} ${targetFunc} - #define {NUM_SAMPLES} ${numSamples} - #define {NUM_SAMPLES_SQRT} ${Math.round(Math.sqrt(numSamples)).toFixed(1)} - `; + const defines = new Map(); + if (prefilterSamples) defines.set('USE_SAMPLES_TEX', ''); + if (source.cubemap) defines.set('CUBEMAP_SOURCE', ''); + defines.set('{PROCESS_FUNC}', processFunc); + defines.set('{DECODE_FUNC}', decodeFunc); + defines.set('{ENCODE_FUNC}', encodeFunc); + defines.set('{SOURCE_FUNC}', sourceFunc); + defines.set('{TARGET_FUNC}', targetFunc); + defines.set('{NUM_SAMPLES}', numSamples); + defines.set('{NUM_SAMPLES_SQRT}', Math.round(Math.sqrt(numSamples)).toFixed(1)); const wgsl = device.isWebGPU; const chunks = wgsl ? shaderChunksWGSL : shaderChunks; @@ -449,22 +446,16 @@ function reprojectTexture(source, target, options = {}) { includes.set('decodePS', chunks.decodePS); includes.set('encodePS', chunks.encodePS); - const vert = chunks.reprojectVS; - const frag = chunks.reprojectPS; - shader = createShaderFromCode( - device, - vert, - ` - ${defines} - ${frag} - `, - shaderKey, { - vertex_position: SEMANTIC_POSITION - }, { - fragmentIncludes: includes, - shaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL - } - ); + shader = ShaderUtils.createShader(device, { + uniqueName: shaderKey, + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.reprojectVS, + vertexWGSL: shaderChunksWGSL.reprojectVS, + fragmentGLSL: shaderChunks.reprojectPS, + fragmentWGSL: shaderChunksWGSL.reprojectPS, + fragmentIncludes: includes, + fragmentDefines: defines + }); } DebugGraphics.pushGpuMarker(device, 'ReprojectTexture'); diff --git a/src/scene/materials/lit-material.js b/src/scene/materials/lit-material.js index 854798ef90a..d67b65af07b 100644 --- a/src/scene/materials/lit-material.js +++ b/src/scene/materials/lit-material.js @@ -5,7 +5,7 @@ import { LitMaterialOptions } from './lit-material-options.js'; import { LitMaterialOptionsBuilder } from './lit-material-options-builder.js'; import { getProgramLibrary } from '../shader-lib/get-program-library.js'; import { lit } from '../shader-lib/programs/lit.js'; -import { getCoreDefines } from '../shader-lib/utils.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; const options = new LitMaterialOptions(); @@ -92,7 +92,7 @@ class LitMaterial extends Material { options.usedUvs = this.usedUvs.slice(); options.shaderChunkGLSL = this.shaderChunkGLSL; options.shaderChunkWGSL = this.shaderChunkWGSL; - options.defines = getCoreDefines(this, params); + options.defines = ShaderUtils.getCoreDefines(this, params); LitMaterialOptionsBuilder.update(options.litOptions, this, params.scene, params.cameraShaderParams, params.objDefs, params.pass, params.sortedLights); const processingOptions = new ShaderProcessorOptions(params.viewUniformFormat, params.viewBindGroupFormat, params.vertexFormat); diff --git a/src/scene/materials/shader-material.js b/src/scene/materials/shader-material.js index 29fbaad571e..05e7580ec70 100644 --- a/src/scene/materials/shader-material.js +++ b/src/scene/materials/shader-material.js @@ -4,7 +4,7 @@ import { ShaderProcessorOptions } from '../../platform/graphics/shader-processor import { SHADERDEF_INSTANCING, SHADERDEF_MORPH_NORMAL, SHADERDEF_MORPH_POSITION, SHADERDEF_MORPH_TEXTURE_BASED_INT, SHADERDEF_SKIN } from '../constants.js'; import { getProgramLibrary } from '../shader-lib/get-program-library.js'; import { shaderGeneratorShader } from '../shader-lib/programs/shader-generator-shader.js'; -import { getCoreDefines } from '../shader-lib/utils.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; import { Material } from './material.js'; /** @@ -132,7 +132,7 @@ class ShaderMaterial extends Material { const { objDefs } = params; const options = { - defines: getCoreDefines(this, params), + defines: ShaderUtils.getCoreDefines(this, params), skin: (objDefs & SHADERDEF_SKIN) !== 0, useInstancing: (objDefs & SHADERDEF_INSTANCING) !== 0, useMorphPosition: (objDefs & SHADERDEF_MORPH_POSITION) !== 0, diff --git a/src/scene/materials/standard-material.js b/src/scene/materials/standard-material.js index 3210f07fa79..ed6e1c03318 100644 --- a/src/scene/materials/standard-material.js +++ b/src/scene/materials/standard-material.js @@ -21,7 +21,7 @@ import { Material } from './material.js'; import { StandardMaterialOptionsBuilder } from './standard-material-options-builder.js'; import { standardMaterialCubemapParameters, standardMaterialTextureParameters } from './standard-material-parameters.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; -import { getCoreDefines } from '../shader-lib/utils.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; /** * @import { BoundingBox } from '../../core/shape/bounding-box.js' @@ -837,7 +837,7 @@ class StandardMaterial extends Material { const shaderPassInfo = ShaderPass.get(device).getByIndex(pass); const minimalOptions = pass === SHADER_PICK || pass === SHADER_PREPASS || shaderPassInfo.isShadow; let options = minimalOptions ? standard.optionsContextMin : standard.optionsContext; - options.defines = getCoreDefines(this, params); + options.defines = ShaderUtils.getCoreDefines(this, params); if (minimalOptions) { this.shaderOptBuilder.updateMinRef(options, scene, this, objDefs, pass, sortedLights); diff --git a/src/scene/morph-instance.js b/src/scene/morph-instance.js index 5959bcb1732..0b13a6aa684 100644 --- a/src/scene/morph-instance.js +++ b/src/scene/morph-instance.js @@ -1,9 +1,9 @@ import { Debug } from '../core/debug.js'; -import { SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from '../platform/graphics/constants.js'; +import { SEMANTIC_POSITION } from '../platform/graphics/constants.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'; -import { createShaderFromCode } from './shader-lib/utils.js'; +import { ShaderUtils } from './shader-lib/shader-utils.js'; import { BlendState } from '../platform/graphics/blend-state.js'; import { shaderChunks } from './shader-lib/chunks/chunks.js'; import { shaderChunksWGSL } from './shader-lib/chunks-wgsl/chunks-wgsl.js'; @@ -191,18 +191,19 @@ class MorphInstance { */ _createShader(maxCount) { - const wgsl = this.device.isWebGPU; - const chunks = wgsl ? shaderChunksWGSL : shaderChunks; - const defines = new Map(); defines.set('{MORPH_TEXTURE_MAX_COUNT}', maxCount); if (this.morph.intRenderFormat) defines.set('MORPH_INT', ''); const outputType = this.morph.intRenderFormat ? 'uvec4' : 'vec4'; - return createShaderFromCode(this.device, chunks.morphVS, chunks.morphPS, 'TextureMorphShader', { - vertex_position: SEMANTIC_POSITION - }, { - shaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL, + + return ShaderUtils.createShader(this.device, { + uniqueName: 'TextureMorphShader', + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.morphVS, + vertexWGSL: shaderChunksWGSL.morphVS, + fragmentGLSL: shaderChunks.morphPS, + fragmentWGSL: shaderChunksWGSL.morphPS, fragmentDefines: defines, fragmentOutputTypes: [outputType] }); diff --git a/src/scene/particle-system/particle-emitter.js b/src/scene/particle-system/particle-emitter.js index e8e6d691590..90a5396358b 100644 --- a/src/scene/particle-system/particle-emitter.js +++ b/src/scene/particle-system/particle-emitter.js @@ -21,8 +21,6 @@ import { typedArrayIndexFormats, requiresManualGamma, PIXELFORMAT_SRGBA8, - SHADERLANGUAGE_WGSL, - SHADERLANGUAGE_GLSL, SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; import { DeviceCache } from '../../platform/graphics/device-cache.js'; @@ -41,7 +39,7 @@ import { } from '../constants.js'; import { Mesh } from '../mesh.js'; import { MeshInstance } from '../mesh-instance.js'; -import { createShaderFromCode } from '../shader-lib/utils.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; import { shaderChunks } from '../shader-lib/chunks/chunks.js'; import { shaderChunksWGSL } from '../shader-lib/chunks-wgsl/chunks-wgsl.js'; import { ParticleCPUUpdater } from './cpu-updater.js'; @@ -361,8 +359,8 @@ class ParticleEmitter { this.worldBoundsMul = new Vec3(); this.worldBoundsAdd = new Vec3(); this.timeToSwitchBounds = 0; - // this.prevPos = new Vec3(); + // simulation shaders - do not destroy those, as they're cached and shared between emitters this.shaderParticleUpdateRespawn = null; this.shaderParticleUpdateNoRespawn = null; this.shaderParticleUpdateOnStop = null; @@ -596,7 +594,6 @@ class ParticleEmitter { this.resetWorldBounds(); if (this.node) { - // this.prevPos.copy(this.node.getPosition()); this.worldBounds.setFromTransformedAabb( this.localBounds, this.localSpace ? Mat4.IDENTITY : this.node.getWorldTransform()); @@ -665,40 +662,39 @@ class ParticleEmitter { if (this.localSpace) defines.set('LOCAL_SPACE', ''); if (this.pack8) defines.set('PACK8', ''); if (this.emitterShape === EMITTERSHAPE_BOX) defines.set('EMITTERSHAPE_BOX', ''); - - const shaderLanguage = gd.isWebGPU ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL; - const chunks = shaderLanguage === SHADERLANGUAGE_WGSL ? shaderChunksWGSL : shaderChunks; - const includes = new Map(Object.entries(chunks)); - - const shaderCodeRespawn = `#define RESPAWN\n ${chunks.particle_simulationPS}`; - const shaderCodeNoRespawn = `#define NO_RESPAWN\n ${chunks.particle_simulationPS}`; - const shaderCodeOnStop = `#define ON_STOP\n ${chunks.particle_simulationPS}`; - - // Note: createShaderFromCode can return a shader from the cache (not a new shader) so we *should not* delete these shaders - // when the particle emitter is destroyed - const params = `Shape:${this.emitterShape}-Pack:${this.pack8}-Local:${this.localSpace}`; - this.shaderParticleUpdateRespawn = createShaderFromCode(gd, chunks.fullscreenQuadVS, shaderCodeRespawn, `ParticleUpdateRespawn-${params}`, - { vertex_position: SEMANTIC_POSITION }, { - fragmentDefines: defines, - fragmentIncludes: includes, - shaderLanguage: shaderLanguage - } - ); - this.shaderParticleUpdateNoRespawn = createShaderFromCode(gd, chunks.fullscreenQuadVS, shaderCodeNoRespawn, `ParticleUpdateNoRespawn-${params}`, - { vertex_position: SEMANTIC_POSITION }, { - fragmentDefines: defines, - fragmentIncludes: includes, - shaderLanguage: shaderLanguage - } - ); - this.shaderParticleUpdateOnStop = createShaderFromCode(gd, chunks.fullscreenQuadVS, shaderCodeOnStop, `ParticleUpdateStop-${params}`, - { vertex_position: SEMANTIC_POSITION }, { - fragmentDefines: defines, - fragmentIncludes: includes, - shaderLanguage: shaderLanguage - } - ); - + const shaderUniqueId = `Shape:${this.emitterShape}-Pack:${this.pack8}-Local:${this.localSpace}`; + + const includes = new Map(Object.entries(gd.isWebGPU ? shaderChunksWGSL : shaderChunks)); + + // shader options shared by all 3 shaders + const shaderOptions = { + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.fullscreenQuadVS, + vertexWGSL: shaderChunksWGSL.fullscreenQuadVS, + fragmentGLSL: shaderChunks.particle_simulationPS, + fragmentWGSL: shaderChunksWGSL.particle_simulationPS, + fragmentDefines: defines, + fragmentIncludes: includes + }; + + // shader 1 + shaderOptions.uniqueName = `ParticleUpdateRespawn-${shaderUniqueId}`; + defines.set('RESPAWN', ''); + this.shaderParticleUpdateRespawn = ShaderUtils.createShader(gd, shaderOptions); + defines.delete('RESPAWN'); + + // shader 2 + shaderOptions.uniqueName = `ParticleUpdateNoRespawn-${shaderUniqueId}`; + defines.set('NO_RESPAWN', ''); + this.shaderParticleUpdateNoRespawn = ShaderUtils.createShader(gd, shaderOptions); + defines.delete('NO_RESPAWN'); + + // shader 3 + shaderOptions.uniqueName = `ParticleUpdateStop-${shaderUniqueId}`; + defines.set('ON_STOP', ''); + this.shaderParticleUpdateNoRespawn = ShaderUtils.createShader(gd, shaderOptions); + + // allocate various buffers this.numParticleVerts = this.useMesh ? this.mesh.vertexBuffer.numVertices : 4; this.numParticleIndices = this.useMesh ? this.mesh.indexBuffer[0].numIndices : 6; this._allocate(this.numParticles); diff --git a/src/scene/particle-system/particle-material.js b/src/scene/particle-system/particle-material.js index dce8882c31f..c2c0e46664f 100644 --- a/src/scene/particle-system/particle-material.js +++ b/src/scene/particle-system/particle-material.js @@ -10,7 +10,7 @@ import { import { getProgramLibrary } from '../shader-lib/get-program-library.js'; import { Material } from '../materials/material.js'; import { particle } from '../shader-lib/programs/particle.js'; -import { getCoreDefines } from '../shader-lib/utils.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; /** * @import { ParticleEmitter } from './particle-emitter.js' @@ -41,7 +41,7 @@ class ParticleMaterial extends Material { const { device, scene, cameraShaderParams, objDefs } = params; const { emitter } = this; const options = { - defines: getCoreDefines(this, params), + defines: ShaderUtils.getCoreDefines(this, params), pass: SHADER_FORWARD, useCpu: this.emitter.useCpu, normal: emitter.lighting ? ((emitter.normalMap !== null) ? 2 : 1) : 0, diff --git a/src/scene/renderer/render-pass-cookie-renderer.js b/src/scene/renderer/render-pass-cookie-renderer.js index c984ca4275f..5bdc7244f53 100644 --- a/src/scene/renderer/render-pass-cookie-renderer.js +++ b/src/scene/renderer/render-pass-cookie-renderer.js @@ -1,10 +1,10 @@ import { Debug } from '../../core/debug.js'; import { Vec4 } from '../../core/math/vec4.js'; import { Mat4 } from '../../core/math/mat4.js'; -import { CULLFACE_NONE, SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from '../../platform/graphics/constants.js'; +import { CULLFACE_NONE, SEMANTIC_POSITION } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI } from '../constants.js'; -import { createShaderFromCode } from '../shader-lib/utils.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; import { LightCamera } from './light-camera.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { QuadRender } from '../graphics/quad-render.js'; @@ -112,12 +112,13 @@ class RenderPassCookieRenderer extends RenderPass { get quadRenderer2D() { if (!this._quadRenderer2D) { - const wgsl = this.device.isWebGPU; - const chunks = wgsl ? shaderChunksWGSL : shaderChunks; - const shader = createShaderFromCode(this.device, chunks.cookieBlitVS, chunks.cookieBlit2DPS, 'cookieRenderer2d', { - vertex_position: SEMANTIC_POSITION - }, { - shaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL + const shader = ShaderUtils.createShader(this.device, { + uniqueName: 'cookieRenderer2d', + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.cookieBlitVS, + vertexWGSL: shaderChunksWGSL.cookieBlitVS, + fragmentGLSL: shaderChunks.cookieBlit2DPS, + fragmentWGSL: shaderChunksWGSL.cookieBlit2DPS }); this._quadRenderer2D = new QuadRender(shader); } @@ -126,12 +127,13 @@ class RenderPassCookieRenderer extends RenderPass { get quadRendererCube() { if (!this._quadRendererCube) { - const wgsl = this.device.isWebGPU; - const chunks = wgsl ? shaderChunksWGSL : shaderChunks; - const shader = createShaderFromCode(this.device, chunks.cookieBlitVS, chunks.cookieBlitCubePS, 'cookieRendererCube', { - vertex_position: SEMANTIC_POSITION - }, { - shaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL + const shader = ShaderUtils.createShader(this.device, { + uniqueName: 'cookieRendererCube', + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.cookieBlitVS, + vertexWGSL: shaderChunksWGSL.cookieBlitVS, + fragmentGLSL: shaderChunks.cookieBlitCubePS, + fragmentWGSL: shaderChunksWGSL.cookieBlitCubePS }); this._quadRendererCube = new QuadRender(shader); } diff --git a/src/scene/renderer/shadow-renderer.js b/src/scene/renderer/shadow-renderer.js index 1fdc2976bb6..3b4f61f5326 100644 --- a/src/scene/renderer/shadow-renderer.js +++ b/src/scene/renderer/shadow-renderer.js @@ -5,7 +5,7 @@ import { Mat4 } from '../../core/math/mat4.js'; import { Vec3 } from '../../core/math/vec3.js'; import { Vec4 } from '../../core/math/vec4.js'; import { - SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL, SHADERSTAGE_FRAGMENT, SHADERSTAGE_VERTEX, + SEMANTIC_POSITION, SHADERSTAGE_FRAGMENT, SHADERSTAGE_VERTEX, UNIFORMTYPE_MAT4, UNIFORM_BUFFER_DEFAULT_SLOT_NAME } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; @@ -19,7 +19,7 @@ import { } from '../constants.js'; import { ShaderPass } from '../shader-pass.js'; import { shaderChunks } from '../shader-lib/chunks/chunks.js'; -import { createShaderFromCode } from '../shader-lib/utils.js'; +import { ShaderUtils } from '../shader-lib/shader-utils.js'; import { LightCamera } from './light-camera.js'; import { UniformBufferFormat, UniformFormat } from '../../platform/graphics/uniform-buffer-format.js'; import { BindUniformBufferFormat, BindGroupFormat } from '../../platform/graphics/bind-group-format.js'; @@ -92,8 +92,6 @@ class ShadowRenderer { this.sourceId = scope.resolve('source'); this.pixelOffsetId = scope.resolve('pixelOffset'); this.weightId = scope.resolve('weight[0]'); - const chunks = this.device.isWebGPU ? shaderChunksWGSL : shaderChunks; - this.blurVsmShaderCode = [chunks.blurVSMPS, `#define GAUSS\n${chunks.blurVSMPS}`]; // cache for vsm blur shaders this.blurVsmShader = [{}, {}]; @@ -495,14 +493,20 @@ class ShadowRenderer { if (!blurShader) { this.blurVsmWeights[filterSize] = gaussWeights(filterSize); - const chunks = this.device.isWebGPU ? shaderChunksWGSL : shaderChunks; - const blurVS = chunks.fullscreenQuadVS; - let blurFS = `#define {SAMPLES} ${filterSize}\n`; - blurFS += this.blurVsmShaderCode[blurMode]; - const blurShaderName = `blurVsm${blurMode}${filterSize}`; - blurShader = createShaderFromCode(this.device, blurVS, blurFS, blurShaderName, { vertex_position: SEMANTIC_POSITION }, undefined, { - shaderLanguage: this.device.isWebGPU ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL + const defines = new Map(); + defines.set('{SAMPLES}', filterSize); + if (blurMode === 1) defines.set('GAUSS', ''); + + blurShader = ShaderUtils.createShader(this.device, { + uniqueName: `blurVsm${blurMode}${filterSize}`, + attributes: { vertex_position: SEMANTIC_POSITION }, + vertexGLSL: shaderChunks.fullscreenQuadVS, + vertexWGSL: shaderChunksWGSL.fullscreenQuadVS, + fragmentGLSL: shaderChunks.blurVSMPS, + fragmentWGSL: shaderChunksWGSL.blurVSMPS, + fragmentDefines: defines }); + cache[blurMode][filterSize] = blurShader; } diff --git a/src/scene/shader-lib/shader-utils.js b/src/scene/shader-lib/shader-utils.js new file mode 100644 index 00000000000..1a0daa9dde5 --- /dev/null +++ b/src/scene/shader-lib/shader-utils.js @@ -0,0 +1,191 @@ +import { Shader } from '../../platform/graphics/shader.js'; +import { ShaderDefinitionUtils } from '../../platform/graphics/shader-definition-utils.js'; +import { getProgramLibrary } from './get-program-library.js'; +import { Debug } from '../../core/debug.js'; +import { ShaderGenerator } from './programs/shader-generator.js'; +import { ShaderPass } from '../shader-pass.js'; +import { SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from '../../platform/graphics/constants.js'; + +/** + * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' + * @import { ShaderProcessorOptions } from '../../platform/graphics/shader-processor-options.js' + * @import { CameraShaderParams } from '../camera-shader-params.js' + * @import { Material, ShaderVariantParams } from '../materials/material.js' + */ + +class ShaderGeneratorPassThrough extends ShaderGenerator { + constructor(key, shaderDefinition) { + super(); + this.key = key; + this.shaderDefinition = shaderDefinition; + } + + generateKey(options) { + return this.key; + } + + createShaderDefinition(device, options) { + return this.shaderDefinition; + } +} + +class ShaderUtils { + /** + * Creates a shader. When the active graphics device is WebGL, the provided GLSL vertex and + * fragment source code is used. For WebGPU, if WGSL vertex and fragment source code is + * supplied, it is used directly; otherwise, the system automatically translates the provided + * GLSL code into WGSL. In the case of GLSL shaders, additional blocks are appended to both the + * vertex and fragment source code to support extended features and maintain compatibility. + * These additions include the shader version declaration, precision qualifiers, and commonly + * used extensions, and therefore should be excluded from the user-supplied GLSL source. + * + * @param {GraphicsDevice} device - The graphics device. + * @param {object} options - Object for passing optional arguments. + * @param {string} options.uniqueName - Unique name for the shader. If a shader with this name + * already exists, it will be returned instead of a new shader instance. + * @param {Object} options.attributes - Object detailing the mapping of vertex + * shader attribute names to semantics SEMANTIC_*. This enables the engine to match vertex + * buffer data to the shader attributes. + * @param {boolean} [options.useTransformFeedback] - Whether to use transform feedback. Defaults + * to false. Only supported by WebGL. + * @param {string} [options.vertexGLSL] - The vertex shader code in GLSL. + * @param {string} [options.vertexWGSL] - The vertex shader code in WGSL. + * @param {string} [options.fragmentGLSL] - The fragment shader code in GLSL. + * @param {string} [options.fragmentWGSL] - The fragment shader code in WGSL. + * @param {Map} [options.vertexIncludes] - A map containing key-value pairs of + * include names and their content. These are used for resolving #include directives in the + * vertex shader source. + * @param {Map} [options.vertexDefines] - A map containing key-value pairs of + * define names and their values. These are used for resolving #ifdef style of directives in the + * vertex code. + * @param {Map} [options.fragmentIncludes] - A map containing key-value pairs + * of include names and their content. These are used for resolving #include directives in the + * fragment shader source. + * @param {Map} [options.fragmentDefines] - A map containing key-value pairs of + * define names and their values. These are used for resolving #ifdef style of directives in the + * fragment code. + * @param {string | string[]} [options.fragmentOutputTypes] - Fragment shader output types, + * which default to vec4. Passing a string will set the output type for all color attachments. + * Passing an array will set the output type for each color attachment. + * @returns {Shader} The newly created shader. + */ + static createShader(device, options) { + + const programLibrary = getProgramLibrary(device); + let shader = programLibrary.getCachedShader(options.uniqueName); + if (!shader) { + const wgsl = device.isWebGPU && options.vertexWGSL && options.fragmentWGSL; + shader = new Shader(device, ShaderDefinitionUtils.createDefinition(device, { + name: options.uniqueName, + shaderLanguage: wgsl ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL, + attributes: options.attributes, + vertexCode: wgsl ? options.vertexWGSL : options.vertexGLSL, + fragmentCode: wgsl ? options.fragmentWGSL : options.fragmentGLSL, + useTransformFeedback: options.useTransformFeedback, + vertexIncludes: options.vertexIncludes, + vertexDefines: options.vertexDefines, + fragmentIncludes: options.fragmentIncludes, + fragmentDefines: options.fragmentDefines, + fragmentOutputTypes: options.fragmentOutputTypes + })); + programLibrary.setCachedShader(options.uniqueName, shader); + } + return shader; + } + + /** + * Create a map of defines used for shader generation for a material. + * + * @param {Material} material - The material to create the shader defines for. + * @param {ShaderVariantParams} params - The shader variant parameters. + * @returns {Map} The map of shader defines. + * @ignore + */ + static getCoreDefines(material, params) { + + // merge both maps, with camera shader params taking precedence + const defines = new Map(material.defines); + params.cameraShaderParams.defines.forEach((value, key) => defines.set(key, value)); + + // add pass defines + const shaderPassInfo = ShaderPass.get(params.device).getByIndex(params.pass); + shaderPassInfo.defines.forEach((value, key) => defines.set(key, value)); + + return defines; + } + + /** + * Process shader using shader processing options, utilizing the cache of the ProgramLibrary. + * + * @param {Shader} shader - The shader to be processed. + * @param {ShaderProcessorOptions} processingOptions - The shader processing options. + * @returns {Shader} The processed shader. + * @ignore + */ + static 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'; + + // unique name based of the shader id + const key = `${name}-id-${shader.id}`; + + const materialGenerator = new ShaderGeneratorPassThrough(key, 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; + } +} + +function createShader(device, vsName, fsName, useTransformFeedback = false, shaderDefinitionOptions = {}) { + Debug.removed('pc.createShader has been removed deprecated. Use ShaderUtils.createShader instead.'); +} + +function createShaderFromCode(device, vsCode, fsCode, uniqueName, attributes, useTransformFeedback = false, shaderDefinitionOptions = {}) { + + Debug.deprecated('pc.createShaderFromCode has been deprecated. Use ShaderUtils.createShader instead.'); + + // the function signature has changed, fail if called incorrectly + Debug.assert(typeof attributes !== 'boolean'); + + // Normalize arguments to allow passing shaderDefinitionOptions as the 6th argument + if (typeof useTransformFeedback === 'boolean') { + shaderDefinitionOptions.useTransformFeedback = useTransformFeedback; + } else if (typeof useTransformFeedback === 'object') { + shaderDefinitionOptions = { + ...shaderDefinitionOptions, + ...useTransformFeedback + }; + } + + const programLibrary = getProgramLibrary(device); + let shader = programLibrary.getCachedShader(uniqueName); + if (!shader) { + shader = new Shader(device, ShaderDefinitionUtils.createDefinition(device, { + ...shaderDefinitionOptions, + name: uniqueName, + vertexCode: vsCode, + fragmentCode: fsCode, + attributes: attributes + })); + programLibrary.setCachedShader(uniqueName, shader); + } + return shader; +} + +export { ShaderUtils, createShader, createShaderFromCode }; diff --git a/src/scene/shader-lib/utils.js b/src/scene/shader-lib/utils.js deleted file mode 100644 index 80ba9034436..00000000000 --- a/src/scene/shader-lib/utils.js +++ /dev/null @@ -1,185 +0,0 @@ -import { Shader } from '../../platform/graphics/shader.js'; -import { ShaderDefinitionUtils } from '../../platform/graphics/shader-definition-utils.js'; -import { shaderChunks } from './chunks/chunks.js'; -import { getProgramLibrary } from './get-program-library.js'; -import { Debug } from '../../core/debug.js'; -import { ShaderGenerator } from './programs/shader-generator.js'; -import { ShaderPass } from '../shader-pass.js'; - -/** - * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' - * @import { ShaderProcessorOptions } from '../../platform/graphics/shader-processor-options.js' - * @import { CameraShaderParams } from '../camera-shader-params.js' - * @import { Material, ShaderVariantParams } from '../materials/material.js' - */ - -/** - * Create a shader from named shader chunks. - * - * @param {GraphicsDevice} device - The graphics device. - * @param {string} vsName - The vertex shader chunk name. - * @param {string} fsName - The fragment shader chunk name. - * @param {boolean | Record} [useTransformFeedback] - Whether - * to use transform feedback. Defaults to false. - * @param {object} [shaderDefinitionOptions] - Additional options that will be added to the shader - * definition. - * @param {boolean} [shaderDefinitionOptions.useTransformFeedback] - Whether to use transform - * feedback. Defaults to false. - * @param {string | string[]} [shaderDefinitionOptions.fragmentOutputTypes] - Fragment shader - * output types, which default to vec4. Passing a string will set the output type for all color - * attachments. Passing an array will set the output type for each color attachment. - * @see ShaderDefinitionUtils.createDefinition - * @returns {Shader} The newly created shader. - * @category Graphics - */ -function createShader(device, vsName, fsName, useTransformFeedback = false, shaderDefinitionOptions = {}) { - - // Normalize arguments to allow passing shaderDefinitionOptions as the 6th argument - if (typeof useTransformFeedback === 'boolean') { - shaderDefinitionOptions.useTransformFeedback = useTransformFeedback; - } else if (typeof useTransformFeedback === 'object') { - shaderDefinitionOptions = { - ...shaderDefinitionOptions, - ...useTransformFeedback - }; - } - - return new Shader(device, ShaderDefinitionUtils.createDefinition(device, { - ...shaderDefinitionOptions, - name: `${vsName}_${fsName}`, - vertexCode: shaderChunks[vsName], - fragmentCode: shaderChunks[fsName] - })); -} - -/** - * Create a shader from the supplied source code. Note that this function adds additional shader - * blocks to both vertex and fragment shaders, which allow the shader to use more features and - * compile on both WebGL and WebGPU. Specifically, these blocks are added, and should not be - * part of provided vsCode and fsCode: shader version, shader precision, commonly used extensions. - * - * @param {GraphicsDevice} device - The graphics device. - * @param {string} vsCode - The vertex shader code. - * @param {string} fsCode - The fragment shader code. - * @param {string} uniqueName - Unique name for the shader. If a shader with this name already - * exists, it will be returned instead of a new shader instance. - * @param {Object} [attributes] - Object detailing the mapping of vertex shader - * attribute names to semantics SEMANTIC_*. This enables the engine to match vertex buffer data as - * inputs to the shader. Defaults to undefined, which generates the default attributes. - * @param {boolean | Record} [useTransformFeedback] - Whether - * to use transform feedback. Defaults to false. - * @param {object} [shaderDefinitionOptions] - Additional options that will be added to the shader - * definition. - * @param {boolean} [shaderDefinitionOptions.useTransformFeedback] - Whether to use transform - * feedback. Defaults to false. - * @param {string | string[]} [shaderDefinitionOptions.fragmentOutputTypes] - Fragment shader - * output types, which default to vec4. Passing a string will set the output type for all color - * attachments. Passing an array will set the output type for each color attachment. - * @see ShaderDefinitionUtils.createDefinition - * @returns {Shader} The newly created shader. - * @category Graphics - */ -function createShaderFromCode(device, vsCode, fsCode, uniqueName, attributes, useTransformFeedback = false, shaderDefinitionOptions = {}) { - - // the function signature has changed, fail if called incorrectly - Debug.assert(typeof attributes !== 'boolean'); - - // Normalize arguments to allow passing shaderDefinitionOptions as the 6th argument - if (typeof useTransformFeedback === 'boolean') { - shaderDefinitionOptions.useTransformFeedback = useTransformFeedback; - } else if (typeof useTransformFeedback === 'object') { - shaderDefinitionOptions = { - ...shaderDefinitionOptions, - ...useTransformFeedback - }; - } - - const programLibrary = getProgramLibrary(device); - let shader = programLibrary.getCachedShader(uniqueName); - if (!shader) { - shader = new Shader(device, ShaderDefinitionUtils.createDefinition(device, { - ...shaderDefinitionOptions, - name: uniqueName, - vertexCode: vsCode, - fragmentCode: fsCode, - attributes: attributes - })); - programLibrary.setCachedShader(uniqueName, shader); - } - return shader; -} - -class ShaderGeneratorPassThrough extends ShaderGenerator { - constructor(key, shaderDefinition) { - super(); - this.key = key; - this.shaderDefinition = shaderDefinition; - } - - generateKey(options) { - return this.key; - } - - createShaderDefinition(device, options) { - return this.shaderDefinition; - } -} - -/** - * Process shader using shader processing options, utilizing cache of the ProgramLibrary - * - * @param {Shader} shader - The shader to be processed. - * @param {ShaderProcessorOptions} processingOptions - The shader processing options. - * @returns {Shader} The processed shader. - */ -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'; - - // unique name based of the shader id - const key = `${name}-id-${shader.id}`; - - const materialGenerator = new ShaderGeneratorPassThrough(key, 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; -} - -/** - * Create a map of defines used for shader generation for a material. - * - * @param {Material} material - The material to create the shader defines for. - * @param {ShaderVariantParams} params - The shader variant parameters. - * @returns {Map} The map of shader defines. - * @ignore - */ -const getCoreDefines = (material, params) => { - - // merge both maps, with camera shader params taking precedence - const defines = new Map(material.defines); - params.cameraShaderParams.defines.forEach((value, key) => defines.set(key, value)); - - // add pass defines - const shaderPassInfo = ShaderPass.get(params.device).getByIndex(params.pass); - shaderPassInfo.defines.forEach((value, key) => defines.set(key, value)); - - return defines; -}; - -export { createShader, createShaderFromCode, processShader, getCoreDefines };