Skip to content

Commit

Permalink
Update Effect interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress committed Mar 19, 2024
1 parent 14d5aa0 commit 88a27d9
Show file tree
Hide file tree
Showing 22 changed files with 219 additions and 184 deletions.
50 changes: 26 additions & 24 deletions modules/core/src/effects/lighting/lighting-effect.ts
Expand Up @@ -9,7 +9,7 @@ import ShadowPass from '../../passes/shadow-pass';
import shadow from '../../shaderlib/shadow/shadow';

import type Layer from '../../lib/layer';
import type {Effect, PreRenderOptions} from '../../lib/effect';
import type {Effect, EffectContext, PreRenderOptions} from '../../lib/effect';

const DEFAULT_AMBIENT_LIGHT_PROPS = {color: [255, 255, 255], intensity: 1.0};
const DEFAULT_DIRECTIONAL_LIGHT_PROPS = [
Expand All @@ -33,6 +33,7 @@ export default class LightingEffect implements Effect {
id = 'lighting-effect';
props!: LightingEffectProps;
shadowColor: number[] = DEFAULT_SHADOW_COLOR;
context?: EffectContext;

private shadow: boolean = false;
private ambientLight?: AmbientLight | null = null;
Expand All @@ -48,6 +49,22 @@ export default class LightingEffect implements Effect {
this.setProps(props);
}

setup(context: EffectContext) {
this.context = context;
const {device} = context;

if (this.shadow && !this.dummyShadowMap) {
this._createShadowPasses(device);
this.shaderAssembler = ShaderAssembler.getDefaultShaderAssembler();
this.shaderAssembler.addDefaultModule(shadow);

this.dummyShadowMap = device.createTexture({
width: 1,
height: 1
});
}
}

setProps(props: LightingEffectProps) {
this.ambientLight = null;
this.directionalLights = [];
Expand All @@ -73,36 +90,21 @@ export default class LightingEffect implements Effect {
}
this._applyDefaultLights();

this.shadow = this.directionalLights.some(light => light.shadow);
const useShadow = this.directionalLights.some(light => light.shadow);
if (useShadow !== this.shadow && this.context) {
// Create resources if necessary
this.setup(this.context);
}
this.shadow = useShadow;
this.props = props;
}

preRender(
device: Device,
{layers, layerFilter, viewports, onViewportActive, views}: PreRenderOptions
) {
preRender({layers, layerFilter, viewports, onViewportActive, views}: PreRenderOptions) {
if (!this.shadow) return;

// create light matrix every frame to make sure always updated from light source
this.shadowMatrices = this._calculateMatrices();

if (this.shadowPasses.length === 0) {
this._createShadowPasses(device);
}
if (!this.shaderAssembler) {
this.shaderAssembler = ShaderAssembler.getDefaultShaderAssembler();
if (shadow) {
this.shaderAssembler.addDefaultModule(shadow);
}
}

if (!this.dummyShadowMap) {
this.dummyShadowMap = device.createTexture({
width: 1,
height: 1
});
}

for (let i = 0; i < this.shadowPasses.length; i++) {
const shadowPass = this.shadowPasses[i];
shadowPass.render({
Expand Down Expand Up @@ -165,7 +167,7 @@ export default class LightingEffect implements Effect {
this.dummyShadowMap = null;
}

if (this.shadow && this.shaderAssembler) {
if (this.shaderAssembler) {
this.shaderAssembler.removeDefaultModule(shadow);
this.shaderAssembler = null!;
}
Expand Down
17 changes: 10 additions & 7 deletions modules/core/src/effects/post-process-effect.ts
Expand Up @@ -3,7 +3,7 @@ import {normalizeShaderModule, ShaderPass} from '@luma.gl/shadertools';

import ScreenPass from '../passes/screen-pass';

import type {Effect, PostRenderOptions} from '../lib/effect';
import type {Effect, EffectContext, PostRenderOptions} from '../lib/effect';

export default class PostProcessEffect<ShaderPassT extends ShaderPass> implements Effect {
id: string;
Expand All @@ -18,29 +18,32 @@ export default class PostProcessEffect<ShaderPassT extends ShaderPass> implement
this.module = module;
}

setup({device}: EffectContext) {
this.passes = createPasses(device, this.module, this.id);
}

setProps(props: ShaderPassT['props']) {
this.props = props;
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
preRender(): void {}

postRender(device: Device, params: PostRenderOptions): Framebuffer {
const passes = this.passes || createPasses(device, this.module, this.id);
this.passes = passes;
postRender(params: PostRenderOptions): Framebuffer {
const passes = this.passes!;

const {target} = params;
let inputBuffer = params.inputBuffer;
let outputBuffer: Framebuffer | null = params.swapBuffer;

for (let index = 0; index < this.passes.length; index++) {
const isLastPass = index === this.passes.length - 1;
for (let index = 0; index < passes.length; index++) {
const isLastPass = index === passes.length - 1;
if (target !== undefined && isLastPass) {
outputBuffer = target;
}
const moduleSettings = {};
moduleSettings[this.module.name] = this.props;
this.passes[index].render({inputBuffer, outputBuffer, moduleSettings});
passes[index].render({inputBuffer, outputBuffer, moduleSettings});

const switchBuffer = outputBuffer as Framebuffer;
outputBuffer = inputBuffer;
Expand Down
4 changes: 2 additions & 2 deletions modules/core/src/index.ts
Expand Up @@ -138,10 +138,10 @@ export type {FilterContext} from './passes/layers-pass';
export type {PickingInfo, GetPickingInfoParams} from './lib/picking/pick-info';
export type {ConstructorOf as _ConstructorOf} from './types/types';
export type {BinaryAttribute} from './lib/attribute/attribute';
export type {Effect, PreRenderOptions, PostRenderOptions} from './lib/effect';
export type {Effect, EffectContext, PreRenderOptions, PostRenderOptions} from './lib/effect';
export type {PickingUniforms, ProjectUniforms} from './shaderlib/index';
export type {DefaultProps} from './lifecycle/prop-types';
export type {LayersPassRenderOptions} from './passes/layers-pass';
export type {LayersPassRenderOptions, RenderStats} from './passes/layers-pass';
export type {Widget, WidgetPlacement} from './lib/widget-manager';

// INTERNAL, DO NOT USER
Expand Down
15 changes: 5 additions & 10 deletions modules/core/src/lib/deck-renderer.ts
Expand Up @@ -117,7 +117,7 @@ export default class DeckRenderer {
opts.preRenderStats = opts.preRenderStats || {};

for (const effect of effects) {
opts.preRenderStats[effect.id] = effect.preRender(this.device, opts);
opts.preRenderStats[effect.id] = effect.preRender(opts);
if (effect.postRender) {
this.lastPostProcessEffect = effect.id;
}
Expand Down Expand Up @@ -158,17 +158,12 @@ export default class DeckRenderer {
};
for (const effect of effects) {
if (effect.postRender) {
if (effect.id === this.lastPostProcessEffect) {
// Ready to render to final target
params.target = opts.target;
effect.postRender(this.device, params);
break;
}
// If not the last post processing effect, unset the target so that
// it only renders between the swap buffers
params.target = undefined;
const buffer = effect.postRender(this.device, params);
params.inputBuffer = buffer;
params.target = effect.id === this.lastPostProcessEffect ? opts.target : undefined;
const buffer = effect.postRender(params);
// Buffer cannot be null if target is unset
params.inputBuffer = buffer!;
params.swapBuffer = buffer === renderBuffers[0] ? renderBuffers[1] : renderBuffers[0];
}
}
Expand Down
5 changes: 4 additions & 1 deletion modules/core/src/lib/deck.ts
Expand Up @@ -992,7 +992,10 @@ export default class Deck<ViewsT extends ViewOrViews = ViewOrViews> {
timeline
});

this.effectManager = new EffectManager();
this.effectManager = new EffectManager({
deck: this,
device: this.device
});

this.deckRenderer = new DeckRenderer(this.device);

Expand Down
22 changes: 13 additions & 9 deletions modules/core/src/lib/effect-manager.ts
@@ -1,6 +1,6 @@
import {deepEqual} from '../utils/deep-equal';
import LightingEffect from '../effects/lighting/lighting-effect';
import type {Effect} from './effect';
import type {Effect, EffectContext} from './effect';

const DEFAULT_LIGHTING_EFFECT = new LightingEffect();

Expand All @@ -17,9 +17,11 @@ export default class EffectManager {
/** Effect instances and order preference pairs, sorted by order */
private _defaultEffects: Effect[] = [];
private _needsRedraw: false | string;
private _context: EffectContext;

constructor() {
constructor(context: EffectContext) {
this.effects = [];
this._context = context;
this._needsRedraw = 'Initial render';
this._setEffects([]);
}
Expand All @@ -36,6 +38,7 @@ export default class EffectManager {
} else {
defaultEffects.splice(index, 0, effect);
}
effect.setup(this._context);
this._setEffects(this.effects);
}
}
Expand Down Expand Up @@ -70,21 +73,22 @@ export default class EffectManager {
const nextEffects: Effect[] = [];
for (const effect of effects) {
const oldEffect = oldEffectsMap[effect.id];
let effectToAdd = effect;
if (oldEffect && oldEffect !== effect) {
if (oldEffect.setProps) {
oldEffect.setProps(effect.props);
nextEffects.push(oldEffect);
effectToAdd = oldEffect;
} else {
oldEffect.cleanup();
nextEffects.push(effect);
oldEffect.cleanup(this._context);
}
} else {
nextEffects.push(effect);
} else if (!oldEffect) {
effect.setup(this._context);
}
nextEffects.push(effectToAdd);
delete oldEffectsMap[effect.id];
}
for (const removedEffectId in oldEffectsMap) {
oldEffectsMap[removedEffectId].cleanup();
oldEffectsMap[removedEffectId].cleanup(this._context);
}
this.effects = nextEffects;

Expand All @@ -98,7 +102,7 @@ export default class EffectManager {

finalize() {
for (const effect of this._resolvedEffects) {
effect.cleanup();
effect.cleanup(this._context);
}

this.effects.length = 0;
Expand Down
26 changes: 21 additions & 5 deletions modules/core/src/lib/effect.ts
@@ -1,3 +1,4 @@
import type Deck from './deck';
import type Layer from './layer';
import type {LayersPassRenderOptions} from '../passes/layers-pass';
import type {Device} from '@luma.gl/core';
Expand All @@ -8,17 +9,32 @@ export type PostRenderOptions = LayersPassRenderOptions & {
inputBuffer: Framebuffer;
swapBuffer: Framebuffer;
};
export type EffectContext = {
deck: Deck;
device: Device;
};

export interface Effect {
id: string;
props: any;
/** If true, this effect will also be used when rendering to the picking buffer */
useInPicking?: boolean;
/** Effects with smaller value gets executed first. If not provided, will get executed in the order added. */
order?: number;

preRender: (device: Device, opts: PreRenderOptions) => void;
postRender?: (device: Device, opts: PostRenderOptions) => Framebuffer;
getModuleParameters?: (layer: Layer) => any;
// / Render methods
/** Called before layers are rendered to screen */
preRender(opts: PreRenderOptions): void;
/** Called after layers are rendered to screen */
postRender?(opts: PostRenderOptions): Framebuffer | null;
/** Module settings passed to models */
getModuleParameters?(layer: Layer): any;

setProps?: (props: any) => void;
cleanup(): void;
// / Lifecycle methods
/** Called when this effect is added */
setup(context: EffectContext): void;
/** Called when the effect's props are updated. */
setProps?(props: any): void;
/** Called when this effect is removed */
cleanup(context: EffectContext): void;
}
4 changes: 2 additions & 2 deletions modules/core/src/passes/layers-pass.ts
Expand Up @@ -56,7 +56,7 @@ export type RenderStats = {
export default class LayersPass extends Pass {
_lastRenderIndex: number = -1;

render(options: LayersPassRenderOptions): any {
render(options: LayersPassRenderOptions): RenderStats[] {
// @ts-expect-error TODO - assuming WebGL context
const [width, height] = this.device.canvasContext.getDrawingBufferSize();

Expand All @@ -82,7 +82,7 @@ export default class LayersPass extends Pass {
}

/** Draw a list of layers in a list of viewports */
private _drawLayers(renderPass: RenderPass, options: LayersPassRenderOptions) {
private _drawLayers(renderPass: RenderPass, options: LayersPassRenderOptions): RenderStats[] {
const {
target,
moduleParameters,
Expand Down
42 changes: 21 additions & 21 deletions modules/extensions/src/collision-filter/collision-filter-effect.ts
@@ -1,7 +1,7 @@
import {Device, Framebuffer, Texture} from '@luma.gl/core';
import {equals} from '@math.gl/core';
import {_deepEqual as deepEqual} from '@deck.gl/core';
import type {Effect, Layer, PreRenderOptions, Viewport} from '@deck.gl/core';
import type {Effect, EffectContext, Layer, PreRenderOptions, Viewport} from '@deck.gl/core';
import CollisionFilterPass from './collision-filter-pass';
import MaskEffect, {MaskPreRenderStats} from '../mask/mask-effect';
// import {debugFBO} from '../utils/debug';
Expand All @@ -27,28 +27,32 @@ export default class CollisionFilterEffect implements Effect {
useInPicking = true;
order = 1;

private context?: EffectContext;
private channels: Record<string, RenderInfo> = {};
private collisionFilterPass?: CollisionFilterPass;
private collisionFBOs: Record<string, Framebuffer> = {};
private dummyCollisionMap?: Texture;
private lastViewport?: Viewport;

preRender(
device: Device,
{
effects: allEffects,
layers,
layerFilter,
viewports,
onViewportActive,
views,
isPicking,
preRenderStats = {}
}: PreRenderOptions
): void {
if (!this.dummyCollisionMap) {
this.dummyCollisionMap = device.createTexture({width: 1, height: 1});
}
setup(context: EffectContext) {
this.context = context;
const {device} = context;
this.dummyCollisionMap = device.createTexture({width: 1, height: 1});
this.collisionFilterPass = new CollisionFilterPass(device, {id: 'default-collision-filter'});
}

preRender({
effects: allEffects,
layers,
layerFilter,
viewports,
onViewportActive,
views,
isPicking,
preRenderStats = {}
}: PreRenderOptions): void {
// This can only be called in preRender() after setup() where context is populated
const {device} = this.context!;

if (isPicking) {
// Do not update on picking pass
Expand All @@ -64,10 +68,6 @@ export default class CollisionFilterEffect implements Effect {
return;
}

if (!this.collisionFilterPass) {
this.collisionFilterPass = new CollisionFilterPass(device, {id: 'default-collision-filter'});
}

// Detect if mask has rendered. TODO: better dependency system for Effects
const effects = allEffects?.filter(e => e.constructor === MaskEffect);
const maskEffectRendered = (preRenderStats['mask-effect'] as MaskPreRenderStats)?.didRender;
Expand Down

0 comments on commit 88a27d9

Please sign in to comment.