playcanvas
Version:
PlayCanvas WebGL game engine
216 lines (213 loc) • 11.8 kB
JavaScript
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 };