phaser/v3/src/renderer/webgl/WebGLRenderer.js

539 lines
16 KiB
JavaScript
Raw Normal View History

2016-12-07 02:28:22 +00:00
/**
* @author Richard Davey (@photonstorm)
* @author Felipe Alfonso (@bitnenfer)
* @copyright 2017 Photon Storm Ltd.
2016-12-07 02:28:22 +00:00
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
2016-12-07 03:42:41 +00:00
var CONST = require('../../const');
2017-04-06 17:40:43 +00:00
var BlitterBatch = require('./renderers/blitterbatch/BlitterBatch');
var QuadBatch = require('./renderers/quadbatch/QuadBatch');
var SpriteBatch = require('./renderers/spritebatch/SpriteBatch');
2017-04-25 22:09:13 +00:00
var TileBatch = require('./renderers/tilebatch/TileBatch');
2017-04-06 17:40:43 +00:00
var ShapeBatch = require('./renderers/shapebatch/ShapeBatch');
2017-04-07 01:49:15 +00:00
var EffectRenderer = require('./renderers/effectrenderer/EffectRenderer');
2017-06-01 21:05:50 +00:00
var TilemapRenderer = require('./renderers/tilemaprenderer/TilemapRenderer');
2017-01-23 21:42:47 +00:00
var BlendModes = require('../BlendModes');
var ScaleModes = require('../ScaleModes');
var ResourceManager = require('./ResourceManager');
2017-04-06 17:40:43 +00:00
var Resources = require('./resources');
var WebGLSnapshot = require('../snapshot/WebGLSnapshot');
2016-12-07 02:28:22 +00:00
var WebGLRenderer = function (game)
{
this.game = game;
2016-12-07 03:42:41 +00:00
this.type = CONST.WEBGL;
this.width = game.config.width * game.config.resolution;
this.height = game.config.height * game.config.resolution;
this.resolution = game.config.resolution;
2016-12-07 02:28:22 +00:00
this.view = game.canvas;
// All of these settings will be able to be controlled via the Game Config
this.config = {
clearBeforeRender: true,
transparent: false,
autoResize: false,
preserveDrawingBuffer: false,
2016-12-07 02:28:22 +00:00
WebGLContextOptions: {
alpha: true,
antialias: true,
premultipliedAlpha: true,
stencil: true,
preserveDrawingBuffer: false
}
2016-12-07 02:28:22 +00:00
};
this.contextLost = false;
this.maxTextures = 1;
this.multiTexture = false;
this.blendModes = [];
this.gl = null;
this.extensions = null;
this.rendererArray = [];
this.blitterBatch = null;
this.aaQuadBatch = null;
this.spriteBatch = null;
2017-02-27 20:41:52 +00:00
this.shapeBatch = null;
2017-04-07 01:49:15 +00:00
this.effectRenderer = null;
2017-04-06 17:40:43 +00:00
this.currentRenderer = null;
this.currentTexture = null;
this.shaderCache = {};
this.currentShader = null;
this.resourceManager = null;
2017-04-07 01:49:15 +00:00
this.currentRenderTarget = null;
2017-05-16 19:15:01 +00:00
this.snapshotCallback = null;
this.scissor = {
enabled: false,
x: 0,
y: 0,
width: 0,
height: 0
};
this.init();
2016-12-07 02:28:22 +00:00
};
WebGLRenderer.prototype.constructor = WebGLRenderer;
WebGLRenderer.prototype = {
init: function ()
{
// console.log('WebGLRenderer.init');
this.gl = this.view.getContext('webgl', this.config.WebGLContextOptions) || this.view.getContext('experimental-webgl', this.config.WebGLContextOptions);
2016-12-07 02:28:22 +00:00
if (!this.gl)
{
this.contextLost = true;
throw new Error('This browser does not support WebGL. Try using the Canvas renderer.');
}
var gl = this.gl;
var color = this.game.config.backgroundColor;
this.resourceManager = new ResourceManager(gl);
2016-12-07 02:28:22 +00:00
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE);
gl.enable(gl.BLEND);
2017-02-11 20:25:12 +00:00
gl.clearColor(color.redGL, color.greenGL, color.blueGL, color.alphaGL);
2016-12-07 02:28:22 +00:00
// Map Blend Modes
var add = [ gl.SRC_ALPHA, gl.DST_ALPHA ];
2017-01-24 15:21:49 +00:00
var normal = [ gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ];
2016-12-07 02:28:22 +00:00
var multiply = [ gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA ];
var screen = [ gl.SRC_ALPHA, gl.ONE ];
this.blendModes = [
normal, add, multiply, screen, normal,
normal, normal, normal, normal,
normal, normal, normal, normal,
normal, normal, normal, normal
];
2017-01-23 21:42:47 +00:00
this.blendMode = -1;
this.extensions = gl.getSupportedExtensions();
2017-04-06 17:40:43 +00:00
this.blitterBatch = this.addRenderer(new BlitterBatch(this.game, gl, this));
this.quadBatch = this.addRenderer(new QuadBatch(this.game, gl, this));
this.spriteBatch = this.addRenderer(new SpriteBatch(this.game, gl, this));
this.shapeBatch = this.addRenderer(new ShapeBatch(this.game, gl, this));
2017-04-07 01:49:15 +00:00
this.effectRenderer = this.addRenderer(new EffectRenderer(this.game, gl, this));
2017-04-25 22:09:13 +00:00
this.tileBatch = this.addRenderer(new TileBatch(this.game, gl, this));
2017-06-01 21:05:50 +00:00
this.tilemapRenderer = this.addRenderer(new TilemapRenderer(this.game, gl, this));
this.currentRenderer = this.spriteBatch;
2017-04-20 18:06:57 +00:00
this.setBlendMode(0);
this.resize(this.width, this.height);
2016-12-07 02:28:22 +00:00
},
2017-05-11 00:24:57 +00:00
createTexture: function (source, width, height)
2017-01-19 23:20:36 +00:00
{
width = source ? source.width : width;
height = source ? source.height : height;
2017-05-20 01:16:45 +00:00
var pot = ((width & (width - 1)) == 0 && (height & (height - 1)) == 0);
2017-01-19 23:20:36 +00:00
var gl = this.gl;
var filter = gl.NEAREST;
2017-05-20 01:16:45 +00:00
var wrap = pot ? gl.REPEAT : gl.CLAMP_TO_EDGE;
2017-01-19 23:20:36 +00:00
if (!source.glTexture)
{
if (source.scaleMode === ScaleModes.LINEAR)
{
filter = gl.LINEAR;
}
else if (source.scaleMode === ScaleModes.NEAREST || this.game.config.pixelArt)
{
filter = gl.NEAREST;
}
if (!source && typeof width === 'number' && typeof height === 'number')
2017-05-11 00:24:57 +00:00
{
source.glTexture = this.resourceManager.createTexture(
0,
filter,
filter,
2017-05-20 01:16:45 +00:00
wrap,
wrap,
2017-05-11 00:24:57 +00:00
gl.RGBA,
null,
width, height
);
}
else
{
source.glTexture = this.resourceManager.createTexture(
0,
filter,
filter,
2017-05-20 01:16:45 +00:00
wrap,
wrap,
gl.RGBA,
source.image
);
2017-05-11 00:24:57 +00:00
}
2017-01-19 23:20:36 +00:00
}
2017-04-07 01:49:15 +00:00
this.currentTexture = null;
2017-01-19 23:20:36 +00:00
},
setTexture: function (texture)
2017-01-19 17:53:20 +00:00
{
2017-04-06 17:40:43 +00:00
if (this.currentTexture !== texture)
{
2017-01-20 18:53:53 +00:00
var gl = this.gl;
this.currentRenderer.flush();
gl.activeTexture(gl.TEXTURE0);
if (texture !== null)
{
gl.bindTexture(gl.TEXTURE_2D, texture.texture);
}
else
{
gl.bindTexture(gl.TEXTURE_2D, null);
}
2017-01-20 18:53:53 +00:00
2017-04-06 17:40:43 +00:00
this.currentTexture = texture;
}
2017-01-19 17:53:20 +00:00
},
setRenderer: function (renderer, texture, renderTarget)
2017-01-19 22:43:41 +00:00
{
this.setTexture(texture);
2017-04-07 01:49:15 +00:00
this.setRenderTarget(renderTarget);
if (this.currentRenderer !== renderer || this.currentRenderer.shouldFlush())
2017-04-07 01:49:15 +00:00
{
this.currentRenderer.flush();
2017-04-07 01:49:15 +00:00
this.currentRenderer = renderer;
}
},
setRenderTarget: function (renderTarget)
{
var gl = this.gl;
if (this.currentRenderTarget !== renderTarget)
2017-01-19 22:43:41 +00:00
{
this.currentRenderer.flush();
2017-01-20 18:53:53 +00:00
2017-04-07 01:49:15 +00:00
if (renderTarget !== null)
{
gl.bindFramebuffer(gl.FRAMEBUFFER, renderTarget.framebufferObject);
if (renderTarget.shouldClear)
{
2017-06-28 09:22:48 +00:00
gl.clearColor(0, 0, 0, renderTarget.clearAlpha);
2017-04-07 01:49:15 +00:00
gl.clear(gl.COLOR_BUFFER_BIT);
renderTarget.shouldClear = false;
}
}
else
{
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, this.width, this.height);
}
this.currentRenderTarget = renderTarget;
2017-01-19 22:43:41 +00:00
}
},
2016-12-07 02:28:22 +00:00
resize: function (width, height)
{
var resolution = this.game.config.resolution;
2016-12-07 03:42:41 +00:00
this.width = width * resolution;
this.height = height * resolution;
2016-12-07 02:28:22 +00:00
this.view.width = this.width;
this.view.height = this.height;
if (this.autoResize)
{
this.view.style.width = (this.width / resolution) + 'px';
this.view.style.height = (this.height / resolution) + 'px';
2016-12-07 02:28:22 +00:00
}
this.gl.viewport(0, 0, this.width, this.height);
for (var i = 0, l = this.rendererArray.length; i < l; ++i)
{
this.rendererArray[i].bind();
this.rendererArray[i].resize(width, height, resolution);
}
this.currentRenderer.bind();
2016-12-07 02:28:22 +00:00
},
// Call at the start of the render loop
preRender: function ()
2016-12-07 02:28:22 +00:00
{
2017-04-07 05:06:55 +00:00
this.setRenderTarget(null);
2016-12-07 02:28:22 +00:00
// No point rendering if our context has been blown up!
if (this.contextLost)
{
return;
}
// Add Pre-render hook
var gl = this.gl;
2017-02-11 20:25:12 +00:00
var color = this.game.config.backgroundColor;
gl.clearColor(color.redGL, color.greenGL, color.blueGL, color.alphaGL);
// Some drivers require to call glClear
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
2016-12-07 02:28:22 +00:00
2017-01-23 21:42:47 +00:00
this.setBlendMode(BlendModes.NORMAL);
},
/**
* Renders a single State.
*
* @method render
* @param {Phaser.State} state - The State to be rendered.
* @param {number} interpolationPercentage - The cumulative amount of time that hasn't been simulated yet, divided
* by the amount of time that will be simulated the next time update()
* runs. Useful for interpolating frames.
*/
render: function (state, children, interpolationPercentage, camera)
{
2016-12-07 02:28:22 +00:00
// Could move to the State Systems or MainLoop
var gl = this.gl;
this.scissor.enabled = (camera.x !== 0 || camera.y !== 0 || camera.width !== gl.canvas.width || camera.height !== gl.canvas.height);
2017-02-07 16:12:20 +00:00
2017-04-07 05:06:55 +00:00
this.setRenderTarget(null);
if (this.scissor.enabled)
2017-02-07 19:30:50 +00:00
{
gl.enable(gl.SCISSOR_TEST);
this.scissor.x = camera.x;
this.scissor.y = gl.drawingBufferHeight - camera.y - camera.height;
this.scissor.width = camera.width;
this.scissor.height = camera.height;
gl.scissor(this.scissor.x, this.scissor.y, this.scissor.width, this.scissor.height);
2017-02-07 19:30:50 +00:00
}
2017-06-28 00:51:04 +00:00
if (camera.backgroundColor.alphaGL > 0)
{
var color = camera.backgroundColor;
2017-06-28 16:47:24 +00:00
var quadBatch = this.quadBatch;
quadBatch.bind();
quadBatch.add(
camera.x, camera.y, camera.width, camera.height,
color.redGL, color.greenGL, color.blueGL, color.alphaGL
);
quadBatch.flush();
this.currentRenderer.bind();
2017-06-28 00:51:04 +00:00
}
2017-02-07 16:12:20 +00:00
var list = children.list;
2017-01-31 21:40:29 +00:00
var length = list.length;
2017-01-31 21:40:29 +00:00
for (var index = 0; index < length; ++index)
2017-01-19 17:53:20 +00:00
{
2017-01-31 21:40:29 +00:00
var child = list[index];
// Setting blend mode if needed
2017-04-07 01:49:15 +00:00
var renderer = this.currentRenderer;
var newBlendMode = child.blendMode;
if (this.blendMode !== newBlendMode)
{
if (renderer)
{
2017-04-07 01:49:15 +00:00
renderer.flush();
}
var blend = this.blendModes[newBlendMode];
gl.enable(gl.BLEND);
if (blend.length > 2)
{
gl.blendFuncSeparate(blend[0], blend[1], blend[2], blend[3]);
}
else
{
gl.blendFunc(blend[0], blend[1]);
}
this.blendMode = newBlendMode;
}
// drawing child
child.renderWebGL(this, child, interpolationPercentage, camera);
2017-04-07 01:49:15 +00:00
renderer = this.currentRenderer;
if (renderer.isFull() || renderer.shouldFlush())
{
2017-04-07 01:49:15 +00:00
renderer.flush();
}
2017-01-19 17:53:20 +00:00
}
this.currentRenderer.flush();
2017-02-10 00:48:32 +00:00
if (camera._fadeAlpha > 0 || camera._flashAlpha > 0)
{
this.setRenderTarget(null);
2017-04-06 17:40:43 +00:00
var quadBatch = this.quadBatch;
quadBatch.bind();
2017-02-10 00:48:32 +00:00
// fade rendering
2017-04-06 17:40:43 +00:00
quadBatch.add(
2017-02-10 00:48:32 +00:00
camera.x, camera.y, camera.width, camera.height,
camera._fadeRed,
camera._fadeGreen,
camera._fadeBlue,
camera._fadeAlpha
);
2017-02-10 00:48:32 +00:00
// flash rendering
2017-04-06 17:40:43 +00:00
quadBatch.add(
2017-02-10 00:48:32 +00:00
camera.x, camera.y, camera.width, camera.height,
camera._flashRed,
camera._flashGreen,
camera._flashBlue,
camera._flashAlpha
);
2017-04-06 17:40:43 +00:00
quadBatch.flush();
this.currentRenderer.bind();
2017-02-10 00:48:32 +00:00
}
if (this.scissor.enabled)
2017-02-07 19:30:50 +00:00
{
gl.disable(gl.SCISSOR_TEST);
}
},
2016-12-07 02:28:22 +00:00
2017-02-07 19:30:50 +00:00
// Called at the end of the render loop (tidy things up, etc)
postRender: function ()
{
this.currentRenderer.flush();
2017-05-16 19:15:01 +00:00
if (this.snapshotCallback)
{
this.snapshotCallback(WebGLSnapshot(this.view));
2017-05-16 19:15:01 +00:00
this.snapshotCallback = null;
}
// Add Post-render hook
2016-12-07 02:28:22 +00:00
// console.log('%c render end ', 'color: #ffffff; background: #ff0000;');
},
snapshot: function (callback)
2017-05-16 19:15:01 +00:00
{
this.snapshotCallback = callback;
},
2016-12-07 02:28:22 +00:00
destroy: function ()
{
this.gl = null;
2017-01-19 17:53:20 +00:00
},
2017-01-23 21:42:47 +00:00
createFBO: function () {},
setBlendMode: function (newBlendMode)
{
var gl = this.gl;
2017-04-07 01:49:15 +00:00
var renderer = this.currentRenderer;
2017-01-23 21:42:47 +00:00
var blend = null;
if (this.blendMode !== newBlendMode)
{
2017-04-07 01:49:15 +00:00
if (renderer)
{
2017-04-07 01:49:15 +00:00
renderer.flush();
}
2017-01-23 21:42:47 +00:00
blend = this.blendModes[newBlendMode];
gl.enable(gl.BLEND);
2017-01-24 15:21:49 +00:00
if (blend.length > 2)
{
gl.blendFuncSeparate(blend[0], blend[1], blend[2], blend[3]);
}
else
{
gl.blendFunc(blend[0], blend[1]);
}
2017-01-23 21:42:47 +00:00
this.blendMode = newBlendMode;
}
},
2017-04-06 17:40:43 +00:00
addRenderer: function (rendererInstance)
{
var index = this.rendererArray.indexOf(rendererInstance);
if (index < 0)
{
this.rendererArray.push(rendererInstance);
2017-04-06 17:40:43 +00:00
return rendererInstance;
}
return null;
},
2017-05-20 01:16:45 +00:00
setTextureFilterMode: function (texture, filterMode)
{
var gl = this.gl;
var glFilter = [ gl.LINEAR, gl.NEAREST ][filterMode];
2017-05-20 01:16:45 +00:00
gl.bindTexture(gl.TEXTURE_2D, texture.texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, glFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, glFilter);
2017-05-20 01:16:45 +00:00
if (this.currentTexture !== null)
{
2017-05-20 01:16:45 +00:00
gl.bindTexture(gl.TEXTURE_2D, this.currentTexture.texture);
}
2017-05-20 01:16:45 +00:00
else
{
2017-05-20 01:16:45 +00:00
gl.bindTexture(gl.TEXTURE_2D, null);
}
2017-05-20 01:16:45 +00:00
return texture;
},
uploadCanvasToGPU: function (srcCanvas, dstTexture, shouldUpdateResource)
{
var gl = this.gl;
if (!dstTexture)
{
2017-04-06 17:40:43 +00:00
dstTexture = new Resources.Texture(null, 0, 0);
/* only call this once */
2017-04-06 17:40:43 +00:00
dstTexture.texture = gl.createTexture();
}
if (shouldUpdateResource)
{
/* Update resource */
2017-04-06 17:40:43 +00:00
gl.bindTexture(gl.TEXTURE_2D, dstTexture.texture);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, srcCanvas);
}
else
{
/* Allocate or Reallocate resource */
2017-04-06 17:40:43 +00:00
gl.bindTexture(gl.TEXTURE_2D, dstTexture.texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, srcCanvas);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
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);
}
2017-04-06 17:40:43 +00:00
dstTexture.width = srcCanvas.width;
dstTexture.height = srcCanvas.height;
/* we must rebind old texture */
this.currentTexture = null;
return dstTexture;
2017-01-23 21:42:47 +00:00
}
2016-12-07 02:28:22 +00:00
};
module.exports = WebGLRenderer;