diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 674902e..9e16304 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -38,5 +38,10 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 674902e..9e16304 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -38,5 +38,10 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 1e682c4..a98ed47 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import bitTwiddle from 'bit-twiddle'; import Geometry from '../../geometry/Geometry'; // TODO rename this @@ -211,7 +212,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultiplyAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -226,16 +228,16 @@ // upload the sprite elemetns... // they have all ready been calculated so we just need to push them into the buffer. - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. const sprite = sprites[i]; nextTexture = sprite._texture.baseTexture; textureId = nextTexture._id; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultiplyAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -320,9 +322,12 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + const argb = alpha < 1.0 && nextTexture.premultiplyAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = textureId; /* eslint-enable max-len */ diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 674902e..9e16304 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -38,5 +38,10 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 1e682c4..a98ed47 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import bitTwiddle from 'bit-twiddle'; import Geometry from '../../geometry/Geometry'; // TODO rename this @@ -211,7 +212,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultiplyAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -226,16 +228,16 @@ // upload the sprite elemetns... // they have all ready been calculated so we just need to push them into the buffer. - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. const sprite = sprites[i]; nextTexture = sprite._texture.baseTexture; textureId = nextTexture._id; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultiplyAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -320,9 +322,12 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + const argb = alpha < 1.0 && nextTexture.premultiplyAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = textureId; /* eslint-enable max-len */ diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 9951fa1..4591863 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -52,6 +52,8 @@ { super(null, scaleMode, resolution, width, height); + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.hasLoaded = true; /** @@ -100,6 +102,8 @@ */ resize(width, height) { + this.width = Math.ceil(width); + this.height = Math.ceil(height); super.resize(width, height); this.frameBuffer.resize(width, height); } diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 674902e..9e16304 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -38,5 +38,10 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 1e682c4..a98ed47 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import bitTwiddle from 'bit-twiddle'; import Geometry from '../../geometry/Geometry'; // TODO rename this @@ -211,7 +212,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultiplyAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -226,16 +228,16 @@ // upload the sprite elemetns... // they have all ready been calculated so we just need to push them into the buffer. - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. const sprite = sprites[i]; nextTexture = sprite._texture.baseTexture; textureId = nextTexture._id; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultiplyAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -320,9 +322,12 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + const argb = alpha < 1.0 && nextTexture.premultiplyAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = textureId; /* eslint-enable max-len */ diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 9951fa1..4591863 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -52,6 +52,8 @@ { super(null, scaleMode, resolution, width, height); + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.hasLoaded = true; /** @@ -100,6 +102,8 @@ */ resize(width, height) { + this.width = Math.ceil(width); + this.height = Math.ceil(height); super.resize(width, height); this.frameBuffer.resize(width, height); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 3a65e61..e6fb514 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -81,6 +81,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -154,8 +155,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -251,9 +254,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -430,6 +431,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ @@ -458,8 +460,8 @@ + `${errorX} ${relationship} ${errorY}`); } - // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.valid; + this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = width && height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 674902e..9e16304 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -38,5 +38,10 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 1e682c4..a98ed47 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import bitTwiddle from 'bit-twiddle'; import Geometry from '../../geometry/Geometry'; // TODO rename this @@ -211,7 +212,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultiplyAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -226,16 +228,16 @@ // upload the sprite elemetns... // they have all ready been calculated so we just need to push them into the buffer. - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. const sprite = sprites[i]; nextTexture = sprite._texture.baseTexture; textureId = nextTexture._id; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultiplyAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -320,9 +322,12 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + const argb = alpha < 1.0 && nextTexture.premultiplyAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = textureId; /* eslint-enable max-len */ diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 9951fa1..4591863 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -52,6 +52,8 @@ { super(null, scaleMode, resolution, width, height); + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.hasLoaded = true; /** @@ -100,6 +102,8 @@ */ resize(width, height) { + this.width = Math.ceil(width); + this.height = Math.ceil(height); super.resize(width, height); this.frameBuffer.resize(width, height); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 3a65e61..e6fb514 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -81,6 +81,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -154,8 +155,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -251,9 +254,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -430,6 +431,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ @@ -458,8 +460,8 @@ + `${errorX} ${relationship} ${errorY}`); } - // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.valid; + this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = width && height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 674902e..9e16304 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -38,5 +38,10 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 1e682c4..a98ed47 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import bitTwiddle from 'bit-twiddle'; import Geometry from '../../geometry/Geometry'; // TODO rename this @@ -211,7 +212,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultiplyAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -226,16 +228,16 @@ // upload the sprite elemetns... // they have all ready been calculated so we just need to push them into the buffer. - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. const sprite = sprites[i]; nextTexture = sprite._texture.baseTexture; textureId = nextTexture._id; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultiplyAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -320,9 +322,12 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + const argb = alpha < 1.0 && nextTexture.premultiplyAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = textureId; /* eslint-enable max-len */ diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 9951fa1..4591863 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -52,6 +52,8 @@ { super(null, scaleMode, resolution, width, height); + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.hasLoaded = true; /** @@ -100,6 +102,8 @@ */ resize(width, height) { + this.width = Math.ceil(width); + this.height = Math.ceil(height); super.resize(width, height); this.frameBuffer.resize(width, height); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 3a65e61..e6fb514 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -81,6 +81,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -154,8 +155,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -251,9 +254,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -430,6 +431,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ @@ -458,8 +460,8 @@ + `${errorX} ${relationship} ${errorY}`); } - // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.valid; + this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = width && height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 674902e..9e16304 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -38,5 +38,10 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 1e682c4..a98ed47 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import bitTwiddle from 'bit-twiddle'; import Geometry from '../../geometry/Geometry'; // TODO rename this @@ -211,7 +212,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultiplyAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -226,16 +228,16 @@ // upload the sprite elemetns... // they have all ready been calculated so we just need to push them into the buffer. - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. const sprite = sprites[i]; nextTexture = sprite._texture.baseTexture; textureId = nextTexture._id; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultiplyAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -320,9 +322,12 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + const argb = alpha < 1.0 && nextTexture.premultiplyAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = textureId; /* eslint-enable max-len */ diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 9951fa1..4591863 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -52,6 +52,8 @@ { super(null, scaleMode, resolution, width, height); + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.hasLoaded = true; /** @@ -100,6 +102,8 @@ */ resize(width, height) { + this.width = Math.ceil(width); + this.height = Math.ceil(height); super.resize(width, height); this.frameBuffer.resize(width, height); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 3a65e61..e6fb514 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -81,6 +81,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -154,8 +155,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -251,9 +254,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -430,6 +431,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ @@ -458,8 +460,8 @@ + `${errorX} ${relationship} ${errorY}`); } - // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.valid; + this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = width && height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index edf0942..1d6288e 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 674902e..9e16304 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -38,5 +38,10 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 1e682c4..a98ed47 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import bitTwiddle from 'bit-twiddle'; import Geometry from '../../geometry/Geometry'; // TODO rename this @@ -211,7 +212,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultiplyAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -226,16 +228,16 @@ // upload the sprite elemetns... // they have all ready been calculated so we just need to push them into the buffer. - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. const sprite = sprites[i]; nextTexture = sprite._texture.baseTexture; textureId = nextTexture._id; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultiplyAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -320,9 +322,12 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + const argb = alpha < 1.0 && nextTexture.premultiplyAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = textureId; /* eslint-enable max-len */ diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 9951fa1..4591863 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -52,6 +52,8 @@ { super(null, scaleMode, resolution, width, height); + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.hasLoaded = true; /** @@ -100,6 +102,8 @@ */ resize(width, height) { + this.width = Math.ceil(width); + this.height = Math.ceil(height); super.resize(width, height); this.frameBuffer.resize(width, height); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 3a65e61..e6fb514 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -81,6 +81,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -154,8 +155,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -251,9 +254,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -430,6 +431,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ @@ -458,8 +460,8 @@ + `${errorX} ${relationship} ${errorY}`); } - // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.valid; + this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = width && height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index edf0942..1d6288e 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 674902e..9e16304 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -38,5 +38,10 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 1e682c4..a98ed47 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import bitTwiddle from 'bit-twiddle'; import Geometry from '../../geometry/Geometry'; // TODO rename this @@ -211,7 +212,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultiplyAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -226,16 +228,16 @@ // upload the sprite elemetns... // they have all ready been calculated so we just need to push them into the buffer. - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. const sprite = sprites[i]; nextTexture = sprite._texture.baseTexture; textureId = nextTexture._id; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultiplyAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -320,9 +322,12 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + const argb = alpha < 1.0 && nextTexture.premultiplyAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = textureId; /* eslint-enable max-len */ diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 9951fa1..4591863 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -52,6 +52,8 @@ { super(null, scaleMode, resolution, width, height); + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.hasLoaded = true; /** @@ -100,6 +102,8 @@ */ resize(width, height) { + this.width = Math.ceil(width); + this.height = Math.ceil(height); super.resize(width, height); this.frameBuffer.resize(width, height); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 3a65e61..e6fb514 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -81,6 +81,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -154,8 +155,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -251,9 +254,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -430,6 +431,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ @@ -458,8 +460,8 @@ + `${errorX} ${relationship} ${errorY}`); } - // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.valid; + this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = width && height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index edf0942..1d6288e 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index a3ca0b2..fc2f8f2 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -4,7 +4,6 @@ import { join } from 'path'; const tempMat = new core.Matrix(); -const tempArray = new Float32Array(4); /** * WebGL renderer plugin for tiling sprites @@ -124,19 +123,15 @@ } shader.uniforms.uTransform = tempMat.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(ts.tint, color); - color[3] = ts.worldAlpha; - shader.uniforms.uColor = color; + shader.uniforms.uColor = core.utils.premultiplyTintToRgba(ts.tint, ts.worldAlpha, + shader.uniforms.uColor, baseTex.premultiplyAlpha); shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); shader.uniforms.uSampler = tex; renderer.shader.bind(shader); renderer.geometry.bind(quad);// , renderer.shader.getGLShader()); - renderer.state.setBlendMode(ts.blendMode); + renderer.setBlendMode(core.utils.correctBlendMode(ts.blendMode, baseTex.premultiplyAlpha)); renderer.geometry.draw(this.renderer.gl.TRIANGLES, 6, 0); } } diff --git a/src/core/const.js b/src/core/const.js index 7e4e787..c47dcfa 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,7 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, - NONE: 17, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** @@ -213,7 +215,7 @@ /** * The gc modes that are supported by pixi. * - * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for pixi textures is AUTO + * The {@link PIXI.settings.GC_MODE} Garbage Collection mode for PixiJS textures is AUTO * If set to GC_MODE, the renderer will occasionally check textures usage. If they are not * used for a specified period of time they will be removed from the GPU. They will of course * be uploaded again when they are required. This is a silent behind the scenes process that diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 37003b1..9c14b74 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -33,13 +33,13 @@ '}', ].join('\n'), // fragment shader - [ - 'varying vec4 vColor;', + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}', - ].join('\n')); + 'void main(void){', + ' gl_FragColor = vColor;', + '}', + ].join('\n')); super(program, {}); } diff --git a/src/core/index.js b/src/core/index.js index 1ff5bd5..aab01d9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -38,6 +38,7 @@ export { default as ImageResource } from './textures/resources/ImageResource'; export { default as ArrayTexture } from './textures/ArrayTexture'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; export { default as VideoBaseTexture } from './textures/VideoBaseTexture'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 674902e..9e16304 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -38,5 +38,10 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 1e682c4..a98ed47 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import bitTwiddle from 'bit-twiddle'; import Geometry from '../../geometry/Geometry'; // TODO rename this @@ -211,7 +212,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultiplyAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -226,16 +228,16 @@ // upload the sprite elemetns... // they have all ready been calculated so we just need to push them into the buffer. - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. const sprite = sprites[i]; nextTexture = sprite._texture.baseTexture; textureId = nextTexture._id; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultiplyAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -320,9 +322,12 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + const argb = alpha < 1.0 && nextTexture.premultiplyAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = textureId; /* eslint-enable max-len */ diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 9951fa1..4591863 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -52,6 +52,8 @@ { super(null, scaleMode, resolution, width, height); + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.hasLoaded = true; /** @@ -100,6 +102,8 @@ */ resize(width, height) { + this.width = Math.ceil(width); + this.height = Math.ceil(height); super.resize(width, height); this.frameBuffer.resize(width, height); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 3a65e61..e6fb514 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -81,6 +81,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -154,8 +155,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -251,9 +254,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -430,6 +431,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ @@ -458,8 +460,8 @@ + `${errorX} ${relationship} ${errorY}`); } - // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.valid; + this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = width && height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index edf0942..1d6288e 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index a3ca0b2..fc2f8f2 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -4,7 +4,6 @@ import { join } from 'path'; const tempMat = new core.Matrix(); -const tempArray = new Float32Array(4); /** * WebGL renderer plugin for tiling sprites @@ -124,19 +123,15 @@ } shader.uniforms.uTransform = tempMat.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(ts.tint, color); - color[3] = ts.worldAlpha; - shader.uniforms.uColor = color; + shader.uniforms.uColor = core.utils.premultiplyTintToRgba(ts.tint, ts.worldAlpha, + shader.uniforms.uColor, baseTex.premultiplyAlpha); shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); shader.uniforms.uSampler = tex; renderer.shader.bind(shader); renderer.geometry.bind(quad);// , renderer.shader.getGLShader()); - renderer.state.setBlendMode(ts.blendMode); + renderer.setBlendMode(core.utils.correctBlendMode(ts.blendMode, baseTex.premultiplyAlpha)); renderer.geometry.draw(this.renderer.gl.TRIANGLES, 6, 0); } } diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index d524f61..88c5b57 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -147,7 +147,7 @@ const baseTexture = children[0]._texture.baseTexture; // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(core.utils.correctBlendMode(container.blendMode, baseTexture.premultipliedAlpha)); + this.renderer.setBlendMode(core.utils.correctBlendMode(container.blendMode, baseTexture.premultiplyAlpha)); const gl = renderer.gl; @@ -158,7 +158,7 @@ this.shader.uniforms.projectionMatrix = m.toArray(true); this.shader.uniforms.uColor = core.utils.premultiplyRgba(container.tintRgb, - container.worldAlpha, this.shader.uniforms.uColor, baseTexture.premultipliedAlpha); + container.worldAlpha, this.shader.uniforms.uColor, baseTexture.premultiplyAlpha); // make sure the texture is bound.. this.shader.uniforms.uSampler = renderer.bindTexture(baseTexture); @@ -419,7 +419,7 @@ for (let i = 0; i < amount; ++i) { const sprite = children[startIndex + i]; - const premultiplied = sprite._texture.baseTexture.premultipliedAlpha; + const premultiplied = sprite._texture.baseTexture.premultiplyAlpha; const alpha = sprite.alpha; // we dont call extra function if alpha is 1.0, that's faster const argb = alpha < 1.0 && premultiplied ? premultiplyTint(sprite._tintRGB, alpha)