UNPKG

@lightningjs/renderer

Version:
202 lines 8.44 kB
/* * If not stated otherwise in this file or this component's LICENSE file the * following copyright and licenses apply: * * Copyright 2023 Comcast Cable Communications Management, LLC. * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { assertTruthy, hasOwn } from '../../../utils.js'; import { CoreShader } from '../CoreShader.js'; import { createProgram, createShader, } from './internal/ShaderUtils.js'; export class WebGlCoreShader extends CoreShader { buffersBound = false; program; /** * Vertex Array Object * * @remarks * Used by WebGL2 Only */ vao; renderer; glw; attributeLocations; uniformLocations; supportsIndexedTextures; constructor(options) { super(); const renderer = (this.renderer = options.renderer); const glw = (this.glw = this.renderer.glw); this.supportsIndexedTextures = options.supportsIndexedTextures || false; // Check that extensions are supported const webGl2 = glw.isWebGl2(); const requiredExtensions = (webGl2 && options.webgl2Extensions) || (!webGl2 && options.webgl1Extensions) || []; const glVersion = webGl2 ? '2.0' : '1.0'; requiredExtensions.forEach((extensionName) => { if (!glw.getExtension(extensionName)) { throw new Error(`Shader "${this.constructor.name}" requires extension "${extensionName}" for WebGL ${glVersion} but wasn't found`); } }); // Gather shader sources // - If WebGL 2 and special WebGL 2 sources are provided, we copy those sources and delete // the extra copy of them to save memory. // TODO: This could be further made optimal by just caching the compiled shaders and completely deleting // the source code const shaderSources = options.shaderSources || this.constructor.shaderSources; if (!shaderSources) { throw new Error(`Shader "${this.constructor.name}" is missing shaderSources.`); } else if (webGl2 && shaderSources?.webGl2) { shaderSources.fragment = shaderSources.webGl2.fragment; shaderSources.vertex = shaderSources.webGl2.vertex; delete shaderSources.webGl2; } const textureUnits = renderer.system.parameters.MAX_VERTEX_TEXTURE_IMAGE_UNITS; const vertexSource = shaderSources.vertex instanceof Function ? shaderSources.vertex(textureUnits) : shaderSources.vertex; const fragmentSource = shaderSources.fragment instanceof Function ? shaderSources.fragment(textureUnits) : shaderSources.fragment; const vertexShader = createShader(glw, glw.VERTEX_SHADER, vertexSource); const fragmentShader = createShader(glw, glw.FRAGMENT_SHADER, fragmentSource); if (!vertexShader || !fragmentShader) { throw new Error(`Unable to create the following shader(s): ${[ !vertexShader && 'VERTEX_SHADER', !fragmentShader && 'FRAGMENT_SHADER', ] .filter(Boolean) .join(' and ')}`); } const program = createProgram(glw, vertexShader, fragmentShader); if (!program) { throw new Error('Unable to create program'); } this.program = program; this.attributeLocations = glw.getAttributeLocations(this.program); this.uniformLocations = glw.getUniformLocations(this.program); } disableAttribute(location) { this.glw.disableVertexAttribArray(location); } disableAttributes() { const glw = this.glw; const attribLen = this.attributeLocations.length; for (let i = 0; i < attribLen; i++) { glw.disableVertexAttribArray(i); } } /** * Given two sets of Shader props destined for this Shader, determine if they can be batched together * to reduce the number of draw calls. * * @remarks * This is used by the {@link WebGlCoreRenderer} to determine if it can batch multiple consecutive draw * calls into a single draw call. * * By default, this returns false (meaning no batching is allowed), but can be * overridden by child classes to provide more efficient batching. * * @param propsA * @param propsB * @returns */ canBatchShaderProps(propsA, propsB) { return false; } bindRenderOp(renderOp, props) { this.bindBufferCollection(renderOp.buffers); // Since we're not using batched rendering yet we can safely test // for first texture only if (renderOp.textures.length > 0 && renderOp.textures[0]?.ctxTexture) { this.bindTextures(renderOp.textures); } const { glw, parentHasRenderTexture, renderToTexture } = renderOp; // Skip if the parent and current operation both have render textures if (renderToTexture && parentHasRenderTexture) { return; } // Bind render texture framebuffer dimensions as resolution // if the parent has a render texture if (parentHasRenderTexture) { const { width, height } = renderOp.framebufferDimensions || {}; // Force pixel ratio to 1.0 for render textures since they are always 1:1 // the final render texture will be rendered to the screen with the correct pixel ratio glw.uniform1f(this.getUniformLocation('u_pixelRatio'), 1.0); // Set resolution to the framebuffer dimensions glw.uniform2f(this.getUniformLocation('u_resolution'), width ?? 0, height ?? 0); } else { glw.uniform1f(this.getUniformLocation('u_pixelRatio'), renderOp.options.pixelRatio); glw.uniform2f(this.getUniformLocation('u_resolution'), glw.canvas.width, glw.canvas.height); } if (props) { // Bind optional automatic uniforms // These are only bound if their keys are present in the props. if (hasOwn(props, '$dimensions')) { let dimensions = props.$dimensions; if (!dimensions) { dimensions = renderOp.dimensions; } glw.uniform2f(this.getUniformLocation('u_dimensions'), dimensions.width, dimensions.height); } if (hasOwn(props, '$alpha')) { let alpha = props.$alpha; if (!alpha) { alpha = renderOp.alpha; } glw.uniform1f(this.getUniformLocation('u_alpha'), alpha); } this.bindProps(props); } } getUniformLocation(name) { return this.uniformLocations[name] || null; } bindBufferCollection(buffer) { const { glw } = this; const attribs = this.attributeLocations; const attribLen = attribs.length; for (let i = 0; i < attribLen; i++) { const name = attribs[i]; const resolvedBuffer = buffer.getBuffer(name); const resolvedInfo = buffer.getAttributeInfo(name); if (resolvedBuffer === undefined || resolvedInfo === undefined) { continue; } glw.enableVertexAttribArray(i); glw.vertexAttribPointer(resolvedBuffer, i, resolvedInfo.size, resolvedInfo.type, resolvedInfo.normalized, resolvedInfo.stride, resolvedInfo.offset); } } bindProps(props) { // Implement in child class } bindTextures(textures) { // no defaults } attach() { this.glw.useProgram(this.program); if (this.glw.isWebGl2() && this.vao) { this.glw.bindVertexArray(this.vao); } } detach() { this.disableAttributes(); } static shaderSources; } //# sourceMappingURL=WebGlCoreShader.js.map