Use transformed elements instead of instanced rendering.

There is simply no good way to control instanced rendering with batches,
and there is no efficient way to use hardware transform when using
vertices to render.
This commit is contained in:
Ben Richards 2024-05-17 16:34:04 +12:00
parent ce6324837e
commit 6bbfde50da
7 changed files with 326 additions and 534 deletions

View file

@ -2925,26 +2925,28 @@ var WebGLRenderer = new Class({
},
/**
* Draw a number of instances to a drawing context.
* Draw a number of vertices to a drawing context.
*
* This draws the vertices using an index buffer. The buffer should be
* bound to the VAO. Vertices are drawn as a `TRIANGLE_STRIP`.
*
* This is the primary render method. It requires all the WebGL resources
* necessary to render the instances, so they don't have to be set up
* necessary to render the vertices, so they don't have to be set up
* ad-hoc elsewhere.
*
* It does not upload vertex data to buffers. Ensure that this is done
* before calling this method.
*
* @method Phaser.Renderer.WebGL.WebGLRenderer#drawInstances
* @method Phaser.Renderer.WebGL.WebGLRenderer#drawElements
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The DrawingContext to draw to.
* @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper[]} textures - An array of textures to bind. Textures are bound to units corresponding to their indices in the array.
* @param {Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper} program - The shader program to use.
* @param {Phaser.Renderer.WebGL.Wrappers.WebGLVAOWrapper} vao - The Vertex Array Object to bind.
* @param {number} instanceStart - The instance to start drawing from.
* @param {number} instanceVertices - The number of vertices per instance.
* @param {number} instanceCount - The number of instances to draw.
* @param {Phaser.Renderer.WebGL.Wrappers.WebGLVAOWrapper} vao - The Vertex Array Object to bind. It must have an index buffer attached.
* @param {number} count - The number of vertices to draw. Because of the TRIANGLE_STRIP topology, this should be `n + 2`, where `n` is the number of triangles to draw, including degenerate triangles.
* @param {number} offset - The offset to start drawing from in the index buffer. This is in bytes, and should be a multiple of 2 (for 16-bit `UNSIGNED_SHORT` indices).
*/
drawInstances: function (drawingContext, textures, program, vao, instanceStart, instanceVertices, instanceCount)
drawElements: function (drawingContext, textures, program, vao, count, offset)
{
var gl = this.gl;
@ -2952,29 +2954,11 @@ var WebGLRenderer = new Class({
program.bind();
vao.bind(instanceStart);
vao.bind();
this.glTextureUnits.bindUnits(textures);
if (vao.indexBuffer)
{
this.instancedArraysExtension.drawElementsInstancedANGLE(
gl.TRIANGLE_STRIP,
instanceVertices,
gl.UNSIGNED_SHORT,
0,
instanceCount
);
}
else
{
this.instancedArraysExtension.drawArraysInstancedANGLE(
gl.TRIANGLE_STRIP,
0,
instanceVertices,
instanceCount
);
}
gl.drawElements(gl.TRIANGLE_STRIP, count, gl.UNSIGNED_SHORT, offset);
},
/**

View file

@ -5,8 +5,8 @@
*/
var Class = require('../../../utils/Class');
var ShaderSourceFS = require('../shaders/BatchQuad-frag.js');
var ShaderSourceVS = require('../shaders/BatchQuad-vert.js');
var ShaderSourceFS = require('../shaders/Multi-frag.js');
var ShaderSourceVS = require('../shaders/Multi-vert.js');
var Utils = require('../Utils.js');
var WebGLVertexBufferLayoutWrapper = require('../wrappers/WebGLVertexBufferLayoutWrapper.js');
var Batch = require('./Batch');
@ -15,7 +15,7 @@ var Batch = require('./Batch');
* @classdesc
* This RenderNode draws Standard Batch Render (SBR) quads in batches.
*
* @class BatchTexturedTintedRawQuads
* @class BatchTexturedTintedTransformedQuads
* @extends Phaser.Renderer.WebGL.RenderNodes.Batch
* @memberof Phaser.Renderer.WebGL.RenderNodes
* @constructor
@ -23,12 +23,12 @@ var Batch = require('./Batch');
* @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode.
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The renderer that owns this RenderNode.
*/
var BatchTexturedTintedRawQuads = new Class({
var BatchTexturedTintedTransformedQuads = new Class({
Extends: Batch,
initialize: function BatchTexturedTintedRawQuads (manager, renderer)
initialize: function BatchTexturedTintedTransformedQuads (manager, renderer)
{
Batch.call(this, 'BatchTexturedTintedRawQuads', manager, renderer);
Batch.call(this, 'BatchTexturedTintedTransformedQuads', manager, renderer);
var gl = renderer.gl;
@ -36,7 +36,7 @@ var BatchTexturedTintedRawQuads = new Class({
* The number of quads per batch, used to determine the size of the
* vertex and quad buffers, and the number of instances to render.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#quadsPerBatch
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#quadsPerBatch
* @type {number}
* @since 3.90.0
*/
@ -44,20 +44,47 @@ var BatchTexturedTintedRawQuads = new Class({
/**
* The number of vertices per instance.
* This is always 4 for a quad.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#verticesPerInstance
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#verticesPerInstance
* @type {number}
* @since 3.90.0
* @default 4
*/
this.verticesPerInstance = 4;
/**
* The number of indices per instance.
* This is always 6 for a quad.
* It is composed of four triangles,
* but the first and last are degenerate to allow for
* TRIANGLE_STRIP rendering, so there are only two true triangles.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#indicesPerInstance
* @type {number}
* @since 3.90.0
* @default 6
*/
this.indicesPerInstance = 6;
/**
* The number of bytes per index per instance.
* This is used to advance the index buffer, and accounts for the
* size of a Uint16Array element.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#bytesPerIndexPerInstance
* @type {number}
* @since 3.90.0
* @default 12
*/
this.bytesPerIndexPerInstance = this.indicesPerInstance * Uint16Array.BYTES_PER_ELEMENT;
/**
* The maximum number of textures per batch entry.
* This is usually the maximum number of texture units available,
* but it might be smaller for some uses.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#maxTexturesPerBatch
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#maxTexturesPerBatch
* @type {number}
* @since 3.90.0
*/
@ -66,7 +93,7 @@ var BatchTexturedTintedRawQuads = new Class({
/**
* The number of quads currently in the batch.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#instanceCount
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#instanceCount
* @type {number}
* @since 3.90.0
*/
@ -84,126 +111,69 @@ var BatchTexturedTintedRawQuads = new Class({
this.program = renderer.createProgram(ShaderSourceVS, ParsedShaderSourceFS);
/**
* The layout, data, and vertex buffer used to store the quad data.
* The index buffer defining vertex order.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#quadBufferLayout
* @type {Phaser.Renderer.WebGL.Wrappers.WebGLVertexBufferLayoutWrapper}
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#indexBuffer
* @type {Phaser.Renderer.WebGL.Wrappers.WebGLBufferWrapper}
* @since 3.90.0
*/
this.quadBufferLayout = new WebGLVertexBufferLayoutWrapper(renderer, this.program, {
instanceDivisor: 1,
usage: gl.DYNAMIC_DRAW,
count: this.quadsPerBatch,
layout: [
{
name: 'inTexIdAndTintEffect',
location: -1,
size: 2,
type: gl.FLOAT,
normalized: false
},
{
name: 'inTextureBox',
location: -1,
size: 4,
type: gl.FLOAT,
normalized: false
},
{
name: 'inTintTL',
location: -1,
size: 4,
type: gl.UNSIGNED_BYTE,
normalized: true
},
{
name: 'inTintBL',
location: -1,
size: 4,
type: gl.UNSIGNED_BYTE,
normalized: true
},
{
name: 'inTintTR',
location: -1,
size: 4,
type: gl.UNSIGNED_BYTE,
normalized: true
},
{
name: 'inTintBR',
location: -1,
size: 4,
type: gl.UNSIGNED_BYTE,
normalized: true
},
{
name: 'inObjectMatrixABCD',
location: -1,
size: 4,
type: gl.FLOAT,
normalized: false
},
{
name: 'inObjectMatrixXY',
location: -1,
size: 2,
type: gl.FLOAT,
normalized: false
},
{
name: 'inWorldMatrixABCD',
location: -1,
size: 4,
type: gl.FLOAT,
normalized: false
},
{
name: 'inWorldMatrixXY',
location: -1,
size: 2,
type: gl.FLOAT,
normalized: false
},
{
name: 'inViewMatrixABCD',
location: -1,
size: 4,
type: gl.FLOAT,
normalized: false
},
{
name: 'inViewMatrixXY',
location: -1,
size: 2,
type: gl.FLOAT,
normalized: false
}
]
});
this.indexBuffer = renderer.createIndexBuffer(
this._generateElementIndices(this.quadsPerBatch),
gl.STATIC_DRAW
);
/**
* The layout, data, and vertex buffer used to store the instance data.
* The layout, data, and vertex buffer used to store the vertex data.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#instanceBufferLayout
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#vertexBufferLayout
* @type {Phaser.Renderer.WebGL.Wrappers.WebGLVertexBufferLayoutWrapper}
* @since 3.90.0
*/
this.instanceBufferLayout = new WebGLVertexBufferLayoutWrapper(renderer, this.program, {
usage: gl.STATIC_DRAW,
count: this.verticesPerInstance,
instanceDivisor: 0,
layout: [
{
name: 'inPositionAndIndex',
location: -1,
size: 3,
type: gl.FLOAT,
normalized: false
}
]
});
this.vertexBufferLayout = new WebGLVertexBufferLayoutWrapper(
renderer,
this.program,
{
usage: gl.DYNAMIC_DRAW,
count: this.quadsPerBatch * 4,
layout: [
{
name: 'inPosition',
location: -1,
size: 2,
type: gl.FLOAT,
normalized: false
},
{
name: 'inTexCoord',
location: -1,
size: 2,
type: gl.FLOAT,
normalized: false
},
{
name: 'inTexId',
location: -1,
size: 1,
type: gl.FLOAT,
normalized: false
},
{
name: 'inTintEffect',
location: -1,
size: 1,
type: gl.FLOAT,
normalized: false
},
{
name: 'inTint',
location: -1,
size: 4,
type: gl.UNSIGNED_BYTE,
normalized: true
}
]
}
);
/**
* The Vertex Array Object used to render the batch.
@ -212,15 +182,14 @@ var BatchTexturedTintedRawQuads = new Class({
* @type {Phaser.Renderer.WebGL.Wrappers.WebGLVAOWrapper}
* @since 3.90.0
*/
this.vao = renderer.createVAO(null, [
this.quadBufferLayout,
this.instanceBufferLayout
this.vao = renderer.createVAO(this.indexBuffer, [
this.vertexBufferLayout
]);
/**
* The current batch entry being filled with textures.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#currentBatchEntry
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#currentBatchEntry
* @type {Phaser.Types.Renderer.WebGL.WebGLPipelineBatchEntry}
* @since 3.90.0
*/
@ -239,7 +208,7 @@ var BatchTexturedTintedRawQuads = new Class({
* buffer. When the batch flushes, there will be one vertex buffer
* upload, and one draw call per batch entry.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#batchEntries
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#batchEntries
* @type {Phaser.Types.Renderer.WebGL.WebGLPipelineBatchEntry[]}
* @since 3.90.0
* @default []
@ -247,13 +216,13 @@ var BatchTexturedTintedRawQuads = new Class({
this.batchEntries = [];
/**
* The number of floats per quad, used to determine how much of the quad buffer to update.
* The number of floats per instance, used to determine how much of the vertex buffer to update.
*
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#floatsPerQuad
* @name Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#floatsPerInstance
* @type {number}
* @since 3.90.0
*/
this.floatsPerQuad = this.quadBufferLayout.layout.stride / Float32Array.BYTES_PER_ELEMENT;
this.floatsPerInstance = this.vertexBufferLayout.layout.stride * this.verticesPerInstance / Float32Array.BYTES_PER_ELEMENT;
// Set the dimension-related uniforms and listen for resize events.
this.resize(renderer.width, renderer.height);
@ -263,10 +232,6 @@ var BatchTexturedTintedRawQuads = new Class({
// because it addresses texture units, not textures.
this.program.setUniform('uMainSampler[0]', this.renderer.textureUnitIndices);
// Initialize the instance buffer, and listen for context loss and restore.
this.populateInstanceBuffer();
this.renderer.on(Phaser.Renderer.Events.RESTORE_WEBGL, this.populateInstanceBuffer, this);
// Listen for changes to the number of draw calls per batch.
this.manager.on(Phaser.Renderer.Events.SET_PARALLEL_TEXTURE_UNITS, this.updateTextureCount, this);
},
@ -274,7 +239,7 @@ var BatchTexturedTintedRawQuads = new Class({
/**
* Draw then empty the current batch.
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#run
* @method Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#run
* @since 3.90.0
* @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context.
*/
@ -282,8 +247,12 @@ var BatchTexturedTintedRawQuads = new Class({
{
if (this.instanceCount === 0) { return; }
var bytesPerIndexPerInstance = this.bytesPerIndexPerInstance;
var indicesPerInstance = this.indicesPerInstance;
var program = this.program;
var vao = this.vao;
var renderer = this.renderer;
var quadBuffer = this.quadBufferLayout.buffer;
var vertexBuffer = this.vertexBufferLayout.buffer;
// Finalize the current batch entry.
this.pushCurrentBatchEntry();
@ -293,11 +262,11 @@ var BatchTexturedTintedRawQuads = new Class({
{
// We use a subarray to avoid copying the buffer, but still
// control the length.
quadBuffer.update(this.quadBufferLayout.viewFloat32.subarray(0, this.instanceCount * this.floatsPerQuad));
vertexBuffer.update(this.vertexBufferLayout.viewFloat32.subarray(0, this.instanceCount * this.floatsPerInstance));
}
else
{
quadBuffer.update(this.quadBufferLayout.data);
vertexBuffer.update(this.vertexBufferLayout.data);
}
this.program.setUniform('uRoundPixels', drawingContext.camera.roundPixels);
@ -306,14 +275,13 @@ var BatchTexturedTintedRawQuads = new Class({
for (var i = 0; i < subBatches; i++)
{
var entry = this.batchEntries[i];
renderer.drawInstances(
renderer.drawElements(
drawingContext,
entry.texture,
this.program,
this.vao,
entry.start,
this.verticesPerInstance,
entry.count
program,
vao,
entry.count * indicesPerInstance,
entry.start * bytesPerIndexPerInstance
);
}
@ -334,26 +302,34 @@ var BatchTexturedTintedRawQuads = new Class({
* - Top-right
* - Bottom-right
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#batch
* @method Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#batch
* @since 3.90.0
* @param {Phaser.Types.Renderer.WebGL.DrawingContext} currentContext - The current drawing context.
* @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} glTexture - The texture to render.
* @param {boolean} tintFill - Whether to tint the fill color.
* @param {Phaser.GameObjects.Components.TransformMatrix} objectMatrix - The matrix to transform the base quad into the object space.
* @param {Phaser.GameObjects.Components.TransformMatrix} worldMatrix - The matrix to transform the object space quad into the world space.
* @param {Phaser.GameObjects.Components.TransformMatrix} viewMatrix - The matrix to transform the world space quad into the view space.
* @param {number} x0 - The x coordinate of the top-left corner.
* @param {number} y0 - The y coordinate of the top-left corner.
* @param {number} x1 - The x coordinate of the bottom-left corner.
* @param {number} y1 - The y coordinate of the bottom-left corner.
* @param {number} x2 - The x coordinate of the top-right corner.
* @param {number} y2 - The y coordinate of the top-right corner.
* @param {number} x3 - The x coordinate of the bottom-right corner.
* @param {number} y3 - The y coordinate of the bottom-right corner.
* @param {number} texX - The left u coordinate (0-1).
* @param {number} texY - The top v coordinate (0-1).
* @param {number} texWidth - The width of the texture (0-1).
* @param {number} texHeight - The height of the texture (0-1).
* @param {number} tintFill - Whether to tint the fill color.
* @param {number} tintTL - The top-left tint color.
* @param {number} tintBL - The bottom-left tint color.
* @param {number} tintTR - The top-right tint color.
* @param {number} tintBR - The bottom-right tint color.
*/
batch: function (currentContext, glTexture, tintFill, objectMatrix, worldMatrix, viewMatrix, texX, texY, texWidth, texHeight, tintTL, tintBL, tintTR, tintBR)
batch: function (currentContext, glTexture, x0, y0, x1, y1, x2, y2, x3, y3, texX, texY, texWidth, texHeight, tintFill, tintTL, tintBL, tintTR, tintBR)
{
this.manager.setCurrentBatchNode(this, currentContext);
if (this.instanceCount === 0)
{
this.manager.setCurrentBatchNode(this, currentContext);
}
// Texture
@ -381,24 +357,46 @@ var BatchTexturedTintedRawQuads = new Class({
currentBatchEntry.unit++;
}
var quadOffset32 = this.instanceCount * this.floatsPerQuad;
var quadViewF32 = this.quadBufferLayout.viewFloat32;
var quadViewU32 = this.quadBufferLayout.viewUint32;
// Update the vertex buffer.
var vertexOffset32 = this.instanceCount * this.floatsPerInstance;
var vertexViewF32 = this.vertexBufferLayout.viewFloat32;
var vertexViewU32 = this.vertexBufferLayout.viewUint32;
// Quad
quadViewF32[quadOffset32 + 0] = textureIndex;
quadViewF32[quadOffset32 + 1] = tintFill;
quadViewF32[quadOffset32 + 2] = texX;
quadViewF32[quadOffset32 + 3] = texY;
quadViewF32[quadOffset32 + 4] = texWidth;
quadViewF32[quadOffset32 + 5] = texHeight;
quadViewU32[quadOffset32 + 6] = tintTL;
quadViewU32[quadOffset32 + 7] = tintBL;
quadViewU32[quadOffset32 + 8] = tintTR;
quadViewU32[quadOffset32 + 9] = tintBR;
quadViewF32.set(objectMatrix.matrix.subarray(0, 6), quadOffset32 + 10);
quadViewF32.set(worldMatrix.matrix.subarray(0, 6), quadOffset32 + 16);
quadViewF32.set(viewMatrix.matrix.subarray(0, 6), quadOffset32 + 22);
// Top-left
vertexViewF32[vertexOffset32++] = x0;
vertexViewF32[vertexOffset32++] = y0;
vertexViewF32[vertexOffset32++] = texX;
vertexViewF32[vertexOffset32++] = texY;
vertexViewF32[vertexOffset32++] = textureIndex;
vertexViewF32[vertexOffset32++] = tintFill;
vertexViewU32[vertexOffset32++] = tintTL;
// Bottom-left
vertexViewF32[vertexOffset32++] = x1;
vertexViewF32[vertexOffset32++] = y1;
vertexViewF32[vertexOffset32++] = texX;
vertexViewF32[vertexOffset32++] = texY + texHeight;
vertexViewF32[vertexOffset32++] = textureIndex;
vertexViewF32[vertexOffset32++] = tintFill;
vertexViewU32[vertexOffset32++] = tintBL;
// Top-right
vertexViewF32[vertexOffset32++] = x2;
vertexViewF32[vertexOffset32++] = y2;
vertexViewF32[vertexOffset32++] = texX + texWidth;
vertexViewF32[vertexOffset32++] = texY;
vertexViewF32[vertexOffset32++] = textureIndex;
vertexViewF32[vertexOffset32++] = tintFill;
vertexViewU32[vertexOffset32++] = tintTR;
// Bottom-right
vertexViewF32[vertexOffset32++] = x3;
vertexViewF32[vertexOffset32++] = y3;
vertexViewF32[vertexOffset32++] = texX + texWidth;
vertexViewF32[vertexOffset32++] = texY + texHeight;
vertexViewF32[vertexOffset32++] = textureIndex;
vertexViewF32[vertexOffset32++] = tintFill;
vertexViewU32[vertexOffset32++] = tintBR;
// Increment the instance count.
this.instanceCount++;
@ -418,7 +416,7 @@ var BatchTexturedTintedRawQuads = new Class({
* Push the current batch entry to the batch entry list,
* and create a new batch entry for future use.
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#pushCurrentBatchEntry
* @method Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#pushCurrentBatchEntry
* @since 3.90.0
*/
pushCurrentBatchEntry: function ()
@ -441,29 +439,41 @@ var BatchTexturedTintedRawQuads = new Class({
},
/**
* Populate the instance buffer with the base quad.
* Generate element indices for the quad vertices.
* This is called automatically when the node is initialized.
*
* This is called automatically when the renderer is initialized,
* or when the context is lost and restored.
* Each quad is drawn as two triangles, with the vertices in the order:
* 0, 0, 1, 2, 3, 3. The quads are drawn as a TRIANGLE_STRIP, so the
* repeated vertices form degenerate triangles to connect the quads
* without being drawn.
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#populateInstanceBuffer
* @method Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#_generateElementIndices
* @since 3.90.0
* @private
* @param {number} quads - The number of quads to define.
* @return {Uint16Array} The index buffer data.
*/
populateInstanceBuffer: function ()
_generateElementIndices: function (quads)
{
this.instanceBufferLayout.viewFloat32.set([
0, 0, 0,
0, 1, 1,
1, 0, 2,
1, 1, 3
]);
this.instanceBufferLayout.buffer.update(this.instanceBufferLayout.data);
var indices = new Uint16Array(quads * 6);
var offset = 0;
for (var i = 0; i < quads; i++)
{
var index = i * 4;
indices[offset++] = index;
indices[offset++] = index;
indices[offset++] = index + 1;
indices[offset++] = index + 2;
indices[offset++] = index + 3;
indices[offset++] = index + 3;
}
return indices;
},
/**
* Set new dimensions for the renderer. This is called automatically when the renderer is resized.
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedRawQuads#resize
* @method Phaser.Renderer.WebGL.RenderNodes.BatchTexturedTintedTransformedQuads#resize
* @since 3.90.0
* @param {number} width - The new width of the renderer.
* @param {number} height - The new height of the renderer.
@ -489,7 +499,7 @@ var BatchTexturedTintedRawQuads = new Class({
* for the `Phaser.Renderer.Events.SET_PARALLEL_TEXTURE_UNITS` event,
* triggered by the RenderNodeManager.
*
* @method Phaser.Renderer.WebGL.RenderNodes.Pressurizer#updateDrawCallCount
* @method Phaser.Renderer.WebGL.RenderNodes.Pressurizer#updateTextureCount
* @since 3.90.0
* @param {number} [count] - The new number of draw calls per batch. If undefined, the maximum number of texture units is used.
*/
@ -534,4 +544,4 @@ var BatchTexturedTintedRawQuads = new Class({
}
});
module.exports = BatchTexturedTintedRawQuads;
module.exports = BatchTexturedTintedTransformedQuads;

View file

@ -1,199 +0,0 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @copyright 2013-2024 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var TransformMatrix = require('../../../gameobjects/components/TransformMatrix.js');
var Class = require('../../../utils/Class.js');
var RenderNode = require('./RenderNode.js');
/**
* @classdesc
* A RenderNode which computes 2D matrix transforms for a quad.
*
* The three matrices returned are:
*
* - objectMatrix: The matrix used to transform the object from a 1x1 quad to its screen region.
* - worldMatrix: The matrix used to transform the object from its local space to world space.
* - viewMatrix: The matrix used to transform the object from world space to camera space.
*
* @class GetSBRQuadMatrices
* @memberof Phaser.Renderer.WebGL.RenderNodes
* @constructor
* @since 3.90.0
* @extends Phaser.Renderer.WebGL.RenderNodes.RenderNode
* @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode.
* @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The renderer that owns this RenderNode.
*/
var GetSBRQuadMatrices = new Class({
Extends: RenderNode,
initialize: function GetSBRQuadMatrices (manager, renderer)
{
RenderNode.call(this, 'GetSBRQuadMatrices', manager, renderer);
/**
* The matrix used internally to compute camera transforms.
*
* @name Phaser.Renderer.WebGL.RenderNodes.Single#_camMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.90.0
* @private
*/
this._camMatrix = new TransformMatrix();
/**
* The matrix used internally to compute sprite transforms.
*
* @name Phaser.Renderer.WebGL.RenderNodes.Single#_objectMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.90.0
* @private
*/
this._objectMatrix = new TransformMatrix();
/**
* The matrix used internally to compute the final transform.
*
* @name Phaser.Renderer.WebGL.RenderNodes.Single#_worldMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.90.0
* @private
*/
this._worldMatrix = new TransformMatrix();
/**
* The matrices computed by this node. This is a reference to
* the internal matrices. It is the return value of the run method.
*
* @name Phaser.Renderer.WebGL.RenderNodes.Single#_matrices
* @type {{
* objectMatrix: Phaser.GameObjects.Components.TransformMatrix,
* worldMatrix: Phaser.GameObjects.Components.TransformMatrix,
* camMatrix: Phaser.GameObjects.Components.TransformMatrix
* }}
* @since 3.90.0
* @private
*/
this._matrices = {
objectMatrix: this._objectMatrix,
worldMatrix: this._worldMatrix,
camMatrix: this._camMatrix
};
},
/**
* Compute the 2D matrix transforms for a quad.
*
* The return value is an object held by this node,
* so be sure to copy it if you need it later.
*
* @method Phaser.Renderer.WebGL.RenderNodes.GetSBRQuadMatrices#run
* @since 3.90.0
* @param {Phaser.GameObjects.GameObject} gameObject - The game object to transform.
* @param {Phaser.Cameras.Scene2D.Camera} camera - The camera to use for the transformation.
* @param {Phaser.GameObjects.Components.TransformMatrix} [parentTransformMatrix] - The parent matrix to apply, if any.
* @return {{
* objectMatrix: Phaser.GameObjects.Components.TransformMatrix,
* worldMatrix: Phaser.GameObjects.Components.TransformMatrix,
* camMatrix: Phaser.GameObjects.Components.TransformMatrix
* }} The computed transform matrices.
*/
run: function (gameObject, camera, parentTransformMatrix)
{
var camMatrix = this._camMatrix;
var objectMatrix = this._objectMatrix;
var worldMatrix = this._worldMatrix;
var frame = gameObject.frame;
var frameX = frame.x;
var frameY = frame.y;
var frameWidth = frame.cutWidth;
var frameHeight = frame.cutHeight;
var customPivot = frame.customPivot;
var displayOriginX = gameObject.displayOriginX;
var displayOriginY = gameObject.displayOriginY;
if (gameObject.isCropped)
{
var crop = gameObject._crop;
frameWidth = crop.width;
frameHeight = crop.height;
frameX = crop.x;
frameY = crop.y;
}
var x = -displayOriginX + frameX;
var y = -displayOriginY + frameY;
var flipX = 1;
var flipY = 1;
if (gameObject.flipX)
{
if (!customPivot)
{
x += (-frame.realWidth + (displayOriginX * 2));
}
flipX = -1;
}
if (gameObject.flipY)
{
if (!customPivot)
{
y += (-frame.realHeight + (displayOriginY * 2));
}
flipY = -1;
}
var gx = gameObject.x;
var gy = gameObject.y;
objectMatrix.applyITRS(
x,
y,
0,
frameWidth,
frameHeight
);
worldMatrix.applyITRS(
gx,
gy,
gameObject.rotation,
gameObject.scaleX * flipX,
gameObject.scaleY * flipY
);
if (parentTransformMatrix)
{
// Multiply the camera by the parent matrix
camMatrix.copyFrom(camera.matrix);
camMatrix.multiplyWithOffset(parentTransformMatrix, -camera.scrollX * gameObject.scrollFactorX, -camera.scrollY * gameObject.scrollFactorY);
return this._matrices;
}
else
{
// camMatrix will not be mutated after this point, so we just take a reference.
camMatrix = camera.matrix;
worldMatrix.e -= camera.scrollX * gameObject.scrollFactorX;
worldMatrix.f -= camera.scrollY * gameObject.scrollFactorY;
return {
objectMatrix: objectMatrix,
worldMatrix: worldMatrix,
camMatrix: camMatrix
};
}
}
});
module.exports = GetSBRQuadMatrices;

View file

@ -4,6 +4,7 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var TransformMatrix = require('../../../gameobjects/components/TransformMatrix.js');
var Class = require('../../../utils/Class');
var Utils = require('../Utils.js');
var RenderNode = require('./RenderNode');
@ -29,6 +30,36 @@ var ImageQuadrangulateBatch = new Class({
initialize: function ImageQuadrangulateBatch (manager, renderer)
{
RenderNode.call(this, 'ImageQuadrangulateBatch', manager, renderer);
/**
* The matrix used internally to compute camera transforms.
*
* @name Phaser.Renderer.WebGL.RenderNodes.Single#_camMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.90.0
* @private
*/
this._camMatrix = new TransformMatrix();
/**
* The matrix used internally to compute sprite transforms.
*
* @name Phaser.Renderer.WebGL.RenderNodes.Single#_spriteMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.90.0
* @private
*/
this._spriteMatrix = new TransformMatrix();
/**
* The matrix used internally to compute the final transform.
*
* @name Phaser.Renderer.WebGL.RenderNodes.Single#_calcMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.90.0
* @private
*/
this._calcMatrix = new TransformMatrix();
},
/**
@ -43,7 +74,16 @@ var ImageQuadrangulateBatch = new Class({
run: function (drawingContext, gameObject, parentMatrix)
{
var frame = gameObject.frame;
var frameX = frame.x;
var frameY = frame.y;
var frameWidth = frame.cutWidth;
var frameHeight = frame.cutHeight;
var customPivot = frame.customPivot;
var displayOriginX = gameObject.displayOriginX;
var displayOriginY = gameObject.displayOriginY;
// Get UVs.
var uvSource = frame;
if (gameObject.isCropped)
{
@ -54,6 +94,13 @@ var ImageQuadrangulateBatch = new Class({
{
gameObject.frame.updateCropUVs(crop, gameObject.flipX, gameObject.flipY);
}
// Modify the frame dimensions based on the crop.
frameWidth = crop.width;
frameHeight = crop.height;
frameX = crop.x;
frameY = crop.y;
}
var u0 = uvSource.u0;
var v0 = uvSource.v0;
@ -61,29 +108,90 @@ var ImageQuadrangulateBatch = new Class({
var v1 = uvSource.v1;
var cameraAlpha = drawingContext.camera.alpha;
// Get tints.
var tintTL = getTint(gameObject.tintTopLeft, cameraAlpha * gameObject._alphaTL);
var tintTR = getTint(gameObject.tintTopRight, cameraAlpha * gameObject._alphaTR);
var tintBL = getTint(gameObject.tintBottomLeft, cameraAlpha * gameObject._alphaBL);
var tintBR = getTint(gameObject.tintBottomRight, cameraAlpha * gameObject._alphaBR);
// Render with separate matrices.
// Get the transformed quad.
var x = -displayOriginX + frameX;
var y = -displayOriginY + frameY;
var matrices = this.manager.nodes.GetSBRQuadMatrices.run(gameObject, drawingContext.camera, parentMatrix);
var flipX = 1;
var flipY = 1;
// Use `frame.source.glTexture` instead of `frame.glTexture` to avoid
// unnecessary getter function calls.
if (gameObject.flipX)
{
if (!customPivot)
{
x += (-frame.realWidth + (displayOriginX * 2));
}
this.manager.nodes.BatchTexturedTintedRawQuads.batch(
flipX = -1;
}
if (gameObject.flipY)
{
if (!customPivot)
{
y += (-frame.realHeight + (displayOriginY * 2));
}
flipY = -1;
}
var gx = gameObject.x;
var gy = gameObject.y;
var camera = drawingContext.camera;
var calcMatrix = this._calcMatrix;
var camMatrix = this._camMatrix;
var spriteMatrix = this._spriteMatrix;
spriteMatrix.applyITRS(gx, gy, gameObject.rotation, gameObject.scaleX * flipX, gameObject.scaleY * flipY);
if (parentMatrix)
{
// Multiply the camera by the parent matrix
camMatrix.copyFrom(camera.matrix);
camMatrix.multiplyWithOffset(parentMatrix, -camera.scrollX * gameObject.scrollFactorX, -camera.scrollY * gameObject.scrollFactorY);
// Undo the camera scroll
spriteMatrix.e = gx;
spriteMatrix.f = gy;
}
else
{
// camMatrix will not be mutated after this point, so we just take a reference.
camMatrix = camera.matrix;
spriteMatrix.e -= camera.scrollX * gameObject.scrollFactorX;
spriteMatrix.f -= camera.scrollY * gameObject.scrollFactorY;
}
// Multiply by the Sprite matrix, store result in calcMatrix
camMatrix.multiply(spriteMatrix, calcMatrix);
var quad = calcMatrix.setQuad(x, y, x + frameWidth, y + frameHeight);
this.manager.nodes.BatchTexturedTintedTransformedQuads.batch(
drawingContext,
frame.source.glTexture,
gameObject.tintFill,
matrices.objectMatrix,
matrices.worldMatrix,
matrices.camMatrix,
// Use `frame.source.glTexture` instead of `frame.glTexture`
// to avoid unnecessary getter function calls.
frame.source.glTexture,
// Transformed quad in TRIANGLE_STRIP order:
quad[0], quad[1],
quad[2], quad[3],
quad[6], quad[7],
quad[4], quad[5],
// Texture coordinates in X, Y, Width, Height:
u0, v0, u1 - u0, v1 - v0,
gameObject.tintFill,
// Tint colors in TRIANGLE_STRIP order:
tintTL, tintTR, tintBL, tintBR
);

View file

@ -7,11 +7,10 @@
var EventEmitter = require('eventemitter3');
var Class = require('../../../utils/Class');
var Events = require('../../events');
var BatchTexturedTintedRawQuads = require('./BatchTexturedTintedRawQuads');
var BatchTexturedTintedTransformedQuads = require('./BatchTexturedTintedTransformedQuads');
var Camera = require('./Camera');
var FillCamera = require('./FillCamera');
var FillRect = require('./FillRect');
var GetSBRQuadMatrices = require('./GetSBRQuadMatrices');
var ImageQuadrangulateBatch = require('./ImageQuadrangulateBatch');
var ListCompositor = require('./ListCompositor');
@ -77,11 +76,10 @@ var RenderNodeManager = new Class({
* @since 3.90.0
*/
this.nodes = {
BatchTexturedTintedRawQuads: new BatchTexturedTintedRawQuads(this, renderer),
BatchTexturedTintedTransformedQuads: new BatchTexturedTintedTransformedQuads(this, renderer),
Camera: new Camera(this, renderer),
FillCamera: new FillCamera(this, renderer),
FillRect: new FillRect(this, renderer),
GetSBRQuadMatrices: new GetSBRQuadMatrices(this, renderer),
ImageQuadrangulateBatch: new ImageQuadrangulateBatch(this, renderer),
ListCompositor: new ListCompositor(this, renderer),
};

View file

@ -25,6 +25,5 @@
* @property {number} stride - The stride of the attribute data.
* @property {number} count - The maximum number of elements in the buffer.
* @property {GLenum} usage - The usage pattern of the data store. gl.STATIC_DRAW, gl.DYNAMIC_DRAW or gl.STREAM_DRAW.
* @property {number} instanceDivisor - The instance divisor of the attribute data. This is how many vertex instances to draw before moving to the next one.
* @property {Phaser.Types.Renderer.WebGL.WebGLAttributeLayout} layout - The layout of the attribute data.
*/

View file

@ -68,25 +68,6 @@ var WebGLVAOWrapper = new Class({
*/
this.attributeBufferLayouts = attributeBufferLayouts;
/**
* The current instance offset for this VAO.
*
* When using instanced rendering, there is no way to offset
* the instance data in the draw call itself.
* It only specifies the number of instances to draw,
* starting from 0.
* Instead, we we must rebind the attributes with a different
* offset so they start at the desired instance.
* This property stores the current instance offset,
* so it can be updated when necessary.
*
* @name Phaser.Renderer.WebGL.Wrappers.WebGLVAOWrapper#instanceOffset
* @type {number}
* @default 0
* @since 3.90.0
*/
this.instanceOffset = 0;
/**
* The state object used to bind this VAO.
*
@ -111,16 +92,11 @@ var WebGLVAOWrapper = new Class({
{
var gl = this.renderer.gl;
var extVAO = this.renderer.vaoExtension;
var extInstances = this.renderer.instancedArraysExtension;
if (!extVAO)
{
throw new Error('WebGLVertexArrayObject not supported by this browser');
}
if (!extInstances)
{
throw new Error('ANGLE_instanced_arrays not supported by this browser');
}
this.vertexArrayObject = extVAO.createVertexArrayOES();
@ -137,7 +113,6 @@ var WebGLVAOWrapper = new Class({
attributeBufferLayout.buffer.bind();
var stride = attributeBufferLayout.layout.stride;
var instanceDivisor = attributeBufferLayout.layout.instanceDivisor || 0;
for (var j = 0; j < attributeBufferLayout.layout.layout.length; j++)
{
@ -164,11 +139,6 @@ var WebGLVAOWrapper = new Class({
stride,
offset + bytes * column * size
);
if (instanceDivisor > 0)
{
extInstances.vertexAttribDivisorANGLE(location + column, instanceDivisor);
}
}
}
}
@ -193,88 +163,10 @@ var WebGLVAOWrapper = new Class({
*
* @method Phaser.Renderer.WebGL.Wrappers.WebGLVAOWrapper#bind
* @since 3.90.0
* @param {number} [instanceOffset=0] - The instance from which drawing should start.
*/
bind: function (instanceOffset)
bind: function ()
{
this.renderer.glWrapper.updateVAO(this.glState);
if (instanceOffset === undefined)
{
instanceOffset = 0;
}
if (instanceOffset !== this.instanceOffset)
{
this.instanceOffset = instanceOffset;
this.bindAttributes();
}
},
/**
* Sets up the attribute pointers for this VAO,
* based on the current instance offset. Used internally.
*
* The VAO must be bound before calling this method, or all hell will break loose.
*
* This is called whenever a new instance offset is required,
* generally when a batch has more textures than a single draw call
* can support. It does not change the enabled state of the attributes,
* or the instance divisor.
* While it does require binding the relevant buffers, they are
* generally already bound as part of the render process.
*
* @method Phaser.Renderer.WebGL.Wrappers.WebGLVAOWrapper#bindAttributes
* @since 3.90.0
*/
bindAttributes: function ()
{
var gl = this.renderer.gl;
for (var i = 0; i < this.attributeBufferLayouts.length; i++)
{
var attributeBufferLayout = this.attributeBufferLayouts[i];
var layout = attributeBufferLayout.layout;
var divisor = layout.instanceDivisor;
if (divisor === 0)
{
// Skip attributes that are not instanced.
// They will always be repeated for each instance.
continue;
}
attributeBufferLayout.buffer.bind();
var stride = layout.stride;
var instanceOffset = Math.floor(this.instanceOffset / divisor) * stride;
for (var j = 0; j < layout.layout.length; j++)
{
var attribute = layout.layout[j];
var bytes = attribute.bytes || 4;
var columns = attribute.columns || 1;
var location = attribute.location;
var normalized = attribute.normalized;
var offset = attribute.offset;
var size = attribute.size;
var type = attribute.type;
for (var column = 0; column < columns; column++)
{
gl.vertexAttribPointer(
location + column,
size,
type,
normalized,
stride,
offset + bytes * column * size + instanceOffset
);
}
}
}
},
/**