First pass of the newly re-structured Canvas Renderer (still using old texture system though).

This commit is contained in:
photonstorm 2016-10-03 12:44:54 +01:00
parent 42b8118fa0
commit 1da95994a5
13 changed files with 575 additions and 147 deletions

View file

@ -133,6 +133,10 @@ EOL;
<script src="$path/src/textures/parsers/JSONHash.js"></script>
<script src="$path/src/textures/parsers/SpriteSheet.js"></script>
<script src="$path/src/renderer/canvas/CanvasRenderer.js"></script>
<script src="$path/src/renderer/canvas/gameobjects/Container.js"></script>
<script src="$path/src/renderer/canvas/gameobjects/Sprite.js"></script>
EOL;

View file

@ -542,7 +542,13 @@ var Phaser = Phaser || { // jshint ignore:line
NEAREST: 1
},
PIXI: PIXI || {},
Renderer: {
},
PIXI: PIXI || {
},
// Used to create IDs for various Pixi objects.
_UID: 0

View file

@ -685,10 +685,12 @@ Phaser.Game.prototype = {
return;
}
var c = (this.renderType === Phaser.CANVAS) ? 'Canvas' : 'WebGL';
if (!this.device.ie)
{
var args = [
'%c %c %c %c %c Phaser v' + Phaser.VERSION + ' / Pixi.js %c http://phaser.io',
'%c %c %c %c %c Phaser v' + Phaser.VERSION + ' / Pixi.js / ' + c + ' %c http://phaser.io',
'background: #ff0000',
'background: #ffff00',
'background: #00ff00',
@ -739,7 +741,8 @@ Phaser.Game.prototype = {
// They requested Canvas and their browser supports it
this.renderType = Phaser.CANVAS;
this.renderer = new PIXI.CanvasRenderer(this);
// this.renderer = new PIXI.CanvasRenderer(this);
this.renderer = new Phaser.Renderer.Canvas(this);
this.context = this.renderer.context;
}

View file

@ -46,6 +46,11 @@ Phaser.Component.Children.prototype = {
addAt: function (child, index) {
if (this.list.length === 0)
{
return this.add(child);
}
if (index >= 0 && index <= this.list.length)
{
if (child.parent)

View file

@ -33,6 +33,8 @@ PIXI.DisplayObjectContainer = function () {
* @default
*/
this.ignoreChildInput = false;
this.render = Phaser.Renderer.Canvas.GameObjects.Container.render;
};

View file

@ -116,6 +116,8 @@ PIXI.Sprite = function (texture) {
this.renderable = true;
this.render = Phaser.Renderer.Canvas.GameObjects.Sprite.render;
};
// constructor

View file

@ -499,6 +499,8 @@ PIXI.WebGLRenderer.prototype.updateCompressedTexture = function (texture) {
};
/**
* Note: Moved to TextureManager class.
*
* Updates and Creates a WebGL texture for the renderers context.
*
* @method updateTexture

View file

@ -0,0 +1,277 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @author Mat Groves (@Doormat23)
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Camera is your view into the game world. It has a position and size and renders only those objects within its field of view.
* The game automatically creates a single Stage sized camera on boot. Move the camera around the world with Phaser.Camera.x/y
*
* @class Phaser.Camera
* @constructor
* @param {Phaser.Game} game - Game reference to the currently running game.
*/
Phaser.Renderer.Canvas = function (game)
{
console.log('CanvasRenderer Alive');
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = game;
this.type = Phaser.CANVAS;
// this.resolution = game.resolution;
/**
* This sets if the CanvasRenderer will clear the canvas or not before the new render pass.
* If the Stage is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color.
* If the Stage is transparent Pixi will use clearRect to clear the canvas every frame.
* Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set.
*
* @property clearBeforeRender
* @type Boolean
* @default
*/
this.clearBeforeRender = game.clearBeforeRender;
/**
* Whether the render view is transparent
*
* @property transparent
* @type Boolean
*/
this.transparent = game.transparent;
/**
* Whether the render view should be resized automatically
*
* @property autoResize
* @type Boolean
*/
this.autoResize = false;
/**
* The width of the canvas view
*
* @property width
* @type Number
* @default 800
*/
this.width = game.width * game.resolution;
/**
* The height of the canvas view
*
* @property height
* @type Number
* @default 600
*/
this.height = game.height * game.resolution;
/**
* The canvas element that everything is drawn to.
*
* @property view
* @type HTMLCanvasElement
*/
this.view = game.canvas;
/**
* The canvas 2d context that everything is drawn with
* @property context
* @type CanvasRenderingContext2D
*/
this.context = this.view.getContext('2d', {
alpha: this.transparent
});
this.smoothProperty = Phaser.Canvas.getSmoothingPrefix(this.context);
this.roundPixels = false;
var so = 'source-over';
this.blendModes = [ so, 'lighter', so, so, so, so, so, so, so, so, so, so, so, so, so, so, so ];
this.currentBlendMode = 0;
this.currentScaleMode = 0;
if (this.game.device.canUseMultiply)
{
this.mapBlendModes();
}
this.resize(this.width, this.height);
// this.renderTypes = [];
// this.renderTypes[Phaser.GROUP] = Phaser.Renderer.Canvas.GameObjects.Container;
// this.renderTypes[Phaser.SPRITE] = Phaser.Renderer.Canvas.GameObjects.Sprite;
};
Phaser.Renderer.Canvas.GameObjects = {
// Populated by the GameObjects classes
};
Phaser.Renderer.Canvas.prototype.constructor = Phaser.Renderer.Canvas;
Phaser.Renderer.Canvas.prototype = {
/**
* Maps Blend modes to Canvas blend modes.
*
* @method mapBlendModes
* @private
*/
mapBlendModes: function ()
{
var modes = Phaser.blendModes;
this.blendModes[modes.MULTIPLY] = 'multiply';
this.blendModes[modes.SCREEN] = 'screen';
this.blendModes[modes.OVERLAY] = 'overlay';
this.blendModes[modes.DARKEN] = 'darken';
this.blendModes[modes.LIGHTEN] = 'lighten';
this.blendModes[modes.COLOR_DODGE] = 'color-dodge';
this.blendModes[modes.COLOR_BURN] = 'color-burn';
this.blendModes[modes.HARD_LIGHT] = 'hard-light';
this.blendModes[modes.SOFT_LIGHT] = 'soft-light';
this.blendModes[modes.DIFFERENCE] = 'difference';
this.blendModes[modes.EXCLUSION] = 'exclusion';
this.blendModes[modes.HUE] = 'hue';
this.blendModes[modes.SATURATION] = 'saturation';
this.blendModes[modes.COLOR] = 'color';
this.blendModes[modes.LUMINOSITY] = 'luminosity';
},
resize: function (width, height)
{
this.width = width * this.game.resolution;
this.height = height * this.game.resolution;
this.view.width = this.width;
this.view.height = this.height;
if (this.autoResize)
{
this.view.style.width = (this.width / this.game.resolution) + 'px';
this.view.style.height = (this.height / this.game.resolution) + 'px';
}
if (this.smoothProperty)
{
this.context[this.smoothProperty] = (this.scaleMode === Phaser.scaleModes.LINEAR);
}
},
/**
* Renders the Phaser.Stage to the canvas view, then iterates through its children.
*
* @method render
* @param stage {Phaser.Stage}
*/
render: function (stage)
{
this.context.setTransform(1, 0, 0, 1, 0, 0);
this.context.globalAlpha = 1;
this.context.globalCompositeOperation = 'source-over';
this.currentBlendMode = 0;
this.currentScaleMode = 0;
// Add Pre-render hook
// this.renderSession.currentBlendMode = 0;
// this.renderSession.shakeX = this.game.camera._shake.x;
// this.renderSession.shakeY = this.game.camera._shake.y;
// Is this needed any longer?
/*
if (navigator.isCocoonJS && this.view.screencanvas)
{
this.context.fillStyle = "black";
this.context.clear();
}
*/
if (this.clearBeforeRender)
{
if (this.transparent)
{
this.context.clearRect(0, 0, this.width, this.height);
}
else if (stage._bgColor)
{
this.context.fillStyle = stage._bgColor.rgba;
this.context.fillRect(0, 0, this.width , this.height);
}
}
stage.render(this, stage);
// Add Post-render hook
},
/**
* This method adds it to the current stack of masks.
*
* @method pushMask
* @param maskData {Object} the maskData that will be pushed
* @param renderSession {Object} The renderSession whose context will be used for this mask manager.
*/
pushMask: function (maskData)
{
this.context.save();
var cacheAlpha = maskData.alpha;
var transform = maskData.worldTransform;
var resolution = this.game.resolution;
this.context.setTransform(
transform.a * resolution,
transform.b * resolution,
transform.c * resolution,
transform.d * resolution,
transform.tx * resolution,
transform.ty * resolution
);
// PIXI.CanvasGraphics.renderGraphicsMask(maskData, context);
this.context.clip();
maskData.worldAlpha = cacheAlpha;
},
popMask: function ()
{
this.context.restore();
},
/**
* Removes everything from the renderer and optionally removes the Canvas DOM element.
*
* @method destroy
* @param [removeView=true] {boolean} Removes the Canvas element from the DOM.
*/
destroy: function ()
{
// CanvasPool
this.view = null;
this.context = null;
this.maskManager = null;
}
};

View file

@ -0,0 +1,49 @@
/**
* Renders the object using the Canvas renderer
*
* @method _renderCanvas
* @param renderSession {RenderSession}
* @private
*/
Phaser.Renderer.Canvas.GameObjects.Container = {
render: function (renderer, source)
{
if (source.visible === false || source.alpha === 0)
{
return;
}
// if (this._cacheAsBitmap)
// {
// this._renderCachedSprite(renderSession);
// return;
// }
if (source._mask)
{
renderer.pushMask(source._mask);
}
for (var i = 0; i < source.children.length; i++)
{
var child = source.children[i];
child.render(renderer, child);
}
if (this._mask)
{
renderer.popMask();
}
},
renderCache: function (renderer, source)
{
// Do something
return source;
}
};

View file

@ -6,138 +6,145 @@
* @param {Matrix} [matrix] - Optional matrix. If provided the Display Object will be rendered using this matrix, otherwise it will use its worldTransform.
* @private
*/
Phaser.CanvasRenderer.GameObjects.Sprite = function (src, renderSession, matrix) {
Phaser.Renderer.Canvas.GameObjects.Sprite = {
// If the sprite is not visible or the alpha is 0 then no need to render this element
if (!src.visible || src.alpha === 0 || !src.renderable || src.texture.crop.width <= 0 || src.texture.crop.height <= 0)
render: function (renderer, source, matrix)
{
return;
}
var wt = src.worldTransform;
// If they provided an alternative rendering matrix then use it
if (matrix)
{
wt = matrix;
}
if (src.blendMode !== renderSession.currentBlendMode)
{
renderSession.currentBlendMode = src.blendMode;
renderSession.context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode];
}
if (src._mask)
{
renderSession.maskManager.pushMask(src._mask, renderSession);
}
// Ignore null sources
if (!src.texture.valid)
{
// Update the children and leave
for (var i = 0; i < src.children.length; i++)
// If the sprite is not visible or the alpha is 0 then no need to render this element
if (!source.visible || source.alpha === 0 || !source.renderable)
{
src.children[i]._renderCanvas(renderSession);
return;
}
if (src._mask)
// Add back in: || src.texture.crop.width <= 0 || src.texture.crop.height <= 0
// If they provided an alternative rendering matrix then use it
var wt = (matrix) ? matrix : source.worldTransform;
if (source.blendMode !== renderer.currentBlendMode)
{
renderSession.maskManager.popMask(renderSession);
renderer.currentBlendMode = source.blendMode;
renderer.context.globalCompositeOperation = Phaser.blendModesCanvas[renderer.currentBlendMode];
}
return;
}
// Check if the texture can be rendered
var resolution = src.texture.baseTexture.resolution / renderSession.resolution;
renderSession.context.globalAlpha = src.worldAlpha;
// If smoothingEnabled is supported and we need to change the smoothing property for src texture
if (renderSession.smoothProperty && renderSession.scaleMode !== src.texture.baseTexture.scaleMode)
{
renderSession.scaleMode = src.texture.baseTexture.scaleMode;
renderSession.context[renderSession.smoothProperty] = (renderSession.scaleMode === PIXI.scaleModes.LINEAR);
}
// If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions
var dx = (src.texture.trim) ? src.texture.trim.x - src.anchor.x * src.texture.trim.width : src.anchor.x * -src.texture.frame.width;
var dy = (src.texture.trim) ? src.texture.trim.y - src.anchor.y * src.texture.trim.height : src.anchor.y * -src.texture.frame.height;
var tx = (wt.tx * renderSession.resolution) + renderSession.shakeX;
var ty = (wt.ty * renderSession.resolution) + renderSession.shakeY;
var cw = src.texture.crop.width;
var ch = src.texture.crop.height;
if (src.texture.rotated)
{
var a = wt.a;
var b = wt.b;
var c = wt.c;
var d = wt.d;
var e = cw;
// Offset before rotating
tx = wt.c * ch + tx;
ty = wt.d * ch + ty;
// Rotate matrix by 90 degrees
// We use precalculated values for sine and cosine of rad(90)
wt.a = a * 6.123233995736766e-17 + -c;
wt.b = b * 6.123233995736766e-17 + -d;
wt.c = a + c * 6.123233995736766e-17;
wt.d = b + d * 6.123233995736766e-17;
// Update cropping dimensions.
cw = ch;
ch = e;
}
// Allow for pixel rounding
if (renderSession.roundPixels)
{
renderSession.context.setTransform(wt.a, wt.b, wt.c, wt.d, tx | 0, ty | 0);
dx |= 0;
dy |= 0;
}
else
{
renderSession.context.setTransform(wt.a, wt.b, wt.c, wt.d, tx, ty);
}
dx /= resolution;
dy /= resolution;
if (src.tint !== 0xFFFFFF)
{
if (src.texture.requiresReTint || src.cachedTint !== src.tint)
if (source._mask)
{
src.tintedTexture = PIXI.CanvasTinter.getTintedTexture(src, src.tint);
src.cachedTint = src.tint;
src.texture.requiresReTint = false;
renderer.pushMask(source._mask);
}
renderSession.context.drawImage(src.tintedTexture, 0, 0, cw, ch, dx, dy, cw / resolution, ch / resolution);
}
else
{
var cx = src.texture.crop.x;
var cy = src.texture.crop.y;
// Ignore null sources
/*
if (!src.texture.valid)
{
// Update the children and leave
for (var i = 0; i < src.children.length; i++)
{
src.children[i]._renderCanvas(renderSession);
}
renderSession.context.drawImage(src.texture.baseTexture.source, cx, cy, cw, ch, dx, dy, cw / resolution, ch / resolution);
}
if (src._mask)
{
renderSession.maskManager.popMask(renderSession);
}
for (var i = 0; i < src.children.length; i++)
{
src.children[i]._renderCanvas(renderSession);
}
return;
}
*/
var resolution = source.texture.baseTexture.resolution / renderer.game.resolution;
renderer.context.globalAlpha = source.worldAlpha;
// If smoothingEnabled is supported and we need to change the smoothing property for src texture
if (renderer.smoothProperty && renderer.currentScaleMode !== source.texture.baseTexture.scaleMode)
{
renderer.currentScaleMode = source.texture.baseTexture.scaleMode;
renderer.context[renderer.smoothProperty] = (renderer.currentScaleMode === Phaser.scaleModes.LINEAR);
}
// If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions
var dx = (source.texture.trim) ? source.texture.trim.x - source.anchor.x * source.texture.trim.width : source.anchor.x * -source.texture.frame.width;
var dy = (source.texture.trim) ? source.texture.trim.y - source.anchor.y * source.texture.trim.height : source.anchor.y * -source.texture.frame.height;
var tx = (wt.tx * renderer.game.resolution) + renderer.game.camera._shake.x;
var ty = (wt.ty * renderer.game.resolution) + renderer.game.camera._shake.y;
var cw = source.texture.crop.width;
var ch = source.texture.crop.height;
if (source.texture.rotated)
{
var a = wt.a;
var b = wt.b;
var c = wt.c;
var d = wt.d;
var e = cw;
// Offset before rotating
tx = wt.c * ch + tx;
ty = wt.d * ch + ty;
// Rotate matrix by 90 degrees
// We use precalculated values for sine and cosine of rad(90)
wt.a = a * 6.123233995736766e-17 + -c;
wt.b = b * 6.123233995736766e-17 + -d;
wt.c = a + c * 6.123233995736766e-17;
wt.d = b + d * 6.123233995736766e-17;
// Update cropping dimensions.
cw = ch;
ch = e;
}
// Allow for pixel rounding
if (renderer.roundPixels)
{
renderer.context.setTransform(wt.a, wt.b, wt.c, wt.d, tx | 0, ty | 0);
dx |= 0;
dy |= 0;
}
else
{
renderer.context.setTransform(wt.a, wt.b, wt.c, wt.d, tx, ty);
}
dx /= resolution;
dy /= resolution;
if (source.tint !== 0xFFFFFF)
{
if (source.texture.requiresReTint || source.cachedTint !== source.tint)
{
source.tintedTexture = PIXI.CanvasTinter.getTintedTexture(source, source.tint);
source.cachedTint = source.tint;
source.texture.requiresReTint = false;
}
renderer.context.drawImage(source.tintedTexture, 0, 0, cw, ch, dx, dy, cw / resolution, ch / resolution);
}
else
{
var cx = source.texture.crop.x;
var cy = source.texture.crop.y;
renderer.context.drawImage(source.texture.baseTexture.source, cx, cy, cw, ch, dx, dy, cw / resolution, ch / resolution);
}
for (var i = 0; i < source.children.length; i++)
{
var child = source.children[i];
child.render(renderer, child);
}
if (source._mask)
{
renderer.popMask();
}
if (src._mask)
{
renderSession.maskManager.popMask(renderSession);
}
};

View file

@ -100,6 +100,9 @@ Phaser.TextureFrame = function (texture, name, sourceIndex, x, y, width, height)
*/
this.requiresReTint = false;
// Over-rides the Renderer setting? 0 = use Renderer Setting, 1 = No rounding, 2 = Round
this.autoRound = 0;
/**
* The un-modified source frame, trim and UV data.
*

View file

@ -120,33 +120,6 @@ Phaser.Texture.prototype = {
}
},
/**
* Removes the base texture from the GPU, useful for managing resources on the GPU.
* A texture is still 100% usable and will simply be re-uploaded if there is a sprite on screen that is using it.
*
* @method unloadFromGPU
*/
unloadFromGPU: function ()
{
this.dirty();
// delete the webGL textures if any.
for (var i = this._glTextures.length - 1; i >= 0; i--)
{
var glTexture = this._glTextures[i];
var gl = PIXI.glContexts[i];
if (gl && glTexture)
{
gl.deleteTexture(glTexture);
}
}
this._glTextures.length = 0;
this.dirty();
},
/**
* Destroys this base texture
*

View file

@ -185,6 +185,101 @@ Phaser.TextureManager.prototype = {
callback.apply(thisArg, args);
}
},
/**
* TODO: This should move to the WebGL Renderer class.
*
* Removes the base texture from the GPU, useful for managing resources on the GPU.
* A texture is still 100% usable and will simply be re-uploaded if there is a sprite on screen that is using it.
*
* @method unloadFromGPU
*/
unloadFromGPU: function ()
{
this.dirty();
// delete the webGL textures if any.
for (var i = this._glTextures.length - 1; i >= 0; i--)
{
var glTexture = this._glTextures[i];
var gl = PIXI.glContexts[i];
if (gl && glTexture)
{
gl.deleteTexture(glTexture);
}
}
this._glTextures.length = 0;
this.dirty();
},
/**
* TODO: This should move to the WebGL Renderer class.
*
* Updates and Creates a WebGL texture for the renderers context.
*
* @method updateTexture
* @param texture {Texture} the texture to update
* @return {boolean} True if the texture was successfully bound, otherwise false.
*/
loadToGPU: function (texture)
{
if (!texture.hasLoaded)
{
return false;
}
if (texture.source.compressionAlgorithm)
{
return this.updateCompressedTexture(texture);
}
var gl = this.gl;
if (!texture._glTextures[gl.id])
{
texture._glTextures[gl.id] = gl.createTexture();
}
gl.activeTexture(gl.TEXTURE0 + texture.textureIndex);
gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultipliedAlpha);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST);
if (texture.mipmap && Phaser.Math.isPowerOfTwo(texture.width, texture.height))
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
}
else
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST);
}
if (!texture._powerOf2)
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
}
else
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
}
texture._dirty[gl.id] = false;
// return texture._glTextures[gl.id];
return true;
}
};