@luma.gl/engine
Version:
3D Engine Components for luma.gl
181 lines • 7.21 kB
JavaScript
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { initializeShaderModule } from '@luma.gl/shadertools';
import { ShaderInputs } from "../shader-inputs.js";
import { ClipSpace } from "../models/clip-space.js";
import { SwapFramebuffers } from "../compute/swap.js";
import { BackgroundTextureModel } from "../models/billboard-texture-model.js";
import { getFragmentShaderForRenderPass } from "./get-fragment-shader.js";
/** A pass that renders a given texture into screen space */
export class ShaderPassRenderer {
device;
shaderInputs;
passRenderers;
swapFramebuffers;
textureModel;
constructor(device, props) {
this.device = device;
props.shaderPasses.map(shaderPass => initializeShaderModule(shaderPass));
const modules = props.shaderPasses.reduce((object, shaderPass) => ({ ...object, [shaderPass.name]: shaderPass }), {});
this.shaderInputs = props.shaderInputs || new ShaderInputs(modules);
const size = device.getCanvasContext().getDrawingBufferSize();
this.swapFramebuffers = new SwapFramebuffers(device, {
colorAttachments: [device.preferredColorFormat],
width: size[0],
height: size[1]
});
this.textureModel = new BackgroundTextureModel(device, {
backgroundTexture: this.swapFramebuffers.current.colorAttachments[0].texture
});
this.passRenderers = props.shaderPasses.map(shaderPass => new PassRenderer(device, shaderPass));
}
/** Destroys resources created by this ShaderPassRenderer */
destroy() {
for (const subPassRenderer of this.passRenderers) {
subPassRenderer.destroy();
}
this.swapFramebuffers.destroy();
this.textureModel.destroy();
}
resize(size) {
size ||= this.device.getCanvasContext().getDrawingBufferSize();
this.swapFramebuffers.resize({ width: size[0], height: size[1] });
}
renderToScreen(options) {
// Run the shader passes and generate an output texture
const outputTexture = this.renderToTexture(options);
if (!outputTexture) {
// source texture not yet loaded
return false;
}
const framebuffer = this.device
.getDefaultCanvasContext()
.getCurrentFramebuffer({ depthStencilFormat: false });
const renderPass = this.device.beginRenderPass({
id: 'shader-pass-renderer-to-screen',
framebuffer,
// clearColor: [1, 1, 0, 1],
clearDepth: false
});
this.textureModel.setProps({ backgroundTexture: outputTexture });
this.textureModel.draw(renderPass);
renderPass.end();
return true;
}
/** Runs the shaderPasses in sequence on the sourceTexture and returns a texture with the results.
* @returns null if the the sourceTexture has not yet been loaded
*/
renderToTexture(options) {
const { sourceTexture } = options;
if (!sourceTexture.isReady) {
return null;
}
// If no shader passes are provided, just return the original texture
if (this.passRenderers.length === 0) {
return sourceTexture.texture;
}
this.textureModel.setProps({ backgroundTexture: sourceTexture });
// Clear the current texture before we begin
const clearTexturePass = this.device.beginRenderPass({
id: 'shader-pass-renderer-clear-texture',
framebuffer: this.swapFramebuffers.current,
clearColor: [1, 0, 0, 1]
});
this.textureModel.draw(clearTexturePass);
clearTexturePass.end();
// Copy the texture contents
// const commandEncoder = this.device.createCommandEncoder();
// commandEncoder.copyTextureToTexture({
// sourceTexture: sourceTexture.texture,
// destinationTexture: this.swapFramebuffers.current.colorAttachments[0].texture
// });
// commandEncoder.finish();
let first = true;
for (const passRenderer of this.passRenderers) {
for (const subPassRenderer of passRenderer.subPassRenderers) {
if (!first) {
this.swapFramebuffers.swap();
}
first = false;
const swapBufferTexture = this.swapFramebuffers.current.colorAttachments[0].texture;
const bindings = {
sourceTexture: swapBufferTexture
// texSize: [sourceTextures.width, sourceTextures.height]
};
const renderPass = this.device.beginRenderPass({
id: 'shader-pass-renderer-run-pass',
framebuffer: this.swapFramebuffers.next,
clearColor: [0, 0, 0, 1],
clearDepth: 1
});
subPassRenderer.render({ renderPass, bindings });
renderPass.end();
}
}
this.swapFramebuffers.swap();
const outputTexture = this.swapFramebuffers.current.colorAttachments[0].texture;
return outputTexture;
}
}
/** renders one ShaderPass */
class PassRenderer {
shaderPass;
subPassRenderers;
constructor(device, shaderPass, props = {}) {
this.shaderPass = shaderPass;
// const id = `${shaderPass.name}-pass`;
const subPasses = shaderPass.passes || [];
// normalizePasses(gl, module, id, props);
this.subPassRenderers = subPasses.map(subPass => {
// const idn = `${id}-${subPasses.length + 1}`;
return new SubPassRenderer(device, shaderPass, subPass);
});
}
destroy() {
for (const subPassRenderer of this.subPassRenderers) {
subPassRenderer.destroy();
}
}
}
/** Renders one subpass of a ShaderPass */
class SubPassRenderer {
model;
shaderPass;
subPass;
constructor(device, shaderPass, subPass) {
this.shaderPass = shaderPass;
this.subPass = subPass;
const action = subPass.action || (subPass.filter && 'filter') || (subPass.sampler && 'sample') || 'filter';
const fs = getFragmentShaderForRenderPass({
shaderPass,
action,
shadingLanguage: device.info.shadingLanguage
});
this.model = new ClipSpace(device, {
id: `${shaderPass.name}-subpass`,
source: fs,
fs,
modules: [shaderPass],
parameters: {
depthWriteEnabled: false
}
});
}
destroy() {
this.model.destroy();
}
render(options) {
const { renderPass, bindings } = options;
this.model.shaderInputs.setProps({
[this.shaderPass.name]: this.shaderPass.uniforms || {}
});
this.model.shaderInputs.setProps({
[this.shaderPass.name]: this.subPass.uniforms || {}
});
// this.model.setBindings(this.subPass.bindings || {});
this.model.setBindings(bindings || {});
this.model.draw(renderPass);
}
}
//# sourceMappingURL=shader-pass-renderer.js.map