playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
202 lines (201 loc) • 8.59 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { TRACEID_SHADER_ALLOC } from "../../core/constants.js";
import { Debug } from "../../core/debug.js";
import { platform } from "../../core/platform.js";
import { Preprocessor } from "../../core/preprocessor.js";
import { SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from "./constants.js";
import { DebugGraphics } from "./debug-graphics.js";
import { ShaderDefinitionUtils } from "./shader-definition-utils.js";
import halfTypes from "./shader-chunks/frag/half-types.js";
let id = 0;
class Shader {
/**
* Creates a new Shader instance.
*
* 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.
* @param {object} definition - The shader definition from which to build the shader.
* @param {string} [definition.name] - The name of the shader.
* @param {Object<string, string>} [definition.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. When not specified, rendering without vertex
* buffer is assumed.
* @param {string[]} [definition.feedbackVaryings] - A list of shader output variable
* names that will be captured when using transform feedback. This setting is only effective
* if the useTransformFeedback property is enabled.
* @param {string} [definition.vshader] - Vertex shader source (GLSL code). Optional when
* compute shader is specified.
* @param {string} [definition.fshader] - Fragment shader source (GLSL code). Optional when
* useTransformFeedback or compute shader is specified.
* @param {string} [definition.cshader] - Compute shader source (WGSL code). Only supported on
* WebGPU platform.
* @param {string} [definition.computeEntryPoint] - The entry point function name for the compute
* shader. Defaults to 'main'.
* @param {Map<string, string>} [definition.vincludes] - 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>} [definition.fincludes] - 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>} [definition.cincludes] - A map containing key-value pairs
* of include names and their content. These are used for resolving #include directives in the
* compute shader source.
* @param {Map<string, string>} [definition.cdefines] - A map containing key-value pairs of
* define names and their values. These are used for resolving defines in the compute shader.
* @param {boolean} [definition.useTransformFeedback] - Specifies that this shader outputs
* post-VS data to a buffer.
* @param {string | string[]} [definition.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.
* @param {string} [definition.shaderLanguage] - Specifies the shader language of vertex and
* fragment shaders. Defaults to {@link SHADERLANGUAGE_GLSL}.
* @example
* // Create a shader that renders primitives with a solid red color
*
* // Vertex shader
* const vshader = `
* attribute vec3 aPosition;
*
* void main(void) {
* gl_Position = vec4(aPosition, 1.0);
* }
* `;
*
* // Fragment shader
* const fshader = `
* precision ${graphicsDevice.precision} float;
*
* void main(void) {
* gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
* }
* `;
*
* const shaderDefinition = {
* attributes: {
* aPosition: pc.SEMANTIC_POSITION
* },
* vshader,
* fshader
* };
*
* const shader = new pc.Shader(graphicsDevice, shaderDefinition);
*/
constructor(graphicsDevice, definition) {
/**
* Format of the uniform buffer for mesh bind group.
*
* @type {UniformBufferFormat}
* @ignore
*/
__publicField(this, "meshUniformBufferFormat");
/**
* Format of the bind group for the mesh bind group.
*
* @type {BindGroupFormat}
* @ignore
*/
__publicField(this, "meshBindGroupFormat");
/**
* The attributes that this shader code uses. The location is the key, the value is the name.
* These attributes are queried / extracted from the final shader.
*
* @type {Map<number, string>}
* @ignore
*/
__publicField(this, "attributes", /* @__PURE__ */ new Map());
this.id = id++;
this.device = graphicsDevice;
this.definition = definition;
this.name = definition.name || "Untitled";
this.init();
if (definition.cshader) {
Debug.assert(graphicsDevice.supportsCompute, "Compute shaders are not supported on this device.");
Debug.assert(!definition.vshader && !definition.fshader, "Vertex and fragment shaders are not supported when creating a compute shader.");
Debug.call(() => {
this.cUnmodified = definition.cshader;
});
const enablesCode = ShaderDefinitionUtils.getWGSLEnables(graphicsDevice, "compute");
const definesCode = ShaderDefinitionUtils.getDefinesCode(graphicsDevice, definition.cdefines);
const cshader = enablesCode + definesCode + definition.cshader;
const cincludes = definition.cincludes ?? /* @__PURE__ */ new Map();
if (!cincludes.has("halfTypesCS")) {
cincludes.set("halfTypesCS", halfTypes);
}
definition.cshader = Preprocessor.run(cshader, cincludes, {
sourceName: `compute shader for ${this.label}`,
stripDefines: true
});
} else {
Debug.assert(definition.vshader, "No vertex shader has been specified when creating a shader.");
Debug.assert(definition.fshader, "No fragment shader has been specified when creating a shader.");
Debug.call(() => {
this.vUnmodified = definition.vshader;
this.fUnmodified = definition.fshader;
});
const wgsl = definition.shaderLanguage === SHADERLANGUAGE_WGSL;
definition.vshader = Preprocessor.run(definition.vshader, definition.vincludes, {
sourceName: `vertex shader for ${this.label}`,
stripDefines: wgsl
});
if (definition.shaderLanguage === SHADERLANGUAGE_GLSL) {
definition.attributes ?? (definition.attributes = ShaderDefinitionUtils.collectAttributes(definition.vshader));
}
const stripUnusedColorAttachments = graphicsDevice.isWebGL2 && (platform.name === "osx" || platform.name === "ios");
definition.fshader = Preprocessor.run(definition.fshader, definition.fincludes, {
stripUnusedColorAttachments,
stripDefines: wgsl,
sourceName: `fragment shader for ${this.label}`
});
if (!definition.vshader || !definition.fshader) {
Debug.error(`Shader: Failed to create shader ${this.label}. Vertex or fragment shader source is empty.`, this);
this.failed = true;
return;
}
}
this.impl = graphicsDevice.createShaderImpl(this);
Debug.trace(TRACEID_SHADER_ALLOC, `Alloc: ${this.label}, stack: ${DebugGraphics.toString()}`, {
instance: this
});
}
/**
* Initialize a shader back to its default state.
*
* @private
*/
init() {
this.ready = false;
this.failed = false;
}
/** @ignore */
get label() {
return `Shader Id ${this.id} (${this.definition.shaderLanguage === SHADERLANGUAGE_WGSL ? "WGSL" : "GLSL"}) ${this.name}`;
}
/**
* Frees resources associated with this shader.
*/
destroy() {
Debug.trace(TRACEID_SHADER_ALLOC, `DeAlloc: Id ${this.id} ${this.name}`);
this.device.onDestroyShader(this);
this.impl.destroy(this);
}
/**
* Called when the WebGL context was lost. It releases all context related resources.
*
* @ignore
*/
loseContext() {
this.init();
this.impl.loseContext();
}
/** @ignore */
restoreContext() {
this.impl.restoreContext(this.device, this);
}
}
export {
Shader
};