UNPKG

@luma.gl/engine

Version:

3D Engine Components for luma.gl

197 lines (194 loc) 7.42 kB
// 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; /** For rendering to the screen */ clipSpace; 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().getPixelSize(); this.swapFramebuffers = new SwapFramebuffers(device, { colorAttachments: ['rgba8unorm'], width: size[0], height: size[1] }); this.textureModel = new BackgroundTextureModel(device, { backgroundTexture: this.swapFramebuffers.current.colorAttachments[0].texture }); this.clipSpace = new ClipSpace(device, { source: /* wgsl */ `\ @group(0) @binding(0) var sourceTexture: texture_2d<f32>; @group(0) @binding(1) var sourceTextureSampler: sampler; @fragment fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> { let texCoord: vec2<f32> = inputs.coordinate; return textureSample(sourceTexture, sourceTextureSampler, texCoord); } `, fs: /* glsl */ `\ #version 300 es uniform sampler2D sourceTexture; in vec2 uv; in vec2 coordinate; out vec4 fragColor; void main() { vec2 texCoord = coordinate; fragColor = texture(sourceTexture, coordinate); } ` }); 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.clipSpace.destroy(); } resize(width, height) { this.swapFramebuffers.resize({ width, height }); // this.props.passes.forEach(pass => pass.resize(width, height)); } 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 renderPass = this.device.beginRenderPass({ clearColor: [0, 0, 0, 1], clearDepth: 1 }); this.clipSpace.setBindings({ sourceTexture: outputTexture }); this.clipSpace.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; } this.textureModel.destroy(); this.textureModel = new BackgroundTextureModel(this.device, { backgroundTexture: sourceTexture }); // Clear the current texture before we begin const clearTexturePass = this.device.beginRenderPass({ framebuffer: this.swapFramebuffers.current, clearColor: [0, 0, 0, 1] }); this.textureModel.draw(clearTexturePass); clearTexturePass.end(); // 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({ 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, depthCompare: 'always' } }); } 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