Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Post Processing Improvements + Pre-multiplied alpha #2142

Merged
merged 8 commits into from
Dec 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Breaking Changes

- `ex.Util.extend()` is removed, modern js spread operator `{...someobject, ...someotherobject}` handles this better.

- Excalibur post processing is now moved to the `engine.graphicsContext.addPostProcessor()`
- Breaking change to `ex.PostProcessor`, all post processors must now now implement this interface
```typescript
export interface PostProcessor {
intialize(gl: WebGLRenderingContext): void;
getShader(): Shader;
}
```
### Deprecated

- The static `Engine.createMainLoop` is now marked deprecated and will be removed in v0.26.0, it is replaced by the `Clock` api
Expand All @@ -24,7 +31,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Pointers can now be configured to use the collider or the graphics bounds as the target for pointers with the `ex.PointerComponent`
- `useColliderShape` - (default true) uses the collider component geometry for pointer events
- `useGraphicsBounds` - (default false) uses the graphics bounds for pointer events
-


### Fixed

Expand All @@ -45,6 +52,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- The following Engine's pieces: `Collision` `Graphics` `Resources` `Trigger` are updated to reflect the new EventDispatcher behavior.

### Changed

- `ex.ColorBlindCorrector` is renamed to `ex.ColorBlindnessPostProcessor`, and `ex.ColorBlindness` is renamed to `ex.ColorBlindnessMode`
- Color blindness can still be corrected or simulated:
* `game.debug.colorBlindMode.correct(ex.ColorBlindnessMode.Deuteranope)`
* `game.debug.colorBlindMode.simulate(ex.ColorBlindnessMode.Deuteranope)`
- Excalibur now uses pre-multiplied alpha automatically, images will be unpacked into memory using `gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true)`
- Excalibur FPS is now sampled over 100ms blocks, this gives a more usable fps in the stats. The sampler is available off of the engine clock `engine.clock.fpsSampler.fps`
- Pointer Events:
* Event types (up, down, move, etc) now all exist in 2 types `ex.Input.PointerEvent` and `ex.Input.WheelEvent`
Expand Down
3 changes: 3 additions & 0 deletions sandbox/src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ var game = new ex.Engine({
maxFps: 60
});

var colorblind = new ex.ColorBlindnessPostProcessor(ex.ColorBlindnessMode.Deuteranope);
game.graphicsContext.addPostProcessor(colorblind);

