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

fix: Performance improvements avoid expensive calculations #2197

Merged
merged 6 commits into from
Jan 18, 2022
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Refactor camera/screen interaction to utilize transforms instead of bespoke coordinate conversion
### Changed

- Updated Graphics to improve general performance
- 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`)
Expand Down
32 changes: 17 additions & 15 deletions src/engine/Graphics/Font.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,13 @@ export class Font extends Graphic implements FontRenderer {
});
}

private _setDimension(text: string, bitmap: CanvasRenderingContext2D): BoundingBox {
const textBounds = this.measureText(text);
private _setDimension(textBounds: BoundingBox, bitmap: CanvasRenderingContext2D) {

// Changing the width and height clears the context properties
// We double the bitmap width to account for all possible alignment
// We scale by "quality" so we render text without jaggies
bitmap.canvas.width = (textBounds.width + this.padding * 2) * 2 * this.quality;
bitmap.canvas.height = (textBounds.height + this.padding * 2) * 2 * this.quality;

return textBounds;
}

protected _postDraw(ex: ExcaliburGraphicsContext): void {
Expand Down Expand Up @@ -281,27 +278,29 @@ export class Font extends Graphic implements FontRenderer {
}
this.checkAndClearCache();
const bitmap = this._getTextBitmap(text, colorOverride);
const bounds = this._setDimension(text, bitmap);
this._textBounds = bounds;
const isNewBitmap = !this._bitmapUsage.get(bitmap);
this._textBounds = this.measureText(text);

bitmap.canvas.width = (bounds.width + this.padding * 2) * 2 * this.quality;
bitmap.canvas.height = (bounds.height + this.padding * 2) * 2 * this.quality;
if (isNewBitmap) {
this._setDimension(this._textBounds, bitmap);
}

this._preDraw(ex, x, y);

const lines = text.split('\n');
const lineHeight = bounds.height / lines.length;
const lineHeight = this._textBounds.height / lines.length;

// draws the text to the bitmap
this._drawText(bitmap, text, colorOverride, lineHeight);
// Cache the bitmap for certain amount of time
this._bitmapUsage.set(bitmap, performance.now());

const rasterWidth = bitmap.canvas.width;
const rasterHeight = bitmap.canvas.height;

// draws the bitmap to excalibur graphics context
TextureLoader.load(bitmap.canvas, this.filtering, true);
if (isNewBitmap) {
// draws the text to the bitmap
this._drawText(bitmap, text, colorOverride, lineHeight);
// draws the bitmap to excalibur graphics context
TextureLoader.load(bitmap.canvas, this.filtering, true);
}

ex.drawImage(
bitmap.canvas,
0,
Expand All @@ -315,6 +314,9 @@ export class Font extends Graphic implements FontRenderer {
);

this._postDraw(ex);

// Cache the bitmap for certain amount of time
this._bitmapUsage.set(bitmap, performance.now());
}

/**
Expand Down
79 changes: 69 additions & 10 deletions src/engine/Graphics/Graphic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Vector, vec } from '../Math/vector';
import { ExcaliburGraphicsContext } from './Context/ExcaliburGraphicsContext';
import { BoundingBox } from '../Collision/BoundingBox';
import { Matrix } from '..';
import { watch } from '../Util/Watch';

export interface GraphicOptions {
/**
Expand Down Expand Up @@ -49,41 +51,91 @@ export abstract class Graphic {
private static _ID: number = 0;
readonly id = Graphic._ID++;

public transform: Matrix = Matrix.identity();
private _transformStale = true;
public isStale() {
return this._transformStale;
}

/**
* Gets or sets wether to show debug information about the graphic
*/
public showDebug: boolean = false;


private _flipHorizontal = false;
/**
* Gets or sets the flipHorizontal, which will flip the graphic horizontally (across the y axis)
*/
public flipHorizontal: boolean = false;
public get flipHorizontal(): boolean {
return this._flipHorizontal;
}

public set flipHorizontal(value: boolean) {
this._flipHorizontal = value;
this._transformStale = true;
}

private _flipVertical = false;
/**
* Gets or sets the flipVertical, which will flip the graphic vertically (across the x axis)
*/
public flipVertical: boolean = false;
public get flipVertical(): boolean {
return this._flipVertical;
}

public set flipVertical(value: boolean) {
this._flipVertical = value;
this._transformStale = true;
}

private _rotation = 0;
/**
* Gets or sets the rotation of the graphic
*/
public rotation: number = 0;
public get rotation(): number {
return this._rotation;
}

public set rotation(value: number) {
this._rotation = value;
this._transformStale = true;
}

/**
* Gets or sets the opacity of the graphic, 0 is transparent, 1 is solid (opaque).
*/
public opacity: number = 1;

private _scale = Vector.One;
/**
* Gets or sets the scale of the graphic, this affects the width and
*/
public scale = Vector.One;
public get scale() {
return this._scale;
}

public set scale(value: Vector) {
this._scale = watch(value, () => {
this._transformStale = true;
});
this._transformStale = true;
}

private _origin: Vector | null = null;
/**
* Gets or sets the origin of the graphic, if not set the center of the graphic is the origin
*/
public origin: Vector | null = null;
public get origin(): Vector | null {
return this._origin;
}

public set origin(value: Vector | null) {
this._origin = watch(value, () => {
this._transformStale = true;
});
this._transformStale = true;
}

constructor(options?: GraphicOptions) {
if (options) {
Expand Down Expand Up @@ -127,10 +179,12 @@ export abstract class Graphic {

public set width(value: number) {
this._width = value;
this._transformStale = true;
}

public set height(value: number) {
this._height = value;
this._transformStale = true;
}

/**
Expand Down Expand Up @@ -171,14 +225,19 @@ export abstract class Graphic {
protected _preDraw(ex: ExcaliburGraphicsContext, x: number, y: number): void {
ex.save();
ex.translate(x, y);
ex.scale(Math.abs(this.scale.x), Math.abs(this.scale.y));
this._rotate(ex);
this._flip(ex);
if (this._transformStale) {
this.transform.reset();
this.transform.scale(Math.abs(this.scale.x), Math.abs(this.scale.y));
this._rotate(this.transform);
this._flip(this.transform);
this._transformStale = false;
}
ex.multiply(this.transform);
// it is important to multiply alphas so graphics respect the current context
ex.opacity = ex.opacity * this.opacity;
}

protected _rotate(ex: ExcaliburGraphicsContext) {
protected _rotate(ex: ExcaliburGraphicsContext | Matrix) {
const scaleDirX = this.scale.x > 0 ? 1 : -1;
const scaleDirY = this.scale.y > 0 ? 1 : -1;
const origin = this.origin ?? vec(this.width / 2, this.height / 2);
Expand All @@ -189,7 +248,7 @@ export abstract class Graphic {
ex.translate(-origin.x, -origin.y);
}

protected _flip(ex: ExcaliburGraphicsContext) {
protected _flip(ex: ExcaliburGraphicsContext | Matrix) {
if (this.flipHorizontal) {
ex.translate(this.width / this.scale.x, 0);
ex.scale(-1, 1);
Expand Down
7 changes: 6 additions & 1 deletion src/engine/Graphics/ImageSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ export class ImageSource implements Loadable<HTMLImageElement> {
return this.image.naturalHeight;
}

private _src: string;
/**
* Returns true if the Texture is completely loaded and is ready
* to be drawn.
*/
public isLoaded(): boolean {
return !!this.data.src;
if (!this._src) {
// this boosts speed of access
this._src = this.data.src;
}
return !!this._src;
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/engine/Graphics/Sprite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class Sprite extends Graphic {
public image: ImageSource;
public sourceView: SourceView;
public destSize: DestinationSize;
private _dirty = true;

public static from(image: ImageSource): Sprite {
return new Sprite({
Expand Down Expand Up @@ -82,15 +83,15 @@ export class Sprite extends Graphic {
}

protected _preDraw(ex: ExcaliburGraphicsContext, x: number, y: number): void {
if (this.image.isLoaded()) {
if (this.image.isLoaded() && this._dirty) {
this._dirty = false;
this._updateSpriteDimensions();
}
super._preDraw(ex, x, y);
}

public _drawImage(ex: ExcaliburGraphicsContext, x: number, y: number): void {
if (this.image.isLoaded()) {
this._updateSpriteDimensions();
ex.drawImage(
this.image.image,
this.sourceView.x,
Expand Down
13 changes: 8 additions & 5 deletions src/engine/Graphics/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,14 @@ export class Text extends Graphic {
if (this.font instanceof Font) {
color = this.color ?? this.font.color;
}
this.font.flipHorizontal = this.flipHorizontal;
this.font.flipVertical = this.flipVertical;
this.font.rotation = this.rotation;
this.font.origin = this.origin;
this.font.opacity = this.opacity;

if (this.isStale() || this.font.isStale()) {
this.font.flipHorizontal = this.flipHorizontal;
this.font.flipVertical = this.flipVertical;
this.font.rotation = this.rotation;
this.font.origin = this.origin;
this.font.opacity = this.opacity;
}

const { width, height } = this.font.measureText(this._text);
this._textWidth = width;
Expand Down
Binary file modified src/spec/images/GraphicsTextSpec/rotated-right.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/spec/images/GraphicsTextSpec/rotated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.