diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f47356c2..f353f779b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,11 +15,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
--
+- New PostProcessor.onDraw() hook to handle uploading textures
### Fixed
- Fixed CollidePolygonPolygon crash with some defense against invalid separation
+- Fixed issue with PostProcessor where it would not run correctly if no actors present
### Updates
diff --git a/sandbox/tests/clip-canvas/index.html b/sandbox/tests/clip-canvas/index.html
new file mode 100644
index 000000000..0d4633ad6
--- /dev/null
+++ b/sandbox/tests/clip-canvas/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Clip Canvas
+
+
+
+
+
+
diff --git a/sandbox/tests/clip-canvas/index.ts b/sandbox/tests/clip-canvas/index.ts
new file mode 100644
index 000000000..e2e14f382
--- /dev/null
+++ b/sandbox/tests/clip-canvas/index.ts
@@ -0,0 +1,55 @@
+var engine = new ex.Engine({
+ width: 800,
+ height: 800,
+ displayMode: ex.DisplayMode.FitScreen
+});
+
+var mouse = ex.vec(400, 400);
+
+var canvas = new ex.Canvas({
+ width: 800,
+ height: 800,
+ draw: (ctx) => {
+ // Clipping path
+ ctx.beginPath();
+ ctx.rect(0, 0, 800, 800); // Outer rectangle
+ ctx.arc(mouse.x, mouse.y, 60, 0, Math.PI * 2, true); // Hole anticlockwise
+ ctx.clip();
+
+ // Draw background
+ ctx.fillStyle = ex.Color.Black.toString();
+ ctx.fillRect(0, 0, 800, 800);
+ }
+});
+
+engine.input.pointers.on('move', (evt) => {
+ mouse.x = evt.screenPos.x;
+ mouse.y = evt.screenPos.y;
+ canvas.flagDirty();
+});
+
+var mask = new ex.Actor({
+ anchor: ex.vec(0, 0),
+ pos: ex.vec(0, 0),
+ coordPlane: ex.CoordPlane.Screen,
+ z: 1 // on top
+});
+mask.graphics.use(canvas);
+engine.add(mask);
+
+var normal = new ex.Actor({
+ pos: ex.vec(400, 400),
+ color: ex.Color.Red,
+ width: 100,
+ height: 100
+});
+
+normal.actions.repeatForever((ctx) => {
+ ctx.moveBy({ offset: ex.vec(100, 0), duration: 1000 });
+ ctx.moveBy({ offset: ex.vec(0, 100), duration: 1000 });
+ ctx.moveBy({ offset: ex.vec(-100, 0), duration: 1000 });
+ ctx.moveBy({ offset: ex.vec(0, -100), duration: 1000 });
+});
+engine.add(normal);
+
+engine.start();
diff --git a/sandbox/tests/occluder/index.html b/sandbox/tests/occluder/index.html
new file mode 100644
index 000000000..b43d83cce
--- /dev/null
+++ b/sandbox/tests/occluder/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Occluder
+
+
+
+
+
+
diff --git a/sandbox/tests/occluder/index.ts b/sandbox/tests/occluder/index.ts
new file mode 100644
index 000000000..d436e9a9a
--- /dev/null
+++ b/sandbox/tests/occluder/index.ts
@@ -0,0 +1,98 @@
+var occluder = new ex.ImageSource('test.png');
+
+var shader = `#version 300 es
+precision mediump float;
+
+uniform sampler2D u_image; // Default texture slot
+uniform sampler2D u_myTexture; // Slot 2 texture
+
+uniform vec2 u_resolution;
+uniform vec2 u_texturePosition; // Position of the second texture
+uniform vec2 u_textureSize; // Size of the second texture
+
+in vec2 v_uv;
+out vec4 fragColor;
+
+void main() {
+ vec2 pixelCoord = v_uv * u_resolution;
+ vec2 adjustedUV = (pixelCoord - u_texturePosition) / u_textureSize;
+
+ vec4 defaultColor = texture(u_image, v_uv);
+ vec4 secondTexture = texture(u_myTexture, adjustedUV);
+ vec4 endColor = mix(defaultColor, secondTexture, .5);
+ fragColor = vec4(endColor.rgb * endColor.a, endColor.a);
+
+ //fragColor = vec4(secondTexture.rgb * secondTexture.a, secondTexture.a);
+
+}
+`;
+
+class LightingPostProcessor implements ex.PostProcessor {
+ private _shader: ex.ScreenShader | undefined;
+ gctx: ex.ExcaliburGraphicsContextWebGL;
+ texture: WebGLTexture;
+
+ constructor(public graphicsContext: ex.ExcaliburGraphicsContextWebGL) {
+ this.gctx = graphicsContext;
+ this.texture = this.graphicsContext.textureLoader.load(
+ occluder.image,
+ { wrapping: { x: ex.ImageWrapping.Repeat, y: ex.ImageWrapping.Repeat } },
+ true
+ ) as WebGLTexture;
+ console.log(occluder);
+
+ console.log(this.texture);
+ }
+
+ initialize(gl: WebGL2RenderingContext): void {
+ this._shader = new ex.ScreenShader(gl, shader);
+ }
+ getLayout(): ex.VertexLayout {
+ return this._shader.getLayout();
+ }
+ getShader(): ex.Shader {
+ return this._shader.getShader();
+ }
+ onUpdate(elapsed: number): void {
+ let myShader = this._shader?.getShader();
+
+ if (myShader) {
+ myShader.trySetUniformInt('u_myTexture', 1);
+ myShader.trySetUniformFloatVector('u_texturePosition', new ex.Vector(100.0, 100.0));
+ myShader.trySetUniformFloatVector('u_textureSize', new ex.Vector(64.0, 64.0));
+ }
+ }
+ onDraw(): void {
+ let myShader = this._shader?.getShader();
+ if (myShader) {
+ this.gctx.textureLoader.load(occluder.image);
+ myShader.setTexture(1, this.texture as WebGLTexture);
+ myShader.trySetUniformInt('u_myTexture', 1);
+ }
+ }
+}
+
+var game = new ex.Engine({
+ width: 600,
+ height: 400,
+ displayMode: ex.DisplayMode.FitScreenAndFill
+});
+
+var actor = new ex.Actor({
+ width: 100,
+ height: 100,
+ color: ex.Color.Red
+});
+actor.angularVelocity = 0.2;
+actor.actions.repeatForever((ctx) => {
+ ctx.moveTo(ex.vec(1000, 0), 200);
+ ctx.moveTo(ex.vec(0, 400), 200);
+ ctx.moveTo(ex.vec(1000, 400), 200);
+ ctx.moveTo(ex.vec(0, 0), 200);
+});
+game.currentScene.add(actor);
+
+var ctx = game.graphicsContext as ex.ExcaliburGraphicsContextWebGL;
+game.start(new ex.Loader([occluder])).then(() => {
+ ctx.addPostProcessor(new LightingPostProcessor(ctx));
+});
diff --git a/sandbox/tests/occluder/test.png b/sandbox/tests/occluder/test.png
new file mode 100644
index 000000000..9316a4135
Binary files /dev/null and b/sandbox/tests/occluder/test.png differ
diff --git a/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts b/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts
index 36c00d8e1..cbfd1d266 100644
--- a/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts
+++ b/src/engine/Graphics/Context/ExcaliburGraphicsContextWebGL.ts
@@ -776,8 +776,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext {
// post process step
if (this._postprocessors.length > 0) {
- const source = currentTarget.toRenderSource();
- source.use();
+ currentTarget.toRenderSource().use();
}
// flip flop render targets for post processing
diff --git a/src/engine/Graphics/Context/screen-pass-painter/screen-pass-painter.ts b/src/engine/Graphics/Context/screen-pass-painter/screen-pass-painter.ts
index 9b85cf54f..db25bf63e 100644
--- a/src/engine/Graphics/Context/screen-pass-painter/screen-pass-painter.ts
+++ b/src/engine/Graphics/Context/screen-pass-painter/screen-pass-painter.ts
@@ -46,8 +46,14 @@ export class ScreenPassPainter {
renderWithPostProcessor(postprocessor: PostProcessor): void {
const gl = this._gl;
- postprocessor.getShader().use();
+ const shader = postprocessor.getShader();
+ shader.use();
postprocessor.getLayout().use();
+ gl.activeTexture(gl.TEXTURE0);
+ shader.trySetUniformInt('u_image', 0);
+ if (postprocessor.onDraw) {
+ postprocessor.onDraw();
+ }
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
diff --git a/src/engine/Graphics/PostProcessor/PostProcessor.ts b/src/engine/Graphics/PostProcessor/PostProcessor.ts
index a9edda217..de48056da 100644
--- a/src/engine/Graphics/PostProcessor/PostProcessor.ts
+++ b/src/engine/Graphics/PostProcessor/PostProcessor.ts
@@ -28,4 +28,10 @@ export interface PostProcessor {
* @param elapsed
*/
onUpdate?(elapsed: number): void;
+
+ /**
+ * Use the onDraw hook to upload any textures or command that need to run right before draw
+ * @param elapsed
+ */
+ onDraw?(): void;
}