fullscreenButton.addEventListener('click', () => {
if (game.screen.isFullScreen) {
game.screen.exitFullScreen();
Expand Down
18 changes: 12 additions & 6 deletions src/engine/Debug/DebugFlags.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Engine } from '../Engine';
import { ColorBlindCorrector, ColorBlindness } from '../PostProcessing/Index';
import { ColorBlindnessMode } from '../Graphics/PostProcessor/ColorBlindnessMode';
import { ColorBlindnessPostProcessor } from '../Graphics/PostProcessor/ColorBlindnessPostProcessor';
import { Engine } from '../Engine';
import { ExcaliburGraphicsContextWebGL } from '..';

export interface DebugFlags {
colorBlindMode: ColorBlindFlags;
Expand All @@ -12,11 +14,15 @@ export class ColorBlindFlags {
this._engine = engine;
}

public correct(colorBlindness: ColorBlindness) {
this._engine.postProcessors.push(new ColorBlindCorrector(this._engine, false, colorBlindness));
public correct(colorBlindness: ColorBlindnessMode) {
if (this._engine.graphicsContext instanceof ExcaliburGraphicsContextWebGL) {
this._engine.graphicsContext.addPostProcessor(new ColorBlindnessPostProcessor(colorBlindness, false));
}
}

public simulate(colorBlindness: ColorBlindness) {
this._engine.postProcessors.push(new ColorBlindCorrector(this._engine, true, colorBlindness));
public simulate(colorBlindness: ColorBlindnessMode) {
if (this._engine.graphicsContext instanceof ExcaliburGraphicsContextWebGL) {
this._engine.graphicsContext.addPostProcessor(new ColorBlindnessPostProcessor(colorBlindness, true));
}
}
}
11 changes: 0 additions & 11 deletions src/engine/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import { Logger, LogLevel } from './Util/Log';
import { Color } from './Color';
import { Scene } from './Scene';
import { Entity } from './EntityComponentSystem/Entity';
import { PostProcessor } from './PostProcessing/PostProcessor';
import { Debug, DebugStats } from './Debug/Debug';
import { Class } from './Class';
import * as Input from './Input/Index';
Expand Down Expand Up @@ -311,11 +310,6 @@ export class Engine extends Class implements CanInitialize, CanUpdate, CanDraw {
return this.debug.stats;
}

/**
* Gets or sets the list of post processors to apply at the end of drawing a frame (such as [[ColorBlindCorrector]])
*/
public postProcessors: PostProcessor[] = [];

/**
* The current [[Scene]] being drawn and updated on screen
*/
Expand Down Expand Up @@ -1110,11 +1104,6 @@ O|===|* >________________>\n\
this.ctx.fillText('FPS:' + this.stats.currFrame.fps.toFixed(2).toString(), 10, 10);
}

// Post processing
for (let i = 0; i < this.postProcessors.length; i++) {
this.postProcessors[i].process(this.ctx.getImageData(0, 0, this.canvasWidth, this.canvasHeight), this.ctx);
}

this._postdraw(ctx, delta);
}

Expand Down
20 changes: 20 additions & 0 deletions src/engine/Graphics/Context/ExcaliburGraphicsContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Vector } from '../../Math/vector';
import { Color } from '../../Color';
import { PostProcessor } from '../PostProcessor/PostProcessor';

export type HTMLImageSource = HTMLImageElement | HTMLCanvasElement;

Expand Down Expand Up @@ -179,6 +180,25 @@ export interface ExcaliburGraphicsContext {
*/
scale(x: number, y: number): void;

/**
* Add a post processor to the graphics context
*
* Post processors are run in the order they were added.
* @param postprocessor
*/
addPostProcessor(postprocessor: PostProcessor): void;

/**
* Remove a specific post processor from the graphics context
* @param postprocessor
*/
removePostProcessor(postprocessor: PostProcessor): void;

/**
* Remove all post processors from the graphics context
*/
clearPostProcessors(): void;

/**
* Clears the screen with the current background color
*/
Expand Down
13 changes: 13 additions & 0 deletions src/engine/Graphics/Context/ExcaliburGraphicsContext2DCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Color } from '../../Color';
import { StateStack } from './state-stack';
import { GraphicsDiagnostics } from '../GraphicsDiagnostics';
import { DebugText } from './debug-text';
import { PostProcessor } from '../PostProcessor/PostProcessor';

class ExcaliburGraphicsContext2DCanvasDebug implements DebugDraw {
private _debugText = new DebugText();
Expand Down Expand Up @@ -250,6 +251,18 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex
this.__ctx.scale(x, y);
}

public addPostProcessor(_postprocessor: PostProcessor) {
throw Error('Not implemented');
}

public removePostProcessor(_postprocessor: PostProcessor) {
throw Error('Not implemented');
}

public clearPostProcessors() {
throw Error('Not implemented');
}

clear(): void {
// Clear frame
this.__ctx.clearRect(0, 0, this.width, this.height);
Expand Down
77 changes: 75 additions & 2 deletions src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import { Canvas } from '../Canvas';
import { GraphicsDiagnostics } from '../GraphicsDiagnostics';
import { DebugText } from './debug-text';
import { ScreenDimension } from '../../Screen';
import { RenderTarget } from './render-target';
import { ScreenRenderer } from './screen-renderer';
import { PostProcessor } from '../PostProcessor/PostProcessor';

class ExcaliburGraphicsContextWebGLDebug implements DebugDraw {
private _debugText = new DebugText();
Expand Down Expand Up @@ -71,6 +74,12 @@ export interface WebGLGraphicsContextInfo {
}

export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
private _renderTarget: RenderTarget;

private _postProcessTargets: RenderTarget[] = [];

private _screenRenderer: ScreenRenderer;
private _postprocessors: PostProcessor[] = [];
/**
* Meant for internal use only. Access the internal context at your own risk and no guarantees this will exist in the future.
* @internal
Expand Down Expand Up @@ -173,12 +182,37 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
gl.clear(gl.COLOR_BUFFER_BIT);

// Enable alpha blending
// https://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

this.__pointRenderer = new PointRenderer(gl, { matrix: this._ortho, transform: this._transform, state: this._state });
this.__lineRenderer = new LineRenderer(gl, { matrix: this._ortho, transform: this._transform, state: this._state });
this.__imageRenderer = new ImageRenderer(gl, { matrix: this._ortho, transform: this._transform, state: this._state });
this._screenRenderer = new ScreenRenderer(gl);

this._renderTarget = new RenderTarget({
gl,
width: gl.canvas.width,
height: gl.canvas.height
});


this._postProcessTargets = [
new RenderTarget({
gl,
width: gl.canvas.width,
height: gl.canvas.height
}),
new RenderTarget({
gl,
width: gl.canvas.width,
height: gl.canvas.height
})
];

// 2D ctx shim
this._canvas = new Canvas({
Expand All @@ -199,6 +233,10 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
this.__lineRenderer.shader.addUniformMatrix('u_matrix', this._ortho.data);
this.__imageRenderer.shader.addUniformMatrix('u_matrix', this._ortho.data);

this._renderTarget.setResolution(gl.canvas.width, gl.canvas.height);
this._postProcessTargets[0].setResolution(gl.canvas.width, gl.canvas.height);
this._postProcessTargets[1].setResolution(gl.canvas.width, gl.canvas.height);

// 2D ctx shim
this._canvas.width = gl.canvas.width;
this._canvas.height = gl.canvas.height;
Expand Down Expand Up @@ -288,12 +326,30 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
this._transform.current = matrix;
}

public addPostProcessor(postprocessor: PostProcessor) {
this._postprocessors.push(postprocessor);
postprocessor.intialize(this.__gl);
}

public removePostProcessor(postprocessor: PostProcessor) {
const index = this._postprocessors.indexOf(postprocessor);
if (index !== -1) {
this._postprocessors.splice(index, 1);
}
}

public clearPostProcessors() {
this._postprocessors.length = 0;
}

clear() {
const gl = this.__gl;
this._renderTarget.use();
gl.clearColor(this.backgroundColor.r / 255, this.backgroundColor.g / 255, this.backgroundColor.b / 255, this.backgroundColor.a);
// Clear the context with the newly set color. This is
// the function call that actually does the drawing.
gl.clear(gl.COLOR_BUFFER_BIT);
this._renderTarget.disable();
GraphicsDiagnostics.clear();
}

Expand All @@ -302,10 +358,27 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
*/
flush() {
const gl = this.__gl;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

// render target captures all draws and redirects to the render target
this._renderTarget.use();
this.__imageRenderer.render();
this.__lineRenderer.render();
this.__pointRenderer.render();
this._renderTarget.disable();

// post process step
const source = this._renderTarget.toRenderSource();
source.use();

// flip flop render targets
for (let i = 0; i < this._postprocessors.length; i++) {
this._postProcessTargets[i % 2].use();
this._screenRenderer.renderWithShader(this._postprocessors[i].getShader());
this._postProcessTargets[i % 2].toRenderSource().use();
}

// passing null switches renderering back to the canvas
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
this._screenRenderer.render();
}
}
16 changes: 16 additions & 0 deletions src/engine/Graphics/Context/render-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export class RenderSource {
constructor(
private _gl: WebGLRenderingContext,
private _texture: WebGLTexture) {}

public use() {
const gl = this._gl;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._texture);
}

public disable() {
const gl = this._gl;
gl.bindTexture(gl.TEXTURE_2D, null);
}
}
Loading