Skip to content

Commit

Permalink
refactor: Renderer simplification and Render plugins (#2185)
Browse files Browse the repository at this point in the history
This PR simplifies the renderer code to prepare for custom render plugins, this will allow custom renderers with custom shaders to be built into excalibur.


## Changes:

- Add border drawing to circles and rectangles 🎉
- Split the monolithic style renderer into separate internal renderer plugins
- Improve the Shader code abstractions, adds a safety feature and error logging to avoid common shader/webgl issues
- Remove complicated pooling strategy, and remove Poolable which is invalid in stricter TS (mentioned in #2157)
  • Loading branch information
eonarheim authored Jan 9, 2022
1 parent 0359679 commit 78de713
Show file tree
Hide file tree
Showing 70 changed files with 3,195 additions and 1,281 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added

- Added ability to build custom renderer plugins that are accessible to the `ex.ExcaliburGraphicsContext.draw<TCustomRenderer>(...)` after registering them `ex.ExcaliburGraphicsContext.register(new LineRenderer())`
- Added ability to draw circles and rectangles with outlines! `ex.ExcaliburGraphicsContext.drawCircle(...)` and `ex.ExcaliburGraphicsContext.drawRectangle(...)`
- Added `ex.CoordPlane` can be set in the `new ex.Actor({coordPlane: CoordPlane.Screen})` constructor
- Added convenience feature, setting the color, sets the color on default graphic if applicable
- Added a `DebugGraphicsComponent` for doing direct debug draw in the `DebugSystem`
Expand Down Expand Up @@ -51,6 +53,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

- Fixed issue [#2157] when compiling in TS strict mode complaining about `ex.Poolable`
- Fixed issue where scaled graphics were not calculating the correct bounds
- Fixed unreleased issue where clock implementation was not updating frame id
- Fixed alpha pre-multiply math in multiple shaders
Expand Down Expand Up @@ -82,7 +85,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Refactor camera/screen interaction to utilize transforms instead of bespoke coordinate conversion
### Changed

- Chaned the debug system to separate displaying the debug position point (`game.debug.transform.showPosition = true`) and debug position label (`game.debug.transform.showPositionLabel = true`)
- Updated the webgl primitives to make building `ex.Shader`s, `ex.VertexBuffer`s, and `ex.VertexLayout`s much easier
- Broke up the internal monolithic shader into separate internal renderer plugins
- Changed the debug system to separate displaying the debug position point (`game.debug.transform.showPosition = true`) and debug position label (`game.debug.transform.showPositionLabel = true`)
- `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)`
Expand Down
1 change: 1 addition & 0 deletions sandbox/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<ul>
<li><a href="html/index.html">Sandbox Platformer</a></li>
<li><a href="tests/drawcalls/index.html">Draw Calls</a></li>
<li><a href="tests/gif/animatedGif.html">Animated Gif</a></li>
<li><a href="tests/culling/culling.html">Sprite Culling</a></li>
<li><a href="tests/culling/culling2.html">Zoom Culling</a></li>
Expand Down
19 changes: 19 additions & 0 deletions sandbox/tests/drawcalls/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Draw Call Limits and Orderings</title>
</head>
<body>
<canvas id="game"></canvas>
<div>
<div>Draw Calls:<span id="draw-calls">0</span></div>
<div># Items:<span id="drawn-items">0</span></div>
<button id="add">Add 1000 Items</button>
</div>
<script src="../../lib/excalibur.js"></script>
<script src="./index.js"></script>
</body>
</html>
63 changes: 63 additions & 0 deletions sandbox/tests/drawcalls/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/// <reference path="../../lib/excalibur.d.ts" />


var game = new ex.Engine({
canvasElementId: 'game',
width: 600,
height: 400
});

var tex = new ex.ImageSource('https://cdn.rawgit.com/excaliburjs/Excalibur/7dd48128/assets/sword.png');

var loader = new ex.Loader([tex]);

var random = new ex.Random(1337);

var items = [{pos: ex.Vector.Zero, vel: ex.vec(random.integer(50, 100), random.integer(50, 100))}];
var drawCalls$ = document.getElementById('draw-calls');
var drawnItems$ = document.getElementById('drawn-items');
var add$ = document.getElementById('add');
add$.addEventListener('click', () => {
for (let i = 0; i < 1000; i++) {
items.push({
pos: ex.vec(0, 0),
vel: ex.vec(random.integer(50, 100), random.integer(50, 100))
});
}
});

game.start(loader).then(() => {
const width = tex.width;
const height = tex.height;
game.onPostUpdate = (_engine, deltaMs) => {
for (let i = 0; i < items.length; i++) {
items[i].pos.addEqual(items[i].vel.scale(deltaMs/1000));
if ((items[i].pos.x + width / 2) > 600 || items[i].pos.x < 0) {
items[i].vel.x *= -1;
}

if ((items[i].pos.y + height / 2) > 400 || items[i].pos.y < 0) {
items[i].vel.y *= -1;
}
}
game.graphicsContext.drawCircle(ex.vec(200, 200), width / 4, ex.Color.Blue, ex.Color.Black, 2);
};

game.onPostDraw = (_engine, deltaMs) => {
const blue = ex.Color.Blue;
const black = ex.Color.Black;
for (let i = 0; i < items.length; i++) {
game.graphicsContext.drawImage(tex.image,
0, 0, width, height,
items[i].pos.x, items[i].pos.y, width / 2, height / 2);

// game.graphicsContext.drawCircle(items[i].pos, width / 4, blue, black, 2);
// game.graphicsContext.drawRectangle(items[i].pos, width, height, blue, black, 2);
}
};

game.on('postframe', () => {
drawCalls$.innerText = game.stats.currFrame.graphics.drawCalls.toString();
drawnItems$.innerText = game.stats.currFrame.graphics.drawnImages.toString();
});
});
2 changes: 1 addition & 1 deletion src/engine/Collision/Colliders/CircleCollider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export class CircleCollider extends Collider {
public debug(ex: ExcaliburGraphicsContext, color: Color) {
const tx = this._transform as TransformComponent;
const pos = tx?.globalPos ? tx?.globalPos.add(this.offset) : this.offset;
ex.drawCircle(pos, this.radius, color);
ex.drawCircle(pos, this.radius, Color.Transparent, color, 2);
}

/**
Expand Down
10 changes: 10 additions & 0 deletions src/engine/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { PointerEventReceiver } from './Input/PointerEventReceiver';
import { FpsSampler } from './Util/Fps';
import { Clock, StandardClock } from './Util/Clock';
import { ImageFiltering } from './Graphics/Filtering';
import { GraphicsDiagnostics } from './Graphics/GraphicsDiagnostics';

/**
* Enum representing the different mousewheel event bubble prevention
Expand Down Expand Up @@ -1109,6 +1110,8 @@ O|===|* >________________>\n\
*/
private _draw(delta: number) {
const ctx = this.ctx;
this.graphicsContext.beginDrawLifecycle();
this.graphicsContext.clear();
this._predraw(ctx, delta);

// Drawing nothing else while loading
Expand Down Expand Up @@ -1144,6 +1147,10 @@ O|===|* >________________>\n\
}

this._postdraw(ctx, delta);

// Flush any pending drawings
this.graphicsContext.flush();
this.graphicsContext.endDrawLifecycle();
}

/**
Expand Down Expand Up @@ -1272,6 +1279,7 @@ O|===|* >________________>\n\
this.stats.currFrame.id = frameId;
this.stats.currFrame.delta = delta;
this.stats.currFrame.fps = this.clock.fpsSampler.fps;
GraphicsDiagnostics.clear();

const beforeUpdate = this.clock.now();
this._update(delta);
Expand All @@ -1281,6 +1289,8 @@ O|===|* >________________>\n\

this.stats.currFrame.duration.update = afterUpdate - beforeUpdate;
this.stats.currFrame.duration.draw = afterDraw - afterUpdate;
this.stats.currFrame.graphics.drawnImages = GraphicsDiagnostics.DrawnImagesCount;
this.stats.currFrame.graphics.drawCalls = GraphicsDiagnostics.DrawCallCount;

this.emit('postframe', new PostFrameEvent(this, this.stats.currFrame));
this.stats.prevFrame.reset(this.stats.currFrame);
Expand Down
12 changes: 9 additions & 3 deletions src/engine/Graphics/Context/ExcaliburGraphicsContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,17 @@ export interface ExcaliburGraphicsContext {
* @param height
* @param color
*/
drawRectangle(pos: Vector, width: number, height: number, color: Color): void;
drawRectangle(pos: Vector, width: number, height: number, color: Color, stroke?: Color, strokeThickness?: number): void;

/**
* Draw a solid circle to the Excalibur Graphics context
* Draw a circle to the Excalibur Graphics context
* @param pos
* @param radius
* @param color
* @param stroke Optionally specify the stroke color
* @param thickness
*/
drawCircle(pos: Vector, radius: number, color: Color): void;
drawCircle(pos: Vector, radius: number, color: Color, stroke?: Color, thickness?: number): void;

/**
* Save the current state of the canvas to the stack (transforms and opacity)
Expand Down Expand Up @@ -221,4 +223,8 @@ export interface ExcaliburGraphicsContext {
* Flushes the batched draw calls to the screen
*/
flush(): void;

beginDrawLifecycle(): void;

endDrawLifecycle(): void
}
19 changes: 18 additions & 1 deletion src/engine/Graphics/Context/ExcaliburGraphicsContext2DCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,21 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex
this.__ctx.restore();
}

public drawCircle(pos: Vector, radius: number, color: Color) {
public drawCircle(pos: Vector, radius: number, color: Color, stroke?: Color, thickness?: number) {
this.__ctx.save();
this.__ctx.beginPath();
if (stroke) {
this.__ctx.strokeStyle = stroke.toString();
}
if (thickness) {
this.__ctx.lineWidth = thickness;
}
this.__ctx.fillStyle = color.toString();
this.__ctx.arc(this.snapToPixel ? ~~pos.x : pos.x, this.snapToPixel ? ~~pos.y : pos.y, radius, 0, Math.PI * 2);
this.__ctx.fill();
if (stroke) {
this.__ctx.stroke();
}
this.__ctx.closePath();
this.__ctx.restore();
}
Expand Down Expand Up @@ -273,6 +282,14 @@ export class ExcaliburGraphicsContext2DCanvas implements ExcaliburGraphicsContex
throw Error('Not implemented');
}

public beginDrawLifecycle() {
// pass
}

public endDrawLifecycle() {
// pass
}

clear(): void {
// Clear frame
this.__ctx.clearRect(0, 0, this.width, this.height);
Expand Down
Loading

0 comments on commit 78de713

Please sign in to comment.