@lightningjs/renderer
Version:
Lightning 3 Renderer
202 lines • 8.44 kB
JavaScript
/*
* 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