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

perf: Use geometry instancing in new ImageRendererV2 #3278

Merged
merged 15 commits into from
Nov 29, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ are doing mtv adjustments during precollision.

### Updates

- Perf improvement to image rendering! with ImageRendererV2! Roughly doubles the performance of image rendering
- Perf improvement to retrieving components with `ex.Entity.get()` which widely improves engine performance
- Non-breaking parameters that reference `delta` to `elapsedMs` to better communicate intent and units
- Perf improvements to `ex.ParticleEmitter`
Expand Down
7 changes: 5 additions & 2 deletions sandbox/tests/drawcalls/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ 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 items = [
{ pos: ex.Vector.Zero, vel: ex.vec(random.integer(50, 100), random.integer(50, 100)) },
{ 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');
Expand All @@ -39,7 +42,7 @@ game.start(loader).then(() => {
items[i].vel.y *= -1;
}
}
game.graphicsContext.drawCircle(ex.vec(200, 200), width / 4, ex.Color.Blue, ex.Color.Black, 2);
// game.graphicsContext.drawCircle(ex.vec(200, 200), width / 4, ex.Color.Blue, ex.Color.Black, 2);
};

game.onPostDraw = (_engine, deltaMs) => {
Expand Down
7 changes: 7 additions & 0 deletions src/engine/Flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export class Flags {
Flags.enable('use-canvas-context');
}

/**
* Force excalibur to use the less optimized image renderer
*/
public static useLegacyImageRenderer() {
Flags.enable('use-legacy-image-renderer');
}

/**
* Freeze all flag modifications making them readonly
*/
Expand Down
47 changes: 32 additions & 15 deletions src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { Material, MaterialOptions } from './material';
import { MaterialRenderer } from './material-renderer/material-renderer';
import { Shader, ShaderOptions } from './shader';
import { GarbageCollector } from '../../GarbageCollector';
import { ImageRendererV2 } from './image-renderer-v2/image-renderer-v2';
import { Flags } from '../../Flags';

export const pixelSnapEpsilon = 0.0001;

Expand Down Expand Up @@ -97,20 +99,11 @@ export interface ExcaliburGraphicsContextWebGLOptions extends ExcaliburGraphicsC
export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
private _logger = Logger.getInstance();
private _renderers: Map<string, RendererPlugin> = new Map<string, RendererPlugin>();
public imageRenderer: 'ex.image' | 'ex.image-v2' = Flags.isEnabled('use-legacy-image-renderer') ? 'ex.image' : 'ex.image-v2';
private _isDrawLifecycle = false;
public useDrawSorting = true;

private _drawCallPool = new Pool<DrawCall>(
() => new DrawCall(),
(instance) => {
instance.priority = 0;
instance.z = 0;
instance.renderer = undefined as any;
instance.args = undefined as any;
return instance;
},
4000
);
private _drawCallPool = new Pool<DrawCall>(() => new DrawCall(), undefined, 4000);

private _drawCallIndex = 0;
private _drawCalls: DrawCall[] = new Array(4000).fill(null);
Expand Down Expand Up @@ -311,7 +304,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
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);

gl.depthMask(false);
// Setup builtin renderers
this.register(
new ImageRenderer({
Expand All @@ -324,6 +317,12 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
this.register(new CircleRenderer());
this.register(new PointRenderer());
this.register(new LineRenderer());
this.register(
new ImageRendererV2({
uvPadding: this.uvPadding,
pixelArtSampler: this.pixelArtSampler
})
);

this.materialScreenTexture = gl.createTexture();
if (!this.materialScreenTexture) {
Expand Down Expand Up @@ -398,6 +397,11 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
}

public draw<TRenderer extends RendererPlugin>(rendererName: TRenderer['type'], ...args: Parameters<TRenderer['draw']>) {
if (process.env.NODE_ENV === 'development') {
if (args.length > 9) {
throw new Error('Only 10 or less renderer arguments are supported!;');
}
}
if (!this._isDrawLifecycle) {
this._logger.warnOnce(
`Attempting to draw outside the the drawing lifecycle (preDraw/postDraw) is not supported and is a source of bugs/errors.\n` +
Expand All @@ -421,7 +425,16 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
drawCall.state.opacity = this._state.current.opacity;
drawCall.state.tint = this._state.current.tint;
drawCall.state.material = this._state.current.material;
drawCall.args = args;
drawCall.args[0] = args[0];
drawCall.args[1] = args[1];
drawCall.args[2] = args[2];
drawCall.args[3] = args[3];
drawCall.args[4] = args[4];
drawCall.args[5] = args[5];
drawCall.args[6] = args[6];
drawCall.args[7] = args[7];
drawCall.args[8] = args[8];
drawCall.args[9] = args[9];
this._drawCalls[this._drawCallIndex++] = drawCall;
} else {
// Set the current renderer if not defined
Expand All @@ -435,7 +448,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
}

// If we are still using the same renderer we can add to the current batch
renderer.draw(...args);
renderer.draw(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);

this._currentRenderer = renderer;
}
Expand Down Expand Up @@ -523,7 +536,11 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
if (this._state.current.material) {
this.draw<MaterialRenderer>('ex.material', image, sx, sy, swidth, sheight, dx, dy, dwidth, dheight);
} else {
this.draw<ImageRenderer>('ex.image', image, sx, sy, swidth, sheight, dx, dy, dwidth, dheight);
if (this.imageRenderer === 'ex.image') {
this.draw<ImageRenderer>(this.imageRenderer, image, sx, sy, swidth, sheight, dx, dy, dwidth, dheight);
} else {
this.draw<ImageRendererV2>(this.imageRenderer, image, sx, sy, swidth, sheight, dx, dy, dwidth, dheight);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/engine/Graphics/Context/draw-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export class DrawCall {
tint: Color.White,
material: null
};
public args!: any[];
public args: any[] = new Array(10);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#version 300 es
precision mediump float;

// UV coord
in vec2 v_texcoord;

// Textures in the current draw
uniform sampler2D u_textures[%%count%%];

uniform bool u_pixelart;

in float v_texture_index;

in float v_opacity;

in vec4 v_tint;

// texture resolution
in vec2 v_res;

in vec2 v_uv_min;
in vec2 v_uv_max;

out vec4 fragColor;

// Inigo Quilez pixel art filter https://jorenjoestar.github.io/post/pixel_art_filtering/
vec2 uv_iq(in vec2 uv, in vec2 texture_size) {
vec2 pixel = uv * texture_size;

vec2 seam=floor(pixel+.5);
vec2 dudv=fwidth(pixel);
pixel=seam+clamp((pixel-seam)/dudv,-.5,.5);

return pixel/texture_size;
}

float lerp(float from, float to, float rel){
return ((1. - rel) * from) + (rel * to);
}

float invLerp(float from, float to, float value){
return (value - from) / (to - from);
}

float remap(float origFrom, float origTo, float targetFrom, float targetTo, float value){
float rel = invLerp(origFrom, origTo, value);
return lerp(targetFrom, targetTo, rel);
}

void main(){
// In order to support the most efficient sprite batching, we have multiple
// textures loaded into the gpu (usually 8) this picker logic skips over textures
// that do not apply to a particular sprite.

vec4 color=vec4(1.,0,0,1.);

// GLSL is templated out to pick the right texture and set the vec4 color
vec2 uv = u_pixelart ? uv_iq(v_texcoord, v_res) : v_texcoord;
uv.x = remap(0.,1., v_uv_min.x, v_uv_max.x, uv.x);
uv.y = remap(0.,1., v_uv_min.y, v_uv_max.y, uv.y);

%%texture_picker%%

color.rgb = color.rgb * v_opacity;
color.a = color.a * v_opacity;
fragColor = color * v_tint;
}
Loading
Loading