diff --git a/CHANGELOG.md b/CHANGELOG.md index bf2fa8ab..2cf31de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ const hits = engine.currentScene.physics.rayCast( ### Fixed +- Fixed issue where rendering multiple materials at once would crash the renderer - Fixed issue where raycasting with more complex collision groups was not working as expected ### Updates diff --git a/sandbox/tests/material/heart.png b/sandbox/tests/material/heart.png new file mode 100644 index 0000000000000000000000000000000000000000..2c830cdc201ec590ddd90c87a58b5f30b842362e GIT binary patch literal 242 zcmeAS@N?(olHy`uVBq!ia0y~yU=U!lvNA9*NCfzVxVCU{HFI*d@$+|yi`O$S{O3{m|NlP&1H=2`Mei6G z7&uCT{DS{O0K>iJy^#zI44efXk;M!Qe1}1p@p%4<6b1$ce@_?35RKs8bJ2VU6gXT0 zqgTGEKhAe}LG7O}YHUS9i_hL~-SYU2L|f1sr!eM2yw>L=7O1|_E_^%RrX&BZ=l`1` o6O@!ZJUCSq4hretSae4{*n~|@m2IXT0|Nttr>mdKI;Vst09`Iop#T5? literal 0 HcmV?d00001 diff --git a/sandbox/tests/material/index.ts b/sandbox/tests/material/index.ts index fb459afc..e565789f 100644 --- a/sandbox/tests/material/index.ts +++ b/sandbox/tests/material/index.ts @@ -1,5 +1,8 @@ /// +// identity tagged template literal lights up glsl-literal vscode plugin +var glsl = x => x; + var game = new ex.Engine({ canvasElementId: 'game', width: 512, @@ -10,11 +13,54 @@ var game = new ex.Engine({ }); var tex = new ex.ImageSource('https://cdn.rawgit.com/excaliburjs/Excalibur/7dd48128/assets/sword.png', false, ex.ImageFiltering.Pixel); +var heartImage = new ex.ImageSource('./heart.png', false, ex.ImageFiltering.Pixel); var background = new ex.ImageSource('./stars.png', false, ex.ImageFiltering.Blended); -var loader = new ex.Loader([tex, background]); +var loader = new ex.Loader([tex, heartImage, background]); + +var outline = glsl`#version 300 es +precision mediump float; + +uniform float iTime; + +uniform sampler2D u_graphic; + +in vec2 v_uv; + +out vec4 fragColor; + +vec3 hsv2rgb(vec3 c){ + vec4 K=vec4(1.,2./3.,1./3.,3.); + return c.z*mix(K.xxx,clamp(abs(fract(c.x+K.xyz)*6.-K.w)-K.x, 0., 1.),c.y); +} + +void main() { + const float TAU = 6.28318530; + const float steps = 4.0; // up/down/left/right pixels + + float radius = 2.0; + vec3 outlineColorHSL = vec3(sin(iTime/2.0) * 1., 1., 1.); + + vec2 aspect = 1.0 / vec2(textureSize(u_graphic, 0)); + + for (float i = 0.0; i < TAU; i += TAU / steps) { + // Sample image in a circular pattern + vec2 offset = vec2(sin(i), cos(i)) * aspect * radius; + vec4 col = texture(u_graphic, v_uv + offset); + + // Mix outline with background + float alpha = smoothstep(0.5, 0.7, col.a); + fragColor = mix(fragColor, vec4(hsv2rgb(outlineColorHSL), 1.0), alpha); // apply outline + } + + // Overlay original texture + vec4 mat = texture(u_graphic, v_uv); + float factor = smoothstep(0.5, 0.7, mat.a); + fragColor = mix(fragColor, mat, factor); +} +` -var fragmentSource = `#version 300 es +var fragmentSource = glsl`#version 300 es precision mediump float; // UV coord @@ -65,6 +111,11 @@ var swirlMaterial = game.graphicsContext.createMaterial({ fragmentSource }); +var outlineMaterial = game.graphicsContext.createMaterial({ + name: 'outline', + fragmentSource: outline +}) + var click = ex.vec(0, 0); game.input.pointers.primary.on('down', evt => { @@ -81,16 +132,39 @@ actor.onInitialize = () => { } }); actor.graphics.add(sprite); + actor.graphics.material = outlineMaterial; }; +actor.onPostUpdate = (_, delta) => { + time += (delta / 1000); + outlineMaterial.getShader().use(); + outlineMaterial.getShader().trySetUniformFloat('iTime', time); +} + +var heartActor = new ex.Actor({x: 200, y: 200}); +heartActor.onInitialize = () => { + var sprite = heartImage.toSprite(); + sprite.scale = ex.vec(4, 4); + heartActor.graphics.add(sprite); + heartActor.graphics.material = outlineMaterial; +} + +game.add(heartActor); + +game.input.pointers.primary.on('move', evt => { + heartActor.pos = evt.worldPos; + swirlMaterial.getShader().use(); + swirlMaterial.getShader().trySetUniformFloatVector('iMouse', evt.worldPos); +}); + var backgroundActor = new ex.ScreenElement({x: 0, y: 0, width: 512, height: 512, z: -1}); var time = 0; backgroundActor.onPostUpdate = (_, delta) => { time += (delta / 1000); - swirlMaterial.getShader().use(); - swirlMaterial.getShader().trySetUniformFloat('iTime', time); - swirlMaterial.getShader().trySetUniformFloatVector('iMouse', click); + // swirlMaterial.getShader().use(); + // swirlMaterial.getShader().trySetUniformFloat('iTime', time); + // swirlMaterial.getShader().trySetUniformFloatVector('iMouse', click); } backgroundActor.onInitialize = () => { backgroundActor.graphics.add(background.toSprite()); diff --git a/src/engine/Graphics/Context/material-renderer/material-renderer.ts b/src/engine/Graphics/Context/material-renderer/material-renderer.ts index 75873462..93fb4de6 100644 --- a/src/engine/Graphics/Context/material-renderer/material-renderer.ts +++ b/src/engine/Graphics/Context/material-renderer/material-renderer.ts @@ -1,5 +1,6 @@ import { vec } from '../../../Math/vector'; import { ImageFiltering } from '../../Filtering'; +import { GraphicsDiagnostics } from '../../GraphicsDiagnostics'; import { HTMLImageSource } from '../ExcaliburGraphicsContext'; import { ExcaliburGraphicsContextWebGL } from '../ExcaliburGraphicsContextWebGL'; import { QuadIndexBuffer } from '../quad-index-buffer'; @@ -140,6 +141,9 @@ export class MaterialRenderer implements RendererPlugin { // apply resolution shader.trySetUniformFloatVector('u_resolution', vec(this._context.width, this._context.height)); + // apply graphic resolution + shader.trySetUniformFloatVector('u_graphic_resolution', vec(imageWidth, imageHeight)); + // apply size shader.trySetUniformFloatVector('u_size', vec(sw, sh)); @@ -159,6 +163,9 @@ export class MaterialRenderer implements RendererPlugin { // Draw a single quad gl.drawElements(gl.TRIANGLES, 6, this._quads.bufferGlType, 0); + + GraphicsDiagnostics.DrawnImagesCount++; + GraphicsDiagnostics.DrawCallCount++; } private _addImageAsTexture(image: HTMLImageSource) { diff --git a/src/engine/Graphics/Context/shader.ts b/src/engine/Graphics/Context/shader.ts index ac180ff7..c0715589 100644 --- a/src/engine/Graphics/Context/shader.ts +++ b/src/engine/Graphics/Context/shader.ts @@ -461,6 +461,9 @@ export class Shader { } private _processSourceForError(source: string, errorInfo: string) { + if (!source) { + return errorInfo; + } const lines = source.split('\n'); const errorLineStart = errorInfo.search(/\d:\d/); const errorLineEnd = errorInfo.indexOf(' ', errorLineStart); diff --git a/src/engine/Graphics/Context/vertex-layout.ts b/src/engine/Graphics/Context/vertex-layout.ts index bd39193a..9a070d7d 100644 --- a/src/engine/Graphics/Context/vertex-layout.ts +++ b/src/engine/Graphics/Context/vertex-layout.ts @@ -89,6 +89,7 @@ export class VertexLayout { if (!this._shader.compiled) { throw Error('Shader not compiled, shader must be compiled before defining a vertex layout'); } + this._vertexTotalSizeBytes = 0; this._layout.length = 0; const shaderAttributes = this._shader.attributes; for (const attribute of this._attributes) { diff --git a/src/spec/MaterialRendererSpec.ts b/src/spec/MaterialRendererSpec.ts index 6e62c031..610b90d9 100644 --- a/src/spec/MaterialRendererSpec.ts +++ b/src/spec/MaterialRendererSpec.ts @@ -129,4 +129,72 @@ describe('A Material', () => { await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) .toEqualImage('src/spec/images/MaterialRendererSpec/material-component.png'); }); + + it('can be draw multiple materials', async () => { + const material1 = new ex.Material({ + name: 'material1', + color: ex.Color.Red, + fragmentSource: `#version 300 es + precision mediump float; + uniform vec4 u_color; + out vec4 fragColor; + void main() { + fragColor = u_color; + }` + }); + + const material2 = new ex.Material({ + name: 'material2', + color: ex.Color.Blue, + fragmentSource: `#version 300 es + precision mediump float; + uniform vec4 u_color; + out vec4 fragColor; + void main() { + fragColor = u_color; + }` + }); + + const engine = TestUtils.engine({ + width: 100, + height: 100, + antialiasing: false, + snapToPixel: true + }); + const context = engine.graphicsContext as ex.ExcaliburGraphicsContextWebGL; + + const tex = new ex.ImageSource('src/spec/images/MaterialRendererSpec/sword.png'); + + const loader = new ex.Loader([tex]); + + await TestUtils.runToReady(engine, loader); + + const actor1 = new ex.Actor({ + x: 0, + y: 0, + width: 100, + height: 100 + }); + actor1.graphics.use(tex.toSprite()); + actor1.graphics.material = material1; + + const actor2 = new ex.Actor({ + x: 100, + y: 100, + width: 100, + height: 100 + }); + actor2.graphics.use(tex.toSprite()); + actor2.graphics.material = material2; + + context.clear(); + engine.currentScene.add(actor1); + engine.currentScene.add(actor2); + engine.currentScene.draw(context, 100); + context.flush(); + + expect(context.material).toBe(null); + await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas)) + .toEqualImage('src/spec/images/MaterialRendererSpec/multi-mat.png'); + }); }); \ No newline at end of file diff --git a/src/spec/images/MaterialRendererSpec/multi-mat.png b/src/spec/images/MaterialRendererSpec/multi-mat.png new file mode 100644 index 0000000000000000000000000000000000000000..a431268fd1fd6a38089ff5173a71294b11be43c8 GIT binary patch literal 628 zcmeAS@N?(olHy`uVBq!ia0y~yU`PRB4mJh`hJr^^Ll_ts7>k44ofy`glX=O&z@+Et z;uum9_x8@lUZy|+mWy`-6dJh&X8$?S5U@ieO1G3Vtkt&O?TYaS3;W|AE%iR_w2!y{ z@9XEd?{vU~_n$0S1XIEf+@BD8J8QE-=(fAcj%B&4&o+hJEbC{vxpl1>cTnnWTcMQe z+ivN2OwIl7G4vJgVma~l=dLsHfA-4PTjyqc*5nAWvQhSs=#mm{(UQF&c5+oEGE3%%OQSvZ{olbZsT9=2|3S-8T`amA&FvW|xWuFOyfdHJDFAyFgCM_^S+ ph5G~p5mPmeP^&$I*0^u{($OV}toEnH85kHCJYD@<);T3K0RSif1`7ZH literal 0 HcmV?d00001