UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

216 lines (213 loc) 11.8 kB
import { Debug } from '../../core/debug.js'; import { SHADERLANGUAGE_WGSL, SHADERLANGUAGE_GLSL, SEMANTIC_BLENDWEIGHT, SEMANTIC_BLENDINDICES, SEMANTIC_COLOR, SEMANTIC_TEXCOORD7, SEMANTIC_TEXCOORD6, SEMANTIC_TEXCOORD5, SEMANTIC_TEXCOORD4, SEMANTIC_TEXCOORD3, SEMANTIC_TEXCOORD2, SEMANTIC_TEXCOORD1, SEMANTIC_TEXCOORD0, SEMANTIC_TANGENT, SEMANTIC_NORMAL, SEMANTIC_POSITION } from './constants.js'; import gles3PS from './shader-chunks/frag/gles3.js'; import gles3VS from './shader-chunks/vert/gles3.js'; import webgpuPS from './shader-chunks/frag/webgpu.js'; import webgpuVS from './shader-chunks/vert/webgpu.js'; import wgslFS from './shader-chunks/frag/webgpu-wgsl.js'; import wgslVS from './shader-chunks/vert/webgpu-wgsl.js'; import sharedGLSL from './shader-chunks/frag/shared.js'; import sharedWGSL from './shader-chunks/frag/shared-wgsl.js'; /** * @import { GraphicsDevice } from './graphics-device.js' */ var _attrib2Semantic = { vertex_position: SEMANTIC_POSITION, vertex_normal: SEMANTIC_NORMAL, vertex_tangent: SEMANTIC_TANGENT, vertex_texCoord0: SEMANTIC_TEXCOORD0, vertex_texCoord1: SEMANTIC_TEXCOORD1, vertex_texCoord2: SEMANTIC_TEXCOORD2, vertex_texCoord3: SEMANTIC_TEXCOORD3, vertex_texCoord4: SEMANTIC_TEXCOORD4, vertex_texCoord5: SEMANTIC_TEXCOORD5, vertex_texCoord6: SEMANTIC_TEXCOORD6, vertex_texCoord7: SEMANTIC_TEXCOORD7, vertex_color: SEMANTIC_COLOR, vertex_boneIndices: SEMANTIC_BLENDINDICES, vertex_boneWeights: SEMANTIC_BLENDWEIGHT }; /** * A class providing utility functions for shader creation. * * @ignore */ class ShaderUtils { /** * Creates a shader definition. * * @param {GraphicsDevice} device - The graphics device. * @param {object} options - Object for passing optional arguments. * @param {string} [options.name] - A name of the shader. * @param {object} [options.attributes] - Attributes. Will be extracted from the vertexCode if * not provided. * @param {string} options.vertexCode - The vertex shader code. * @param {string} [options.vertexExtensions] - The vertex shader extensions code. * @param {string} [options.fragmentCode] - The fragment shader code. * @param {string} [options.fragmentExtensions] - The fragment shader extensions code. * @param {string} [options.fragmentPreamble] - The preamble string for the fragment shader. * @param {boolean} [options.useTransformFeedback] - Whether to use transform feedback. Defaults * to false. * @param {Map<string, string>} [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<string, string>} [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<string, string>} [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<string, string>} [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 {object} Returns the created shader definition. */ static createDefinition(device, options) { Debug.assert(options); Debug.assert(!options.vertexDefines || options.vertexDefines instanceof Map); Debug.assert(!options.vertexIncludes || options.vertexIncludes instanceof Map); Debug.assert(!options.fragmentDefines || options.fragmentDefines instanceof Map); Debug.assert(!options.fragmentIncludes || options.fragmentIncludes instanceof Map); var getDefines = (gpu, gl2, isVertex, options)=>{ var deviceIntro = device.isWebGPU ? gpu : gl2; // a define per supported color attachment, which strips out unsupported output definitions in the deviceIntro var attachmentsDefine = ''; // Define the fragment shader output type, vec4 by default if (!isVertex) { var _options_fragmentOutputTypes; // Normalize fragmentOutputTypes to an array var fragmentOutputTypes = (_options_fragmentOutputTypes = options.fragmentOutputTypes) != null ? _options_fragmentOutputTypes : 'vec4'; if (!Array.isArray(fragmentOutputTypes)) { fragmentOutputTypes = [ fragmentOutputTypes ]; } for(var i = 0; i < device.maxColorAttachments; i++){ attachmentsDefine += "#define COLOR_ATTACHMENT_" + i + "\n"; var _fragmentOutputTypes_i; var outType = (_fragmentOutputTypes_i = fragmentOutputTypes[i]) != null ? _fragmentOutputTypes_i : 'vec4'; attachmentsDefine += "#define outType_" + i + " " + outType + "\n"; } } return attachmentsDefine + deviceIntro; }; var _options_name; var name = (_options_name = options.name) != null ? _options_name : 'Untitled'; var vertCode; var fragCode; var vertexDefinesCode = ShaderUtils.getDefinesCode(device, options.vertexDefines); var fragmentDefinesCode = ShaderUtils.getDefinesCode(device, options.fragmentDefines); var wgsl = options.shaderLanguage === SHADERLANGUAGE_WGSL; if (wgsl) { vertCode = "\n " + wgslVS + "\n " + sharedWGSL + "\n " + vertexDefinesCode + "\n " + options.vertexCode + "\n "; fragCode = "\n " + wgslFS + "\n " + sharedWGSL + "\n " + fragmentDefinesCode + "\n " + options.fragmentCode + "\n "; } else { // vertex code vertCode = ShaderUtils.versionCode(device) + getDefines(webgpuVS, gles3VS, true, options) + vertexDefinesCode + ShaderUtils.precisionCode(device) + "\n " + sharedGLSL + "\n " + ShaderUtils.getShaderNameCode(name) + "\n " + options.vertexCode; // fragment code fragCode = (options.fragmentPreamble || '') + ShaderUtils.versionCode(device) + getDefines(webgpuPS, gles3PS, false, options) + fragmentDefinesCode + ShaderUtils.precisionCode(device) + "\n " + sharedGLSL + "\n " + ShaderUtils.getShaderNameCode(name) + "\n " + (options.fragmentCode || ShaderUtils.dummyFragmentCode()); } var _options_shaderLanguage; return { name: name, shaderLanguage: (_options_shaderLanguage = options.shaderLanguage) != null ? _options_shaderLanguage : SHADERLANGUAGE_GLSL, attributes: options.attributes, vshader: vertCode, vincludes: options.vertexIncludes, fincludes: options.fragmentIncludes, fshader: fragCode, useTransformFeedback: options.useTransformFeedback, meshUniformBufferFormat: options.meshUniformBufferFormat, meshBindGroupFormat: options.meshBindGroupFormat }; } /** * @param {GraphicsDevice} device - The graphics device. * @param {Map<string, string>} [defines] - A map containing key-value pairs. * @returns {string} The shader code for the defines. * @private */ static getDefinesCode(device, defines) { var code = ''; device.capsDefines.forEach((value, key)=>{ code += "#define " + key + " " + value + "\n"; }); code += '\n'; defines == null ? void 0 : defines.forEach((value, key)=>{ code += "#define " + key + " " + value + "\n"; }); code += '\n'; return code; } // SpectorJS integration static getShaderNameCode(name) { return "#define SHADER_NAME " + name + "\n"; } static dummyFragmentCode() { return 'void main(void) {gl_FragColor = vec4(0.0);}'; } static versionCode(device) { return device.isWebGPU ? '#version 450\n' : '#version 300 es\n'; } static precisionCode(device, forcePrecision) { if (forcePrecision && forcePrecision !== 'highp' && forcePrecision !== 'mediump' && forcePrecision !== 'lowp') { forcePrecision = null; } if (forcePrecision) { if (forcePrecision === 'highp' && device.maxPrecision !== 'highp') { forcePrecision = 'mediump'; } if (forcePrecision === 'mediump' && device.maxPrecision === 'lowp') { forcePrecision = 'lowp'; } } var precision = forcePrecision ? forcePrecision : device.precision; var code = "\n precision " + precision + " float;\n precision " + precision + " int;\n precision " + precision + " usampler2D;\n precision " + precision + " isampler2D;\n precision " + precision + " sampler2DShadow;\n precision " + precision + " samplerCubeShadow;\n precision " + precision + " sampler2DArray;\n "; return code; } /** * Extract the attributes specified in a vertex shader. * * @param {string} vsCode - The vertex shader code. * @returns {Object<string, string>} The attribute name to semantic map. * @ignore */ static collectAttributes(vsCode) { var attribs = {}; var attrs = 0; var found = vsCode.indexOf('attribute'); while(found >= 0){ if (found > 0 && vsCode[found - 1] === '/') break; // skip the 'attribute' word inside the #define which we add to the shader var ignore = false; if (found > 0) { var startOfLine = vsCode.lastIndexOf('\n', found); startOfLine = startOfLine !== -1 ? startOfLine + 1 : 0; var lineStartString = vsCode.substring(startOfLine, found); if (lineStartString.includes('#')) { ignore = true; } } if (!ignore) { var endOfLine = vsCode.indexOf(';', found); var startOfAttribName = vsCode.lastIndexOf(' ', endOfLine); var attribName = vsCode.substring(startOfAttribName + 1, endOfLine); // if the attribute already exists in the semantic map if (attribs[attribName]) { Debug.warn("Attribute [" + attribName + "] already exists when extracting the attributes from the vertex shader, ignoring.", { vsCode }); } else { var semantic = _attrib2Semantic[attribName]; if (semantic !== undefined) { attribs[attribName] = semantic; } else { attribs[attribName] = "ATTR" + attrs; attrs++; } } } found = vsCode.indexOf('attribute', found + 1); } return attribs; } } export { ShaderUtils };