From f342fac027833bbc5e6dd198dc1f262393197ede Mon Sep 17 00:00:00 2001 From: Felipe Alfonso Date: Thu, 19 Jan 2017 19:43:41 -0300 Subject: [PATCH] Sprite Batch implementation --- v3/src/checksum.js | 2 +- v3/src/renderer/webgl/WebGLRenderer.js | 20 +- .../webgl/batches/blitter/BlitterBatch.js | 27 +- .../webgl/batches/sprite/FragmentShader.js | 8 + .../webgl/batches/sprite/SpriteBatch.js | 294 +++++++++++++++++- .../webgl/batches/sprite/VertexShader.js | 18 ++ v3/src/renderer/webgl/batches/sprite/const.js | 23 ++ .../webgl/utils/{ => buffer}/CreateBuffer.js | 0 .../webgl/utils/{ => buffer}/IndexBuffer.js | 0 .../webgl/utils/{ => buffer}/VertexBuffer.js | 0 .../webgl/utils/{ => shader}/CreateProgram.js | 0 .../webgl/utils/{ => shader}/CreateShader.js | 0 .../CreateTexture2DArrayBuffer.js | 0 .../{ => texture}/CreateTexture2DImage.js | 0 .../webgl/utils/{ => vao}/Attribute.js | 0 .../webgl/utils/{ => vao}/BindVertexArray.js | 0 .../utils/{ => vao}/CreateAttribArray.js | 0 .../webgl/utils/{ => vao}/CreateAttribDesc.js | 0 .../webgl/utils/{ => vao}/VertexArray.js | 0 19 files changed, 368 insertions(+), 24 deletions(-) create mode 100644 v3/src/renderer/webgl/batches/sprite/FragmentShader.js create mode 100644 v3/src/renderer/webgl/batches/sprite/VertexShader.js create mode 100644 v3/src/renderer/webgl/batches/sprite/const.js rename v3/src/renderer/webgl/utils/{ => buffer}/CreateBuffer.js (100%) rename v3/src/renderer/webgl/utils/{ => buffer}/IndexBuffer.js (100%) rename v3/src/renderer/webgl/utils/{ => buffer}/VertexBuffer.js (100%) rename v3/src/renderer/webgl/utils/{ => shader}/CreateProgram.js (100%) rename v3/src/renderer/webgl/utils/{ => shader}/CreateShader.js (100%) rename v3/src/renderer/webgl/utils/{ => texture}/CreateTexture2DArrayBuffer.js (100%) rename v3/src/renderer/webgl/utils/{ => texture}/CreateTexture2DImage.js (100%) rename v3/src/renderer/webgl/utils/{ => vao}/Attribute.js (100%) rename v3/src/renderer/webgl/utils/{ => vao}/BindVertexArray.js (100%) rename v3/src/renderer/webgl/utils/{ => vao}/CreateAttribArray.js (100%) rename v3/src/renderer/webgl/utils/{ => vao}/CreateAttribDesc.js (100%) rename v3/src/renderer/webgl/utils/{ => vao}/VertexArray.js (100%) diff --git a/v3/src/checksum.js b/v3/src/checksum.js index 34b7dee00..49df6b42c 100644 --- a/v3/src/checksum.js +++ b/v3/src/checksum.js @@ -1,4 +1,4 @@ var CHECKSUM = { -build: 'bc213d90-de6f-11e6-bd32-974da4fb460f' +build: '98f0c510-de98-11e6-aa23-b3f509f20724' }; module.exports = CHECKSUM; \ No newline at end of file diff --git a/v3/src/renderer/webgl/WebGLRenderer.js b/v3/src/renderer/webgl/WebGLRenderer.js index c9500f0a8..dc1abd34c 100644 --- a/v3/src/renderer/webgl/WebGLRenderer.js +++ b/v3/src/renderer/webgl/WebGLRenderer.js @@ -7,7 +7,8 @@ var CONST = require('../../const'); var CreateEmptyTexture = require('./utils/CreateEmptyTexture'); -var BlitterBatch = require('./batches/blitter/BlitterBatch'); +var BlitterBatch = require('./batches/blitter/BlitterBatch'); +var SpriteBatch = require('./batches/sprite/SpriteBatch'); var WebGLRenderer = function (game) { @@ -46,6 +47,7 @@ var WebGLRenderer = function (game) this.init(); this.blitterBatch = new BlitterBatch(game, this.gl, this); + this.spriteBatch = new SpriteBatch(game, this.gl, this); this.batch = null; this.currentTexture2D = null; }; @@ -131,12 +133,26 @@ WebGLRenderer.prototype = { ]; }, - setTexture2D: function(texture2D) + setTexture2D: function (texture2D) { this.currentTexture = texture2D; this.batch.dirty = true; }, + setBatch: function (batch) + { + if (this.batch != batch) + { + if (this.batch) + { + this.batch.flush(); + } + batch.bind(); + batch.setTexture2D(this.currentTexture2D, true); + this.batch = batch; + } + }, + resize: function (width, height) { var res = this.game.config.resolution; diff --git a/v3/src/renderer/webgl/batches/blitter/BlitterBatch.js b/v3/src/renderer/webgl/batches/blitter/BlitterBatch.js index 9075da242..46f5ff206 100644 --- a/v3/src/renderer/webgl/batches/blitter/BlitterBatch.js +++ b/v3/src/renderer/webgl/batches/blitter/BlitterBatch.js @@ -1,13 +1,13 @@ // Could you move these into sub-folders please, i.e. 'vao', 'shader' etc? -var BindVertexArray = require('../../utils/BindVertexArray'); -var CreateProgram = require('../../utils/CreateProgram'); -var CreateShader = require('../../utils/CreateShader'); -var CreateBuffer = require('../../utils/CreateBuffer'); -var CreateAttribDesc = require('../../utils/CreateAttribDesc'); -var VertexBuffer = require('../../utils/VertexBuffer'); -var IndexBuffer = require('../../utils/IndexBuffer'); -var VertexArray = require('../../utils/VertexArray'); +var BindVertexArray = require('../../utils/vao/BindVertexArray'); +var CreateProgram = require('../../utils/shader/CreateProgram'); +var CreateShader = require('../../utils/shader/CreateShader'); +var CreateBuffer = require('../../utils/buffer/CreateBuffer'); +var CreateAttribDesc = require('../../utils/vao/CreateAttribDesc'); +var VertexBuffer = require('../../utils/buffer/VertexBuffer'); +var IndexBuffer = require('../../utils/buffer/IndexBuffer'); +var VertexArray = require('../../utils/vao/VertexArray'); var PHASER_CONST = require('../../../../const'); var CONST = require('./const'); @@ -133,16 +133,7 @@ BlitterBatch.prototype = { add: function (x, y, width, height, umin, vmin, umax, vmax) { - var manager = this.manager; - if (manager.batch !== this || this.dirty) - { - if (manager.batch) - manager.batch.flush(); - - this.bind(); - this.setTexture2D(manager.currentTexture2D, true); - manager.batch = this; - } + this.manager.setBatch(this); // The user must check if the buffers are full before flushing // this is to give freedom of when should the renderer flush. var vertexDataBuffer = this.vertexDataBuffer; diff --git a/v3/src/renderer/webgl/batches/sprite/FragmentShader.js b/v3/src/renderer/webgl/batches/sprite/FragmentShader.js new file mode 100644 index 000000000..33603c126 --- /dev/null +++ b/v3/src/renderer/webgl/batches/sprite/FragmentShader.js @@ -0,0 +1,8 @@ +module.exports = [ + 'precision lowp float;', + 'uniform sampler2D u_sampler2D;', + 'varying vec2 v_tex_coord;', + 'void main() {', + ' gl_FragColor = texture2D(u_sampler2D, v_tex_coord);', + '}' +].join('\n'); diff --git a/v3/src/renderer/webgl/batches/sprite/SpriteBatch.js b/v3/src/renderer/webgl/batches/sprite/SpriteBatch.js index f471bf3e6..8fd065cc5 100644 --- a/v3/src/renderer/webgl/batches/sprite/SpriteBatch.js +++ b/v3/src/renderer/webgl/batches/sprite/SpriteBatch.js @@ -1,4 +1,292 @@ -var SpriteBatch = function () -{}; +var BindVertexArray = require('../../utils/vao/BindVertexArray'); +var CreateProgram = require('../../utils/shader/CreateProgram'); +var CreateShader = require('../../utils/shader/CreateShader'); +var CreateBuffer = require('../../utils/buffer/CreateBuffer'); +var CreateAttribDesc = require('../../utils/vao/CreateAttribDesc'); +var VertexBuffer = require('../../utils/buffer/VertexBuffer'); +var IndexBuffer = require('../../utils/buffer/IndexBuffer'); +var VertexArray = require('../../utils/vao/VertexArray'); -module.exports = SpriteBatch; \ No newline at end of file +var PHASER_CONST = require('../../../../const'); +var CONST = require('./const'); + +var SpriteBatch = function (game, gl, manager) +{ + this.game = game; + this.type = PHASER_CONST.WEBGL; + + this.view = game.canvas; + this.resolution = game.config.resolution; + this.width = game.config.width * game.config.resolution; + this.height = game.config.height * game.config.resolution; + + this.glContext = gl; + + this.maxSprites = null; + + this.vertShader = null; + this.fragShader = null; + + this.program = null; + + this.vertexArray = null; + this.indexBufferObject = null; + this.vertexDataBuffer = null; + this.indexDataBuffer = null; + + this.elementCount = 0; + + this.currentTexture2D = null; + this.viewMatrixLocation = null; + + // All of these settings will be able to be controlled via the Game Config + this.config = { + clearBeforeRender: true, + transparent: false, + autoResize: false, + preserveDrawingBuffer: false, + + WebGLContextOptions: { + alpha: true, + antialias: true, + premultipliedAlpha: true, + stencil: true, + preserveDrawingBuffer: false + } + }; + + this.manager = manager; + this.dirty = false; + + this.init(this.glContext); +}; + +SpriteBatch.prototype.constructor = SpriteBatch; + +SpriteBatch.prototype = { + + init: function (gl) + { + + var vertexDataBuffer = new VertexBuffer(CONST.VERTEX_SIZE * CONST.SPRITE_VERTEX_COUNT * CONST.MAX_SPRITES); + + var indexDataBuffer = new IndexBuffer(CONST.INDEX_SIZE * CONST.SPRITE_INDEX_COUNT * CONST.MAX_SPRITES); + + var vertShader = CreateShader(gl, CONST.VERTEX_SHADER_SOURCE, gl.VERTEX_SHADER); + var fragShader = CreateShader(gl, CONST.FRAGMENT_SHADER_SOURCE, gl.FRAGMENT_SHADER); + var program = CreateProgram(gl, vertShader, fragShader); + + var indexBufferObject = CreateBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, gl.STATIC_DRAW, null, indexDataBuffer.getByteCapacity()); + + var attribArray = [ + CreateAttribDesc(gl, program, 'a_position', 2, gl.FLOAT, false, CONST.VERTEX_SIZE, 0), + CreateAttribDesc(gl, program, 'a_tex_coord', 2, gl.FLOAT, false, CONST.VERTEX_SIZE, 8), + CreateAttribDesc(gl, program, 'a_translate', 2, gl.FLOAT, false, CONST.VERTEX_SIZE, 16), + CreateAttribDesc(gl, program, 'a_scale', 2, gl.FLOAT, false, CONST.VERTEX_SIZE, 24), + CreateAttribDesc(gl, program, 'a_rotation', 1, gl.FLOAT, false, CONST.VERTEX_SIZE, 32) + ]; + + var vertexArray = new VertexArray(CreateBuffer(gl, gl.ARRAY_BUFFER, gl.STREAM_DRAW, null, vertexDataBuffer.getByteCapacity()), attribArray); + + var viewMatrixLocation = gl.getUniformLocation(program, 'u_view_matrix'); + + this.vertexDataBuffer = vertexDataBuffer; + this.indexDataBuffer = indexDataBuffer; + + this.vertShader = vertShader; + this.fragShader = fragShader; + this.program = program; + + this.indexBufferObject = indexBufferObject; + this.vertexArray = vertexArray; + + + this.viewMatrixLocation = viewMatrixLocation; + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject); + + var indexBuffer = indexDataBuffer.wordView; + var max = CONST.MAX_SPRITES * CONST.SPRITE_INDEX_COUNT; + + // Populate the index buffer only once + for (var indexA = 0, indexB = 0; indexA < max; indexA += CONST.SPRITE_INDEX_COUNT, indexB += CONST.SPRITE_VERTEX_COUNT) + { + indexBuffer[indexA + 0] = indexB + 0; + indexBuffer[indexA + 1] = indexB + 1; + indexBuffer[indexA + 2] = indexB + 2; + indexBuffer[indexA + 3] = indexB + 0; + indexBuffer[indexA + 4] = indexB + 2; + indexBuffer[indexA + 5] = indexB + 3; + } + gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, indexDataBuffer.getUsedBufferAsWord()); + + this.bind(); + + this.resize(this.width, this.height); + + this.unbind(); + }, + + isFull: function () + { + return (this.vertexDataBuffer.getByteLength() >= this.vertexDataBuffer.getByteCapacity()); + }, + + add: function (x, y, width, height, umin, vmin, umax, vmax, translateX, translateY, scaleX, scaleY, rotation) + { + this.manager.setBatch(this); + + // The user must check if the buffers are full before flushing + // this is to give freedom of when should the renderer flush. var vertexDataBuffer = this.vertexDataBuffer; + var vertexDataBuffer = this.vertexDataBuffer; + var vertexBuffer = vertexDataBuffer.floatView; + var vertexOffset = vertexDataBuffer.allocate(CONST.SPRITE_VERTEX_COMPONENT_COUNT * CONST.SPRITE_VERTEX_COUNT); + + vertexBuffer[vertexOffset++] = x; + vertexBuffer[vertexOffset++] = y; + vertexBuffer[vertexOffset++] = umin; + vertexBuffer[vertexOffset++] = vmin; + vertexBuffer[vertexOffset++] = translateX; + vertexBuffer[vertexOffset++] = translateY; + vertexBuffer[vertexOffset++] = scaleX; + vertexBuffer[vertexOffset++] = scaleY; + vertexBuffer[vertexOffset++] = rotation; + + vertexBuffer[vertexOffset++] = x; + vertexBuffer[vertexOffset++] = y + height; + vertexBuffer[vertexOffset++] = umin; + vertexBuffer[vertexOffset++] = vmax; + vertexBuffer[vertexOffset++] = translateX; + vertexBuffer[vertexOffset++] = translateY; + vertexBuffer[vertexOffset++] = scaleX; + vertexBuffer[vertexOffset++] = scaleY; + vertexBuffer[vertexOffset++] = rotation; + + vertexBuffer[vertexOffset++] = x + width; + vertexBuffer[vertexOffset++] = y + height; + vertexBuffer[vertexOffset++] = umax; + vertexBuffer[vertexOffset++] = vmax; + vertexBuffer[vertexOffset++] = translateX; + vertexBuffer[vertexOffset++] = translateY; + vertexBuffer[vertexOffset++] = scaleX; + vertexBuffer[vertexOffset++] = scaleY; + vertexBuffer[vertexOffset++] = rotation; + + vertexBuffer[vertexOffset++] = x + width; + vertexBuffer[vertexOffset++] = y; + vertexBuffer[vertexOffset++] = umax; + vertexBuffer[vertexOffset++] = vmin; + vertexBuffer[vertexOffset++] = translateX; + vertexBuffer[vertexOffset++] = translateY; + vertexBuffer[vertexOffset++] = scaleX; + vertexBuffer[vertexOffset++] = scaleY; + vertexBuffer[vertexOffset++] = rotation; + + this.elementCount += CONST.SPRITE_INDEX_COUNT; + }, + + setTexture2D: function (texture2D, force) + { + var gl = this.glContext; + + if (this.currentTexture2D !== texture2D || force) + { + this.flush(); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture2D); + + this.currentTexture2D = texture2D; + } + }, + + bind: function () + { + var gl = this.glContext; + + gl.useProgram(this.program); + + gl.clearColor(0, 0, 0, 1); + + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBufferObject); + + BindVertexArray(gl, this.vertexArray); + }, + + unbind: function () + { + var gl = this.glContext; + + gl.useProgram(null); + + gl.disable(gl.BLEND); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + }, + + flush: function () + { + var gl = this.glContext; + var vertexDataBuffer = this.vertexDataBuffer; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertexDataBuffer.getUsedBufferAsFloat()); + + gl.drawElements(gl.TRIANGLES, this.elementCount, gl.UNSIGNED_SHORT, 0); + + vertexDataBuffer.clear(); + + this.elementCount = 0; + }, + + resize: function (width, height) + { + var gl = this.glContext; + var res = this.game.config.resolution; + + this.width = width * res; + this.height = height * res; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = (this.width / res) + 'px'; + this.view.style.height = (this.height / res) + 'px'; + } + + gl.viewport(0, 0, this.width, this.height); + + gl.uniformMatrix4fv( + this.viewMatrixLocation, + false, + new Float32Array([ + 2 / this.view.width, 0, 0, 0, + 0, -2 / this.view.height, 0, 0, + 0, 0, 1, 1, + -1, 1, 0, 0 + ]) + ); + }, + + destroy: function () + { + var gl = this.glContext; + + if (gl) + { + gl.deleteShader(this.vertShader); + gl.deleteShader(this.fragShader); + gl.deleteProgram(this.program); + gl.deleteBuffer(this.indexBufferObject); + gl.deleteBuffer(this.vertexArray.buffer); + } + } + +}; + +module.exports = SpriteBatch; diff --git a/v3/src/renderer/webgl/batches/sprite/VertexShader.js b/v3/src/renderer/webgl/batches/sprite/VertexShader.js new file mode 100644 index 000000000..ac930ca04 --- /dev/null +++ b/v3/src/renderer/webgl/batches/sprite/VertexShader.js @@ -0,0 +1,18 @@ +module.exports = [ + 'uniform mat4 u_view_matrix;', + 'attribute vec2 a_position;', + 'attribute vec2 a_tex_coord;', + 'attribute vec2 a_translate;', + 'attribute vec2 a_scale;', + 'attribute float a_rotation;', + 'varying vec2 v_tex_coord;', + 'void main () {', + ' float t_cos = cos(a_rotation);', + ' float t_sin = sin(a_rotation);', + ' vec2 t_position = a_position * a_scale;', + ' t_position = vec2(t_position.x * t_cos - t_position.y * t_sin, t_position.x * t_sin + t_position.y * t_cos);', + ' t_position += a_translate;', + ' gl_Position = u_view_matrix * vec4(t_position, 1.0, 1.0);', + ' v_tex_coord = a_tex_coord;', + '}' +].join('\n'); diff --git a/v3/src/renderer/webgl/batches/sprite/const.js b/v3/src/renderer/webgl/batches/sprite/const.js new file mode 100644 index 000000000..d62075ce2 --- /dev/null +++ b/v3/src/renderer/webgl/batches/sprite/const.js @@ -0,0 +1,23 @@ +var FragmentShader = require('./FragmentShader'); +var VertexShader = require('./VertexShader'); + +var CONST = { + + // VERTEX_SIZE = (sizeof(vec2) * 4) + (sizeof(float)) + VERTEX_SIZE: 36, + INDEX_SIZE: 2, + SPRITE_VERTEX_COUNT: 4, + SPRITE_INDEX_COUNT: 6, + + // How many 32-bit components does the vertex have. + SPRITE_VERTEX_COMPONENT_COUNT: 9, + + // Can't be bigger since index are 16-bit + MAX_SPRITES: 10000, + + VERTEX_SHADER_SOURCE: VertexShader, + FRAGMENT_SHADER_SOURCE: FragmentShader + +}; + +module.exports = CONST; diff --git a/v3/src/renderer/webgl/utils/CreateBuffer.js b/v3/src/renderer/webgl/utils/buffer/CreateBuffer.js similarity index 100% rename from v3/src/renderer/webgl/utils/CreateBuffer.js rename to v3/src/renderer/webgl/utils/buffer/CreateBuffer.js diff --git a/v3/src/renderer/webgl/utils/IndexBuffer.js b/v3/src/renderer/webgl/utils/buffer/IndexBuffer.js similarity index 100% rename from v3/src/renderer/webgl/utils/IndexBuffer.js rename to v3/src/renderer/webgl/utils/buffer/IndexBuffer.js diff --git a/v3/src/renderer/webgl/utils/VertexBuffer.js b/v3/src/renderer/webgl/utils/buffer/VertexBuffer.js similarity index 100% rename from v3/src/renderer/webgl/utils/VertexBuffer.js rename to v3/src/renderer/webgl/utils/buffer/VertexBuffer.js diff --git a/v3/src/renderer/webgl/utils/CreateProgram.js b/v3/src/renderer/webgl/utils/shader/CreateProgram.js similarity index 100% rename from v3/src/renderer/webgl/utils/CreateProgram.js rename to v3/src/renderer/webgl/utils/shader/CreateProgram.js diff --git a/v3/src/renderer/webgl/utils/CreateShader.js b/v3/src/renderer/webgl/utils/shader/CreateShader.js similarity index 100% rename from v3/src/renderer/webgl/utils/CreateShader.js rename to v3/src/renderer/webgl/utils/shader/CreateShader.js diff --git a/v3/src/renderer/webgl/utils/CreateTexture2DArrayBuffer.js b/v3/src/renderer/webgl/utils/texture/CreateTexture2DArrayBuffer.js similarity index 100% rename from v3/src/renderer/webgl/utils/CreateTexture2DArrayBuffer.js rename to v3/src/renderer/webgl/utils/texture/CreateTexture2DArrayBuffer.js diff --git a/v3/src/renderer/webgl/utils/CreateTexture2DImage.js b/v3/src/renderer/webgl/utils/texture/CreateTexture2DImage.js similarity index 100% rename from v3/src/renderer/webgl/utils/CreateTexture2DImage.js rename to v3/src/renderer/webgl/utils/texture/CreateTexture2DImage.js diff --git a/v3/src/renderer/webgl/utils/Attribute.js b/v3/src/renderer/webgl/utils/vao/Attribute.js similarity index 100% rename from v3/src/renderer/webgl/utils/Attribute.js rename to v3/src/renderer/webgl/utils/vao/Attribute.js diff --git a/v3/src/renderer/webgl/utils/BindVertexArray.js b/v3/src/renderer/webgl/utils/vao/BindVertexArray.js similarity index 100% rename from v3/src/renderer/webgl/utils/BindVertexArray.js rename to v3/src/renderer/webgl/utils/vao/BindVertexArray.js diff --git a/v3/src/renderer/webgl/utils/CreateAttribArray.js b/v3/src/renderer/webgl/utils/vao/CreateAttribArray.js similarity index 100% rename from v3/src/renderer/webgl/utils/CreateAttribArray.js rename to v3/src/renderer/webgl/utils/vao/CreateAttribArray.js diff --git a/v3/src/renderer/webgl/utils/CreateAttribDesc.js b/v3/src/renderer/webgl/utils/vao/CreateAttribDesc.js similarity index 100% rename from v3/src/renderer/webgl/utils/CreateAttribDesc.js rename to v3/src/renderer/webgl/utils/vao/CreateAttribDesc.js diff --git a/v3/src/renderer/webgl/utils/VertexArray.js b/v3/src/renderer/webgl/utils/vao/VertexArray.js similarity index 100% rename from v3/src/renderer/webgl/utils/VertexArray.js rename to v3/src/renderer/webgl/utils/vao/VertexArray.js