123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- import Check from "../Core/Check.js";
- import clone from "../Core/clone.js";
- import defined from "../Core/defined.js";
- import defaultValue from "../Core/defaultValue.js";
- import DeveloperError from "../Core/DeveloperError.js";
- import ShaderDestination from "./ShaderDestination.js";
- import ShaderProgram from "./ShaderProgram.js";
- import ShaderSource from "./ShaderSource.js";
- import ShaderStruct from "./ShaderStruct.js";
- import ShaderFunction from "./ShaderFunction.js";
- /**
- * An object that makes it easier to build the text of a {@link ShaderProgram}. This tracks GLSL code for both the vertex shader and the fragment shader.
- * <p>
- * For vertex shaders, the shader builder tracks a list of <code>#defines</code>,
- * a list of attributes, a list of uniforms, and a list of shader lines. It also
- * tracks the location of each attribute so the caller can easily build the {@link VertexArray}
- * </p>
- * <p>
- * For fragment shaders, the shader builder tracks a list of <code>#defines</code>,
- * a list of attributes, a list of uniforms, and a list of shader lines.
- * </p>
- *
- * @alias ShaderBuilder
- * @constructor
- *
- * @example
- * const shaderBuilder = new ShaderBuilder();
- * shaderBuilder.addDefine("SOLID_COLOR", undefined, ShaderDestination.FRAGMENT);
- * shaderBuilder.addUniform("vec3", "u_color", ShaderDestination.FRAGMENT);
- * shaderBuilder.addVarying("vec3", v_color");
- * // These locations can be used when creating the VertexArray
- * const positionLocation = shaderBuilder.addPositionAttribute("vec3", "a_position");
- * const colorLocation = shaderBuilder.addAttribute("vec3", "a_color");
- * shaderBuilder.addVertexLines([
- * "void main()",
- * "{",
- * " v_color = a_color;",
- * " gl_Position = vec4(a_position, 1.0);",
- * "}"
- * ]);
- * shaderBuilder.addFragmentLines([
- * "void main()",
- * "{",
- * " #ifdef SOLID_COLOR",
- * " out_FragColor = vec4(u_color, 1.0);",
- * " #else",
- * " out_FragColor = vec4(v_color, 1.0);",
- * " #endif",
- * "}"
- * ]);
- * const shaderProgram = shaderBuilder.build(context);
- *
- * @private
- */
- function ShaderBuilder() {
- // Some WebGL implementations require attribute 0 to always
- // be active, so the position attribute is tracked separately
- this._positionAttributeLine = undefined;
- this._nextAttributeLocation = 1;
- this._attributeLocations = {};
- this._attributeLines = [];
- // Dynamically-generated structs and functions
- // these are dictionaries of id -> ShaderStruct or ShaderFunction respectively
- this._structs = {};
- this._functions = {};
- this._vertexShaderParts = {
- defineLines: [],
- uniformLines: [],
- shaderLines: [],
- varyingLines: [],
- // identifiers of structs/functions to include, listed in insertion order
- structIds: [],
- functionIds: [],
- };
- this._fragmentShaderParts = {
- defineLines: [],
- uniformLines: [],
- shaderLines: [],
- varyingLines: [],
- // identifiers of structs/functions to include, listed in insertion order
- structIds: [],
- functionIds: [],
- };
- }
- Object.defineProperties(ShaderBuilder.prototype, {
- /**
- * Get a dictionary of attribute names to the integer location in
- * the vertex shader.
- *
- * @memberof ShaderBuilder.prototype
- * @type {Object<string, number>}
- * @readonly
- * @private
- */
- attributeLocations: {
- get: function () {
- return this._attributeLocations;
- },
- },
- });
- /**
- * Add a <code>#define</code> macro to one or both of the shaders. These lines
- * will appear at the top of the final shader source.
- *
- * @param {string} identifier An identifier for the macro. Identifiers must use uppercase letters with underscores to be consistent with Cesium's style guide.
- * @param {string} [value] The value of the macro. If undefined, the define will not include a value. The value will be converted to GLSL code via <code>toString()</code>
- * @param {ShaderDestination} [destination=ShaderDestination.BOTH] Whether the define appears in the vertex shader, the fragment shader, or both.
- *
- * @example
- * // creates the line "#define ENABLE_LIGHTING" in both shaders
- * shaderBuilder.addDefine("ENABLE_LIGHTING");
- * // creates the line "#define PI 3.141592" in the fragment shader
- * shaderBuilder.addDefine("PI", 3.141593, ShaderDestination.FRAGMENT);
- */
- ShaderBuilder.prototype.addDefine = function (identifier, value, destination) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("identifier", identifier);
- //>>includeEnd('debug');
- destination = defaultValue(destination, ShaderDestination.BOTH);
- // The ShaderSource created in build() will add the #define part
- let line = identifier;
- if (defined(value)) {
- line += ` ${value.toString()}`;
- }
- if (ShaderDestination.includesVertexShader(destination)) {
- this._vertexShaderParts.defineLines.push(line);
- }
- if (ShaderDestination.includesFragmentShader(destination)) {
- this._fragmentShaderParts.defineLines.push(line);
- }
- };
- /**
- * Add a new dynamically-generated struct to the shader
- * @param {string} structId A unique ID to identify this struct in {@link ShaderBuilder#addStructField}
- * @param {string} structName The name of the struct as it will appear in the shader.
- * @param {ShaderDestination} destination Whether the struct will appear in the vertex shader, the fragment shader, or both.
- *
- * @example
- * // generates the following struct in the fragment shader
- * // struct TestStruct
- * // {
- * // };
- * shaderBuilder.addStruct("testStructId", "TestStruct", ShaderDestination.FRAGMENT);
- */
- ShaderBuilder.prototype.addStruct = function (
- structId,
- structName,
- destination
- ) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("structId", structId);
- Check.typeOf.string("structName", structName);
- Check.typeOf.number("destination", destination);
- //>>includeEnd('debug');
- this._structs[structId] = new ShaderStruct(structName);
- if (ShaderDestination.includesVertexShader(destination)) {
- this._vertexShaderParts.structIds.push(structId);
- }
- if (ShaderDestination.includesFragmentShader(destination)) {
- this._fragmentShaderParts.structIds.push(structId);
- }
- };
- /**
- * Add a field to a dynamically-generated struct.
- * @param {string} structId The ID of the struct. This must be created first with {@link ShaderBuilder#addStruct}
- * @param {string} type The GLSL type of the field
- * @param {string} identifier The identifier of the field.
- *
- * @example
- * // generates the following struct in the fragment shader
- * // struct TestStruct
- * // {
- * // float minimum;
- * // float maximum;
- * // };
- * shaderBuilder.addStruct("testStructId", "TestStruct", ShaderDestination.FRAGMENT);
- * shaderBuilder.addStructField("testStructId", "float", "maximum");
- * shaderBuilder.addStructField("testStructId", "float", "minimum");
- */
- ShaderBuilder.prototype.addStructField = function (structId, type, identifier) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("structId", structId);
- Check.typeOf.string("type", type);
- Check.typeOf.string("identifier", identifier);
- //>>includeEnd('debug');
- this._structs[structId].addField(type, identifier);
- };
- /**
- * Add a new dynamically-generated function to the shader.
- * @param {string} functionName The name of the function. This will be used to identify the function in {@link ShaderBuilder#addFunctionLines}.
- * @param {string} signature The full signature of the function as it will appear in the shader. Do not include the curly braces.
- * @param {ShaderDestination} destination Whether the struct will appear in the vertex shader, the fragment shader, or both.
- * @example
- * // generates the following function in the vertex shader
- * // vec3 testFunction(float parameter)
- * // {
- * // }
- * shaderBuilder.addStruct("testFunction", "vec3 testFunction(float parameter)", ShaderDestination.VERTEX);
- */
- ShaderBuilder.prototype.addFunction = function (
- functionName,
- signature,
- destination
- ) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("functionName", functionName);
- Check.typeOf.string("signature", signature);
- Check.typeOf.number("destination", destination);
- //>>includeEnd('debug');
- this._functions[functionName] = new ShaderFunction(signature);
- if (ShaderDestination.includesVertexShader(destination)) {
- this._vertexShaderParts.functionIds.push(functionName);
- }
- if (ShaderDestination.includesFragmentShader(destination)) {
- this._fragmentShaderParts.functionIds.push(functionName);
- }
- };
- /**
- * Add lines to a dynamically-generated function
- * @param {string} functionName The name of the function. This must be created beforehand using {@link ShaderBuilder#addFunction}
- * @param {string|string[]} lines One or more lines of GLSL code to add to the function body. Do not include any preceding or ending whitespace, but do include the semicolon for each line.
- *
- * @example
- * // generates the following function in the vertex shader
- * // vec3 testFunction(float parameter)
- * // {
- * // float signed = 2.0 * parameter - 1.0;
- * // return vec3(signed, 0.0, 0.0);
- * // }
- * shaderBuilder.addStruct("testFunction", "vec3 testFunction(float parameter)", ShaderDestination.VERTEX);
- * shaderBuilder.addFunctionLines("testFunction", [
- * "float signed = 2.0 * parameter - 1.0;",
- * "return vec3(parameter);"
- * ]);
- */
- ShaderBuilder.prototype.addFunctionLines = function (functionName, lines) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("functionName", functionName);
- if (typeof lines !== "string" && !Array.isArray(lines)) {
- throw new DeveloperError(
- `Expected lines to be a string or an array of strings, actual value was ${lines}`
- );
- }
- //>>includeEnd('debug');
- this._functions[functionName].addLines(lines);
- };
- /**
- * Add a uniform declaration to one or both of the shaders. These lines
- * will appear grouped near the top of the final shader source.
- *
- * @param {string} type The GLSL type of the uniform.
- * @param {string} identifier An identifier for the uniform. Identifiers must begin with <code>u_</code> to be consistent with Cesium's style guide.
- * @param {ShaderDestination} [destination=ShaderDestination.BOTH] Whether the uniform appears in the vertex shader, the fragment shader, or both.
- *
- * @example
- * // creates the line "uniform vec3 u_resolution;"
- * shaderBuilder.addUniform("vec3", "u_resolution", ShaderDestination.FRAGMENT);
- * // creates the line "uniform float u_time;" in both shaders
- * shaderBuilder.addUniform("float", "u_time", ShaderDestination.BOTH);
- */
- ShaderBuilder.prototype.addUniform = function (type, identifier, destination) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("type", type);
- Check.typeOf.string("identifier", identifier);
- //>>includeEnd('debug');
- destination = defaultValue(destination, ShaderDestination.BOTH);
- const line = `uniform ${type} ${identifier};`;
- if (ShaderDestination.includesVertexShader(destination)) {
- this._vertexShaderParts.uniformLines.push(line);
- }
- if (ShaderDestination.includesFragmentShader(destination)) {
- this._fragmentShaderParts.uniformLines.push(line);
- }
- };
- /**
- * Add a position attribute declaration to the vertex shader. These lines
- * will appear grouped near the top of the final shader source.
- * <p>
- * Some WebGL implementations require attribute 0 to be enabled, so this is
- * reserved for the position attribute. For all other attributes, see
- * {@link ShaderBuilder#addAttribute}
- * </p>
- *
- * @param {string} type The GLSL type of the attribute
- * @param {string} identifier An identifier for the attribute. Identifiers must begin with <code>a_</code> to be consistent with Cesium's style guide.
- * @return {number} The integer location of the attribute. This location can be used when creating attributes for a {@link VertexArray}. This will always be 0.
- *
- * @example
- * // creates the line "in vec3 a_position;"
- * shaderBuilder.setPositionAttribute("vec3", "a_position");
- */
- ShaderBuilder.prototype.setPositionAttribute = function (type, identifier) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("type", type);
- Check.typeOf.string("identifier", identifier);
- if (defined(this._positionAttributeLine)) {
- throw new DeveloperError(
- "setPositionAttribute() must be called exactly once for the attribute used for gl_Position. For other attributes, use addAttribute()"
- );
- }
- //>>includeEnd('debug');
- this._positionAttributeLine = `in ${type} ${identifier};`;
- // Some WebGL implementations require attribute 0 to always be active, so
- // this builder assumes the position will always go in location 0
- this._attributeLocations[identifier] = 0;
- return 0;
- };
- /**
- * Add an attribute declaration to the vertex shader. These lines
- * will appear grouped near the top of the final shader source.
- * <p>
- * Some WebGL implementations require attribute 0 to be enabled, so this is
- * reserved for the position attribute. See {@link ShaderBuilder#setPositionAttribute}
- * </p>
- *
- * @param {string} type The GLSL type of the attribute
- * @param {string} identifier An identifier for the attribute. Identifiers must begin with <code>a_</code> to be consistent with Cesium's style guide.
- * @return {number} The integer location of the attribute. This location can be used when creating attributes for a {@link VertexArray}
- *
- * @example
- * // creates the line "in vec2 a_texCoord0;" in the vertex shader
- * shaderBuilder.addAttribute("vec2", "a_texCoord0");
- */
- ShaderBuilder.prototype.addAttribute = function (type, identifier) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("type", type);
- Check.typeOf.string("identifier", identifier);
- //>>includeEnd('debug');
- const line = `in ${type} ${identifier};`;
- this._attributeLines.push(line);
- const location = this._nextAttributeLocation;
- this._attributeLocations[identifier] = location;
- // Most attributes only require a single attribute location, but matrices
- // require more.
- this._nextAttributeLocation += getAttributeLocationCount(type);
- return location;
- };
- /**
- * Add a varying declaration to both the vertex and fragment shaders.
- *
- * @param {string} type The GLSL type of the varying
- * @param {string} identifier An identifier for the varying. Identifiers must begin with <code>v_</code> to be consistent with Cesium's style guide.
- *
- * @example
- * // creates the line "in vec3 v_color;" in the vertex shader
- * // creates the line "out vec3 v_color;" in the fragment shader
- * shaderBuilder.addVarying("vec3", "v_color");
- */
- ShaderBuilder.prototype.addVarying = function (type, identifier) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("type", type);
- Check.typeOf.string("identifier", identifier);
- //>>includeEnd('debug');
- const line = `${type} ${identifier};`;
- this._vertexShaderParts.varyingLines.push(`out ${line}`);
- this._fragmentShaderParts.varyingLines.push(`in ${line}`);
- };
- /**
- * Appends lines of GLSL code to the vertex shader
- *
- * @param {string|string[]} lines One or more lines to add to the end of the vertex shader source
- *
- * @example
- * shaderBuilder.addVertexLines([
- * "void main()",
- * "{",
- * " v_color = a_color;",
- * " gl_Position = vec4(a_position, 1.0);",
- * "}"
- * ]);
- */
- ShaderBuilder.prototype.addVertexLines = function (lines) {
- //>>includeStart('debug', pragmas.debug);
- if (typeof lines !== "string" && !Array.isArray(lines)) {
- throw new DeveloperError(
- `Expected lines to be a string or an array of strings, actual value was ${lines}`
- );
- }
- //>>includeEnd('debug');
- const vertexLines = this._vertexShaderParts.shaderLines;
- if (Array.isArray(lines)) {
- vertexLines.push.apply(vertexLines, lines);
- } else {
- // Single string case
- vertexLines.push(lines);
- }
- };
- /**
- * Appends lines of GLSL code to the fragment shader
- *
- * @param {string[]} lines The lines to add to the end of the fragment shader source
- *
- * @example
- * shaderBuilder.addFragmentLines([
- * "void main()",
- * "{",
- * " #ifdef SOLID_COLOR",
- * " out_FragColor = vec4(u_color, 1.0);",
- * " #else",
- * " out_FragColor = vec4(v_color, 1.0);",
- * " #endif",
- * "}"
- * ]);
- */
- ShaderBuilder.prototype.addFragmentLines = function (lines) {
- //>>includeStart('debug', pragmas.debug);
- if (typeof lines !== "string" && !Array.isArray(lines)) {
- throw new DeveloperError(
- `Expected lines to be a string or an array of strings, actual value was ${lines}`
- );
- }
- //>>includeEnd('debug');
- const fragmentLines = this._fragmentShaderParts.shaderLines;
- if (Array.isArray(lines)) {
- fragmentLines.push.apply(fragmentLines, lines);
- } else {
- // Single string case
- fragmentLines.push(lines);
- }
- };
- /**
- * Builds the {@link ShaderProgram} from the pieces added by the other methods.
- * Call this one time at the end of modifying the shader through the other
- * methods in this class.
- *
- * @param {Context} context The context to use for creating the shader.
- * @return {ShaderProgram} A shader program to use for rendering.
- *
- * @example
- * const shaderProgram = shaderBuilder.buildShaderProgram(context);
- */
- ShaderBuilder.prototype.buildShaderProgram = function (context) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.object("context", context);
- //>>includeEnd('debug');
- const positionAttribute = defined(this._positionAttributeLine)
- ? [this._positionAttributeLine]
- : [];
- const structLines = generateStructLines(this);
- const functionLines = generateFunctionLines(this);
- // Lines are joined here so the ShaderSource
- // generates a single #line 0 directive
- const vertexLines = positionAttribute
- .concat(
- this._attributeLines,
- this._vertexShaderParts.uniformLines,
- this._vertexShaderParts.varyingLines,
- structLines.vertexLines,
- functionLines.vertexLines,
- this._vertexShaderParts.shaderLines
- )
- .join("\n");
- const vertexShaderSource = new ShaderSource({
- defines: this._vertexShaderParts.defineLines,
- sources: [vertexLines],
- });
- const fragmentLines = this._fragmentShaderParts.uniformLines
- .concat(
- this._fragmentShaderParts.varyingLines,
- structLines.fragmentLines,
- functionLines.fragmentLines,
- this._fragmentShaderParts.shaderLines
- )
- .join("\n");
- const fragmentShaderSource = new ShaderSource({
- defines: this._fragmentShaderParts.defineLines,
- sources: [fragmentLines],
- });
- return ShaderProgram.fromCache({
- context: context,
- vertexShaderSource: vertexShaderSource,
- fragmentShaderSource: fragmentShaderSource,
- attributeLocations: this._attributeLocations,
- });
- };
- ShaderBuilder.prototype.clone = function () {
- return clone(this, true);
- };
- function generateStructLines(shaderBuilder) {
- const vertexLines = [];
- const fragmentLines = [];
- let i;
- let structIds = shaderBuilder._vertexShaderParts.structIds;
- let structId;
- let struct;
- let structLines;
- for (i = 0; i < structIds.length; i++) {
- structId = structIds[i];
- struct = shaderBuilder._structs[structId];
- structLines = struct.generateGlslLines();
- vertexLines.push.apply(vertexLines, structLines);
- }
- structIds = shaderBuilder._fragmentShaderParts.structIds;
- for (i = 0; i < structIds.length; i++) {
- structId = structIds[i];
- struct = shaderBuilder._structs[structId];
- structLines = struct.generateGlslLines();
- fragmentLines.push.apply(fragmentLines, structLines);
- }
- return {
- vertexLines: vertexLines,
- fragmentLines: fragmentLines,
- };
- }
- function getAttributeLocationCount(glslType) {
- switch (glslType) {
- case "mat2":
- return 2;
- case "mat3":
- return 3;
- case "mat4":
- return 4;
- default:
- return 1;
- }
- }
- function generateFunctionLines(shaderBuilder) {
- const vertexLines = [];
- const fragmentLines = [];
- let i;
- let functionIds = shaderBuilder._vertexShaderParts.functionIds;
- let functionId;
- let func;
- let functionLines;
- for (i = 0; i < functionIds.length; i++) {
- functionId = functionIds[i];
- func = shaderBuilder._functions[functionId];
- functionLines = func.generateGlslLines();
- vertexLines.push.apply(vertexLines, functionLines);
- }
- functionIds = shaderBuilder._fragmentShaderParts.functionIds;
- for (i = 0; i < functionIds.length; i++) {
- functionId = functionIds[i];
- func = shaderBuilder._functions[functionId];
- functionLines = func.generateGlslLines();
- fragmentLines.push.apply(fragmentLines, functionLines);
- }
- return {
- vertexLines: vertexLines,
- fragmentLines: fragmentLines,
- };
- }
- export default ShaderBuilder;
|